X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fgame.c;h=9127a1b6a5be48bc22c593b6ff728b8a02269192;hb=4ed68b54ed9bed72d87eac4dd7b117fc4b8022d6;hp=3544dcf7be975d6724bca3a9999dd46455f1751d;hpb=7a049c0017388d5d816b77781e5bc368a026610f;p=rocksndiamonds.git diff --git a/src/game.c b/src/game.c index 3544dcf7..9127a1b6 100644 --- a/src/game.c +++ b/src/game.c @@ -89,123 +89,126 @@ // game panel display and control definitions #define GAME_PANEL_LEVEL_NUMBER 0 #define GAME_PANEL_GEMS 1 -#define GAME_PANEL_INVENTORY_COUNT 2 -#define GAME_PANEL_INVENTORY_FIRST_1 3 -#define GAME_PANEL_INVENTORY_FIRST_2 4 -#define GAME_PANEL_INVENTORY_FIRST_3 5 -#define GAME_PANEL_INVENTORY_FIRST_4 6 -#define GAME_PANEL_INVENTORY_FIRST_5 7 -#define GAME_PANEL_INVENTORY_FIRST_6 8 -#define GAME_PANEL_INVENTORY_FIRST_7 9 -#define GAME_PANEL_INVENTORY_FIRST_8 10 -#define GAME_PANEL_INVENTORY_LAST_1 11 -#define GAME_PANEL_INVENTORY_LAST_2 12 -#define GAME_PANEL_INVENTORY_LAST_3 13 -#define GAME_PANEL_INVENTORY_LAST_4 14 -#define GAME_PANEL_INVENTORY_LAST_5 15 -#define GAME_PANEL_INVENTORY_LAST_6 16 -#define GAME_PANEL_INVENTORY_LAST_7 17 -#define GAME_PANEL_INVENTORY_LAST_8 18 -#define GAME_PANEL_KEY_1 19 -#define GAME_PANEL_KEY_2 20 -#define GAME_PANEL_KEY_3 21 -#define GAME_PANEL_KEY_4 22 -#define GAME_PANEL_KEY_5 23 -#define GAME_PANEL_KEY_6 24 -#define GAME_PANEL_KEY_7 25 -#define GAME_PANEL_KEY_8 26 -#define GAME_PANEL_KEY_WHITE 27 -#define GAME_PANEL_KEY_WHITE_COUNT 28 -#define GAME_PANEL_SCORE 29 -#define GAME_PANEL_HIGHSCORE 30 -#define GAME_PANEL_TIME 31 -#define GAME_PANEL_TIME_HH 32 -#define GAME_PANEL_TIME_MM 33 -#define GAME_PANEL_TIME_SS 34 -#define GAME_PANEL_TIME_ANIM 35 -#define GAME_PANEL_HEALTH 36 -#define GAME_PANEL_HEALTH_ANIM 37 -#define GAME_PANEL_FRAME 38 -#define GAME_PANEL_SHIELD_NORMAL 39 -#define GAME_PANEL_SHIELD_NORMAL_TIME 40 -#define GAME_PANEL_SHIELD_DEADLY 41 -#define GAME_PANEL_SHIELD_DEADLY_TIME 42 -#define GAME_PANEL_EXIT 43 -#define GAME_PANEL_EMC_MAGIC_BALL 44 -#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 45 -#define GAME_PANEL_LIGHT_SWITCH 46 -#define GAME_PANEL_LIGHT_SWITCH_TIME 47 -#define GAME_PANEL_TIMEGATE_SWITCH 48 -#define GAME_PANEL_TIMEGATE_SWITCH_TIME 49 -#define GAME_PANEL_SWITCHGATE_SWITCH 50 -#define GAME_PANEL_EMC_LENSES 51 -#define GAME_PANEL_EMC_LENSES_TIME 52 -#define GAME_PANEL_EMC_MAGNIFIER 53 -#define GAME_PANEL_EMC_MAGNIFIER_TIME 54 -#define GAME_PANEL_BALLOON_SWITCH 55 -#define GAME_PANEL_DYNABOMB_NUMBER 56 -#define GAME_PANEL_DYNABOMB_SIZE 57 -#define GAME_PANEL_DYNABOMB_POWER 58 -#define GAME_PANEL_PENGUINS 59 -#define GAME_PANEL_SOKOBAN_OBJECTS 60 -#define GAME_PANEL_SOKOBAN_FIELDS 61 -#define GAME_PANEL_ROBOT_WHEEL 62 -#define GAME_PANEL_CONVEYOR_BELT_1 63 -#define GAME_PANEL_CONVEYOR_BELT_2 64 -#define GAME_PANEL_CONVEYOR_BELT_3 65 -#define GAME_PANEL_CONVEYOR_BELT_4 66 -#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 67 -#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 68 -#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 69 -#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 70 -#define GAME_PANEL_MAGIC_WALL 71 -#define GAME_PANEL_MAGIC_WALL_TIME 72 -#define GAME_PANEL_GRAVITY_STATE 73 -#define GAME_PANEL_GRAPHIC_1 74 -#define GAME_PANEL_GRAPHIC_2 75 -#define GAME_PANEL_GRAPHIC_3 76 -#define GAME_PANEL_GRAPHIC_4 77 -#define GAME_PANEL_GRAPHIC_5 78 -#define GAME_PANEL_GRAPHIC_6 79 -#define GAME_PANEL_GRAPHIC_7 80 -#define GAME_PANEL_GRAPHIC_8 81 -#define GAME_PANEL_ELEMENT_1 82 -#define GAME_PANEL_ELEMENT_2 83 -#define GAME_PANEL_ELEMENT_3 84 -#define GAME_PANEL_ELEMENT_4 85 -#define GAME_PANEL_ELEMENT_5 86 -#define GAME_PANEL_ELEMENT_6 87 -#define GAME_PANEL_ELEMENT_7 88 -#define GAME_PANEL_ELEMENT_8 89 -#define GAME_PANEL_ELEMENT_COUNT_1 90 -#define GAME_PANEL_ELEMENT_COUNT_2 91 -#define GAME_PANEL_ELEMENT_COUNT_3 92 -#define GAME_PANEL_ELEMENT_COUNT_4 93 -#define GAME_PANEL_ELEMENT_COUNT_5 94 -#define GAME_PANEL_ELEMENT_COUNT_6 95 -#define GAME_PANEL_ELEMENT_COUNT_7 96 -#define GAME_PANEL_ELEMENT_COUNT_8 97 -#define GAME_PANEL_CE_SCORE_1 98 -#define GAME_PANEL_CE_SCORE_2 99 -#define GAME_PANEL_CE_SCORE_3 100 -#define GAME_PANEL_CE_SCORE_4 101 -#define GAME_PANEL_CE_SCORE_5 102 -#define GAME_PANEL_CE_SCORE_6 103 -#define GAME_PANEL_CE_SCORE_7 104 -#define GAME_PANEL_CE_SCORE_8 105 -#define GAME_PANEL_CE_SCORE_1_ELEMENT 106 -#define GAME_PANEL_CE_SCORE_2_ELEMENT 107 -#define GAME_PANEL_CE_SCORE_3_ELEMENT 108 -#define GAME_PANEL_CE_SCORE_4_ELEMENT 109 -#define GAME_PANEL_CE_SCORE_5_ELEMENT 110 -#define GAME_PANEL_CE_SCORE_6_ELEMENT 111 -#define GAME_PANEL_CE_SCORE_7_ELEMENT 112 -#define GAME_PANEL_CE_SCORE_8_ELEMENT 113 -#define GAME_PANEL_PLAYER_NAME 114 -#define GAME_PANEL_LEVEL_NAME 115 -#define GAME_PANEL_LEVEL_AUTHOR 116 - -#define NUM_GAME_PANEL_CONTROLS 117 +#define GAME_PANEL_GEMS_NEEDED 2 +#define GAME_PANEL_GEMS_COLLECTED 3 +#define GAME_PANEL_GEMS_SCORE 4 +#define GAME_PANEL_INVENTORY_COUNT 5 +#define GAME_PANEL_INVENTORY_FIRST_1 6 +#define GAME_PANEL_INVENTORY_FIRST_2 7 +#define GAME_PANEL_INVENTORY_FIRST_3 8 +#define GAME_PANEL_INVENTORY_FIRST_4 9 +#define GAME_PANEL_INVENTORY_FIRST_5 10 +#define GAME_PANEL_INVENTORY_FIRST_6 11 +#define GAME_PANEL_INVENTORY_FIRST_7 12 +#define GAME_PANEL_INVENTORY_FIRST_8 13 +#define GAME_PANEL_INVENTORY_LAST_1 14 +#define GAME_PANEL_INVENTORY_LAST_2 15 +#define GAME_PANEL_INVENTORY_LAST_3 16 +#define GAME_PANEL_INVENTORY_LAST_4 17 +#define GAME_PANEL_INVENTORY_LAST_5 18 +#define GAME_PANEL_INVENTORY_LAST_6 19 +#define GAME_PANEL_INVENTORY_LAST_7 20 +#define GAME_PANEL_INVENTORY_LAST_8 21 +#define GAME_PANEL_KEY_1 22 +#define GAME_PANEL_KEY_2 23 +#define GAME_PANEL_KEY_3 24 +#define GAME_PANEL_KEY_4 25 +#define GAME_PANEL_KEY_5 26 +#define GAME_PANEL_KEY_6 27 +#define GAME_PANEL_KEY_7 28 +#define GAME_PANEL_KEY_8 29 +#define GAME_PANEL_KEY_WHITE 30 +#define GAME_PANEL_KEY_WHITE_COUNT 31 +#define GAME_PANEL_SCORE 32 +#define GAME_PANEL_HIGHSCORE 33 +#define GAME_PANEL_TIME 34 +#define GAME_PANEL_TIME_HH 35 +#define GAME_PANEL_TIME_MM 36 +#define GAME_PANEL_TIME_SS 37 +#define GAME_PANEL_TIME_ANIM 38 +#define GAME_PANEL_HEALTH 39 +#define GAME_PANEL_HEALTH_ANIM 40 +#define GAME_PANEL_FRAME 41 +#define GAME_PANEL_SHIELD_NORMAL 42 +#define GAME_PANEL_SHIELD_NORMAL_TIME 43 +#define GAME_PANEL_SHIELD_DEADLY 44 +#define GAME_PANEL_SHIELD_DEADLY_TIME 45 +#define GAME_PANEL_EXIT 46 +#define GAME_PANEL_EMC_MAGIC_BALL 47 +#define GAME_PANEL_EMC_MAGIC_BALL_SWITCH 48 +#define GAME_PANEL_LIGHT_SWITCH 49 +#define GAME_PANEL_LIGHT_SWITCH_TIME 50 +#define GAME_PANEL_TIMEGATE_SWITCH 51 +#define GAME_PANEL_TIMEGATE_SWITCH_TIME 52 +#define GAME_PANEL_SWITCHGATE_SWITCH 53 +#define GAME_PANEL_EMC_LENSES 54 +#define GAME_PANEL_EMC_LENSES_TIME 55 +#define GAME_PANEL_EMC_MAGNIFIER 56 +#define GAME_PANEL_EMC_MAGNIFIER_TIME 57 +#define GAME_PANEL_BALLOON_SWITCH 58 +#define GAME_PANEL_DYNABOMB_NUMBER 59 +#define GAME_PANEL_DYNABOMB_SIZE 60 +#define GAME_PANEL_DYNABOMB_POWER 61 +#define GAME_PANEL_PENGUINS 62 +#define GAME_PANEL_SOKOBAN_OBJECTS 63 +#define GAME_PANEL_SOKOBAN_FIELDS 64 +#define GAME_PANEL_ROBOT_WHEEL 65 +#define GAME_PANEL_CONVEYOR_BELT_1 66 +#define GAME_PANEL_CONVEYOR_BELT_2 67 +#define GAME_PANEL_CONVEYOR_BELT_3 68 +#define GAME_PANEL_CONVEYOR_BELT_4 69 +#define GAME_PANEL_CONVEYOR_BELT_1_SWITCH 70 +#define GAME_PANEL_CONVEYOR_BELT_2_SWITCH 71 +#define GAME_PANEL_CONVEYOR_BELT_3_SWITCH 72 +#define GAME_PANEL_CONVEYOR_BELT_4_SWITCH 73 +#define GAME_PANEL_MAGIC_WALL 74 +#define GAME_PANEL_MAGIC_WALL_TIME 75 +#define GAME_PANEL_GRAVITY_STATE 76 +#define GAME_PANEL_GRAPHIC_1 77 +#define GAME_PANEL_GRAPHIC_2 78 +#define GAME_PANEL_GRAPHIC_3 79 +#define GAME_PANEL_GRAPHIC_4 80 +#define GAME_PANEL_GRAPHIC_5 81 +#define GAME_PANEL_GRAPHIC_6 82 +#define GAME_PANEL_GRAPHIC_7 83 +#define GAME_PANEL_GRAPHIC_8 84 +#define GAME_PANEL_ELEMENT_1 85 +#define GAME_PANEL_ELEMENT_2 86 +#define GAME_PANEL_ELEMENT_3 87 +#define GAME_PANEL_ELEMENT_4 88 +#define GAME_PANEL_ELEMENT_5 89 +#define GAME_PANEL_ELEMENT_6 90 +#define GAME_PANEL_ELEMENT_7 91 +#define GAME_PANEL_ELEMENT_8 92 +#define GAME_PANEL_ELEMENT_COUNT_1 93 +#define GAME_PANEL_ELEMENT_COUNT_2 94 +#define GAME_PANEL_ELEMENT_COUNT_3 95 +#define GAME_PANEL_ELEMENT_COUNT_4 96 +#define GAME_PANEL_ELEMENT_COUNT_5 97 +#define GAME_PANEL_ELEMENT_COUNT_6 98 +#define GAME_PANEL_ELEMENT_COUNT_7 99 +#define GAME_PANEL_ELEMENT_COUNT_8 100 +#define GAME_PANEL_CE_SCORE_1 101 +#define GAME_PANEL_CE_SCORE_2 102 +#define GAME_PANEL_CE_SCORE_3 103 +#define GAME_PANEL_CE_SCORE_4 104 +#define GAME_PANEL_CE_SCORE_5 105 +#define GAME_PANEL_CE_SCORE_6 106 +#define GAME_PANEL_CE_SCORE_7 107 +#define GAME_PANEL_CE_SCORE_8 108 +#define GAME_PANEL_CE_SCORE_1_ELEMENT 109 +#define GAME_PANEL_CE_SCORE_2_ELEMENT 110 +#define GAME_PANEL_CE_SCORE_3_ELEMENT 111 +#define GAME_PANEL_CE_SCORE_4_ELEMENT 112 +#define GAME_PANEL_CE_SCORE_5_ELEMENT 113 +#define GAME_PANEL_CE_SCORE_6_ELEMENT 114 +#define GAME_PANEL_CE_SCORE_7_ELEMENT 115 +#define GAME_PANEL_CE_SCORE_8_ELEMENT 116 +#define GAME_PANEL_PLAYER_NAME 117 +#define GAME_PANEL_LEVEL_NAME 118 +#define GAME_PANEL_LEVEL_AUTHOR 119 + +#define NUM_GAME_PANEL_CONTROLS 120 struct GamePanelOrderInfo { @@ -242,6 +245,21 @@ static struct GamePanelControlInfo game_panel_controls[] = &game.panel.gems, TYPE_INTEGER, }, + { + GAME_PANEL_GEMS_NEEDED, + &game.panel.gems_needed, + TYPE_INTEGER, + }, + { + GAME_PANEL_GEMS_COLLECTED, + &game.panel.gems_collected, + TYPE_INTEGER, + }, + { + GAME_PANEL_GEMS_SCORE, + &game.panel.gems_score, + TYPE_INTEGER, + }, { GAME_PANEL_INVENTORY_COUNT, &game.panel.inventory_count, @@ -879,6 +897,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)) @@ -920,20 +942,20 @@ static struct GamePanelControlInfo game_panel_controls[] = #define ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, condition) \ (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \ (CAN_MOVE_INTO_ACID(e) && \ - Feld[x][y] == EL_ACID) || \ + Tile[x][y] == EL_ACID) || \ (condition))) #define ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, condition) \ (IN_LEV_FIELD(x, y) && (IS_FREE_OR_PLAYER(x, y) || \ (CAN_MOVE_INTO_ACID(e) && \ - Feld[x][y] == EL_ACID) || \ + Tile[x][y] == EL_ACID) || \ (condition))) #define ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, condition) \ (IN_LEV_FIELD(x, y) && (IS_FREE(x, y) || \ (condition) || \ (CAN_MOVE_INTO_ACID(e) && \ - Feld[x][y] == EL_ACID) || \ + Tile[x][y] == EL_ACID) || \ (DONT_COLLIDE_WITH(e) && \ IS_PLAYER(x, y) && \ !PLAYER_ENEMY_PROTECTED(x, y)))) @@ -945,33 +967,33 @@ static struct GamePanelControlInfo game_panel_controls[] = ELEMENT_CAN_ENTER_FIELD_BASE_2(EL_SATELLITE, x, y, 0) #define ANDROID_CAN_ENTER_FIELD(e, x, y) \ - ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Feld[x][y] == EL_EMC_PLANT) + ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, Tile[x][y] == EL_EMC_PLANT) #define ANDROID_CAN_CLONE_FIELD(x, y) \ - (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Feld[x][y]) || \ + (IN_LEV_FIELD(x, y) && (CAN_BE_CLONED_BY_ANDROID(Tile[x][y]) || \ CAN_BE_CLONED_BY_ANDROID(EL_TRIGGER_ELEMENT))) #define ENEMY_CAN_ENTER_FIELD(e, x, y) \ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0) #define YAMYAM_CAN_ENTER_FIELD(e, x, y) \ - ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Feld[x][y] == EL_DIAMOND) + ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND) #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \ - ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Feld[x][y])) + ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y])) #define PACMAN_CAN_ENTER_FIELD(e, x, y) \ - ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Feld[x][y])) + ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y])) #define PIG_CAN_ENTER_FIELD(e, x, y) \ - ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y])) + ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Tile[x][y])) #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \ - ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \ - Feld[x][y] == EL_EM_EXIT_OPEN || \ - Feld[x][y] == EL_STEEL_EXIT_OPEN || \ - Feld[x][y] == EL_EM_STEEL_EXIT_OPEN || \ - IS_FOOD_PENGUIN(Feld[x][y]))) + ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Tile[x][y] == EL_EXIT_OPEN || \ + Tile[x][y] == EL_EM_EXIT_OPEN || \ + Tile[x][y] == EL_STEEL_EXIT_OPEN || \ + Tile[x][y] == EL_EM_STEEL_EXIT_OPEN || \ + IS_FOOD_PENGUIN(Tile[x][y]))) #define DRAGON_CAN_ENTER_FIELD(e, x, y) \ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0) @@ -982,14 +1004,14 @@ static struct GamePanelControlInfo game_panel_controls[] = ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0) #define SPRING_CAN_BUMP_FROM_FIELD(x, y) \ - (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER || \ - Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE)) + (IN_LEV_FIELD(x, y) && (Tile[x][y] == EL_EMC_SPRING_BUMPER || \ + Tile[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE)) #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element) #define CE_ENTER_FIELD_COND(e, x, y) \ (!IS_PLAYER(x, y) && \ - IS_EQUAL_OR_IN_GROUP(Feld[x][y], MOVE_ENTER_EL(e))) + IS_EQUAL_OR_IN_GROUP(Tile[x][y], MOVE_ENTER_EL(e))) #define CUSTOM_ELEMENT_CAN_ENTER_FIELD(e, x, y) \ ELEMENT_CAN_ENTER_FIELD_BASE_4(e, x, y, CE_ENTER_FIELD_COND(e, x, y)) @@ -1013,19 +1035,22 @@ static struct GamePanelControlInfo game_panel_controls[] = #define GAME_CTRL_ID_SAVE 5 #define GAME_CTRL_ID_PAUSE2 6 #define GAME_CTRL_ID_LOAD 7 -#define GAME_CTRL_ID_PANEL_STOP 8 -#define GAME_CTRL_ID_PANEL_PAUSE 9 -#define GAME_CTRL_ID_PANEL_PLAY 10 -#define GAME_CTRL_ID_TOUCH_STOP 11 -#define GAME_CTRL_ID_TOUCH_PAUSE 12 -#define SOUND_CTRL_ID_MUSIC 13 -#define SOUND_CTRL_ID_LOOPS 14 -#define SOUND_CTRL_ID_SIMPLE 15 -#define SOUND_CTRL_ID_PANEL_MUSIC 16 -#define SOUND_CTRL_ID_PANEL_LOOPS 17 -#define SOUND_CTRL_ID_PANEL_SIMPLE 18 - -#define NUM_GAME_BUTTONS 19 +#define GAME_CTRL_ID_RESTART 8 +#define GAME_CTRL_ID_PANEL_STOP 9 +#define GAME_CTRL_ID_PANEL_PAUSE 10 +#define GAME_CTRL_ID_PANEL_PLAY 11 +#define GAME_CTRL_ID_PANEL_RESTART 12 +#define GAME_CTRL_ID_TOUCH_STOP 13 +#define GAME_CTRL_ID_TOUCH_PAUSE 14 +#define GAME_CTRL_ID_TOUCH_RESTART 15 +#define SOUND_CTRL_ID_MUSIC 16 +#define SOUND_CTRL_ID_LOOPS 17 +#define SOUND_CTRL_ID_SIMPLE 18 +#define SOUND_CTRL_ID_PANEL_MUSIC 19 +#define SOUND_CTRL_ID_PANEL_LOOPS 20 +#define SOUND_CTRL_ID_PANEL_SIMPLE 21 + +#define NUM_GAME_BUTTONS 22 // forward declaration for internal use @@ -1054,7 +1079,10 @@ static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *); static void KillPlayerUnlessEnemyProtected(int, int); static void KillPlayerUnlessExplosionProtected(int, int); +static void CheckNextToConditions(int, int); +static void TestIfPlayerNextToCustomElement(int, int); static void TestIfPlayerTouchesCustomElement(int, int); +static void TestIfElementNextToCustomElement(int, int); static void TestIfElementTouchesCustomElement(int, int); static void TestIfElementHitsCustomElement(int, int, int); @@ -1062,15 +1090,17 @@ static void HandleElementChange(int, int, int); static void ExecuteCustomElementAction(int, int, int, int); static boolean ChangeElement(int, int, int, int); -static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int); +static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int); #define CheckTriggeredElementChange(x, y, e, ev) \ - CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1) + CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1) #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \ CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1) #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \ CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1) #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \ CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p) +#define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \ + CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1) static boolean CheckElementChangeExt(int, int, int, int, int, int, int); #define CheckElementChange(x, y, e, te, ev) \ @@ -1079,6 +1109,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); @@ -1092,13 +1124,13 @@ static void FadeLevelSoundsAndMusic(void); static void HandleGameButtons(struct GadgetInfo *); -int AmoebeNachbarNr(int, int); -void AmoebeUmwandeln(int, int); +int AmoebaNeighbourNr(int, int); +void AmoebaToDiamond(int, int); 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 +1151,8 @@ void ExitPlayer(struct PlayerInfo *); static int getInvisibleActiveFromInvisibleElement(int); static int getInvisibleFromInvisibleActiveElement(int); +static void TestFieldAfterSnapping(int, int, int, int, int); + static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS]; // for detection of endless loops, caused by custom element programming @@ -1541,11 +1575,19 @@ access_direction_list[] = { EL_UNDEFINED, MV_NONE } }; +static struct XY xy_topdown[] = +{ + { 0, -1 }, + { -1, 0 }, + { +1, 0 }, + { 0, +1 } +}; + static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS]; #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY]) #define IS_JUST_CHANGING(x, y) (ChangeDelay[x][y] != 0) -#define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Feld[x][y]) || \ +#define IS_CHANGING(x, y) (IS_AUTO_CHANGING(Tile[x][y]) || \ IS_JUST_CHANGING(x, y)) #define CE_PAGE(e, ce) (element_info[e].event_page[ce]) @@ -1717,7 +1759,7 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) { if (stored_player[0].present) { - Feld[x][y] = EL_SP_MURPHY_CLONE; + Tile[x][y] = EL_SP_MURPHY_CLONE; return; } @@ -1730,13 +1772,13 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) stored_player[0].artwork_element = EL_SP_MURPHY; } - Feld[x][y] = EL_PLAYER_1; + Tile[x][y] = EL_PLAYER_1; } } if (init_game) { - struct PlayerInfo *player = &stored_player[Feld[x][y] - EL_PLAYER_1]; + struct PlayerInfo *player = &stored_player[Tile[x][y] - EL_PLAYER_1]; int jx = player->jx, jy = player->jy; player->present = TRUE; @@ -1763,23 +1805,21 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) player->active = TRUE; // remove potentially duplicate players - if (StorePlayer[jx][jy] == Feld[x][y]) + if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y]) StorePlayer[jx][jy] = 0; - StorePlayer[x][y] = Feld[x][y]; + StorePlayer[x][y] = Tile[x][y]; #if DEBUG_INIT_PLAYER - if (options.debug) - { - printf("- player element %d activated", player->element_nr); - printf(" (local player is %d and currently %s)\n", - local_player->element_nr, - local_player->active ? "active" : "not active"); - } + Debug("game:init:player", "- player element %d activated", + player->element_nr); + Debug("game:init:player", " (local player is %d and currently %s)", + local_player->element_nr, + local_player->active ? "active" : "not active"); } #endif - Feld[x][y] = EL_EMPTY; + Tile[x][y] = EL_EMPTY; player->jx = player->last_jx = x; player->jy = player->last_jy = y; @@ -1795,9 +1835,32 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) } } +static void InitFieldForEngine_RND(int x, int y) +{ + int element = Tile[x][y]; + + // convert BD engine elements to corresponding R'n'D engine elements + element = (element == EL_BD_EMPTY ? EL_EMPTY : + element == EL_BD_PLAYER ? EL_PLAYER_1 : + element == EL_BD_INBOX ? EL_PLAYER_1 : + element == EL_BD_SAND ? EL_SAND : + element == EL_BD_STEELWALL ? EL_STEELWALL : + element == EL_BD_EXIT_CLOSED ? EL_EXIT_CLOSED : + element == EL_BD_EXIT_OPEN ? EL_EXIT_OPEN : + element); + + Tile[x][y] = element; +} + +static void InitFieldForEngine(int x, int y) +{ + if (level.game_engine_type == GAME_ENGINE_TYPE_RND) + InitFieldForEngine_RND(x, y); +} + static void InitField(int x, int y, boolean init_game) { - int element = Feld[x][y]; + int element = Tile[x][y]; switch (element) { @@ -1810,10 +1873,10 @@ static void InitField(int x, int y, boolean init_game) break; case EL_SOKOBAN_FIELD_PLAYER: - element = Feld[x][y] = EL_PLAYER_1; + element = Tile[x][y] = EL_PLAYER_1; InitField(x, y, init_game); - element = Feld[x][y] = EL_SOKOBAN_FIELD_EMPTY; + element = Tile[x][y] = EL_SOKOBAN_FIELD_EMPTY; InitField(x, y, init_game); break; @@ -1826,16 +1889,16 @@ static void InitField(int x, int y, boolean init_game) break; case EL_STONEBLOCK: - if (x < lev_fieldx-1 && Feld[x+1][y] == EL_ACID) - Feld[x][y] = EL_ACID_POOL_TOPLEFT; - else if (x > 0 && Feld[x-1][y] == EL_ACID) - Feld[x][y] = EL_ACID_POOL_TOPRIGHT; - else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPLEFT) - Feld[x][y] = EL_ACID_POOL_BOTTOMLEFT; - else if (y > 0 && Feld[x][y-1] == EL_ACID) - Feld[x][y] = EL_ACID_POOL_BOTTOM; - else if (y > 0 && Feld[x][y-1] == EL_ACID_POOL_TOPRIGHT) - Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT; + if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID) + Tile[x][y] = EL_ACID_POOL_TOPLEFT; + else if (x > 0 && Tile[x - 1][y] == EL_ACID) + Tile[x][y] = EL_ACID_POOL_TOPRIGHT; + else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT) + Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT; + else if (y > 0 && Tile[x][y - 1] == EL_ACID) + Tile[x][y] = EL_ACID_POOL_BOTTOM; + else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT) + Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT; break; case EL_BUG: @@ -1890,7 +1953,7 @@ static void InitField(int x, int y, boolean init_game) case EL_AMOEBA_DROP: if (y == lev_fieldy - 1) { - Feld[x][y] = EL_AMOEBA_GROWING; + Tile[x][y] = EL_AMOEBA_GROWING; Store[x][y] = EL_AMOEBA_WET; } break; @@ -1935,9 +1998,9 @@ static void InitField(int x, int y, boolean init_game) case EL_CONVEYOR_BELT_4_SWITCH_RIGHT: if (init_game) { - int belt_nr = getBeltNrFromBeltSwitchElement(Feld[x][y]); - int belt_dir = getBeltDirFromBeltSwitchElement(Feld[x][y]); - int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Feld[x][y]); + int belt_nr = getBeltNrFromBeltSwitchElement(Tile[x][y]); + int belt_dir = getBeltDirFromBeltSwitchElement(Tile[x][y]); + int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(Tile[x][y]); if (game.belt_dir_nr[belt_nr] == 3) // initial value { @@ -1946,7 +2009,7 @@ static void InitField(int x, int y, boolean init_game) } else // more than one switch -- set it like the first switch { - Feld[x][y] = Feld[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr]; + Tile[x][y] = Tile[x][y] - belt_dir_nr + game.belt_dir_nr[belt_nr]; } } break; @@ -1961,17 +2024,17 @@ static void InitField(int x, int y, boolean init_game) case EL_INVISIBLE_SAND: if (game.light_time_left > 0 || game.lenses_time_left > 0) - Feld[x][y] = getInvisibleActiveFromInvisibleElement(element); + Tile[x][y] = getInvisibleActiveFromInvisibleElement(element); break; case EL_EMC_MAGIC_BALL: if (game.ball_active) - Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE; + Tile[x][y] = EL_EMC_MAGIC_BALL_ACTIVE; break; case EL_EMC_MAGIC_BALL_SWITCH: if (game.ball_active) - Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE; + Tile[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE; break; case EL_TRIGGER_PLAYER: @@ -1999,7 +2062,7 @@ static void InitField(int x, int y, boolean init_game) case EL_NEXT_CE_7: case EL_NEXT_CE_8: // reference elements should not be used on the playfield - Feld[x][y] = EL_EMPTY; + Tile[x][y] = EL_EMPTY; break; default: @@ -2009,14 +2072,22 @@ static void InitField(int x, int y, boolean init_game) InitMovDir(x, y); if (!element_info[element].use_last_ce_value || init_game) - CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]); + CustomValue[x][y] = GET_NEW_CE_VALUE(Tile[x][y]); } else if (IS_GROUP_ELEMENT(element)) { - Feld[x][y] = GetElementFromGroupElement(element); + Tile[x][y] = GetElementFromGroupElement(element); InitField(x, y, init_game); } + else if (IS_EMPTY_ELEMENT(element)) + { + GfxElementEmpty[x][y] = element; + Tile[x][y] = EL_EMPTY; + + if (element_info[element].use_gfx_element) + game.use_masked_elements = TRUE; + } break; } @@ -2031,13 +2102,13 @@ static void InitField_WithBug1(int x, int y, boolean init_game) // not needed to call InitMovDir() -- already done by InitField()! if (game.engine_version < VERSION_IDENT(3,1,0,0) && - CAN_MOVE(Feld[x][y])) + CAN_MOVE(Tile[x][y])) InitMovDir(x, y); } static void InitField_WithBug2(int x, int y, boolean init_game) { - int old_element = Feld[x][y]; + int old_element = Tile[x][y]; InitField(x, y, init_game); @@ -2149,8 +2220,9 @@ static void InitGameControlValues(void) if (nr != i) { - Error(ERR_INFO, "'game_panel_controls' structure corrupted at %d", i); - Error(ERR_EXIT, "this should not happen -- please debug"); + Error("'game_panel_controls' structure corrupted at %d", i); + + Fail("this should not happen -- please debug"); } // force update of game controls after initialization @@ -2198,7 +2270,7 @@ static void UpdatePlayfieldElementCount(void) SCAN_PLAYFIELD(x, y) { - element_info[Feld[x][y]].element_count++; + element_info[Tile[x][y]].element_count++; } for (i = 0; i < NUM_GROUP_ELEMENTS; i++) @@ -2213,15 +2285,19 @@ static void UpdateGameControlValues(void) int i, k; int time = (game.LevelSolved ? game.LevelSolved_CountingTime : + level.game_engine_type == GAME_ENGINE_TYPE_BD ? + game_bd.time_played : level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->time : level.game_engine_type == GAME_ENGINE_TYPE_SP ? game_sp.time_played : level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.energy_left : - game.no_time_limit ? TimePlayed : TimeLeft); + game.no_level_time_limit ? TimePlayed : TimeLeft); int score = (game.LevelSolved ? game.LevelSolved_CountingScore : + level.game_engine_type == GAME_ENGINE_TYPE_BD ? + game_bd.score : level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score : level.game_engine_type == GAME_ENGINE_TYPE_SP ? @@ -2229,14 +2305,25 @@ static void UpdateGameControlValues(void) level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score : game.score); - int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ? + int gems = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? + game_bd.gems_still_needed : + level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->gems_needed : level.game_engine_type == GAME_ENGINE_TYPE_SP ? game_sp.infotrons_still_needed : level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.kettles_still_needed : game.gems_still_needed); - int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ? + int gems_needed = level.gems_needed; + int gems_collected = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? + game_bd.game->cave->diamonds_collected : + gems_needed - gems); + int gems_score = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? + game_bd.game->cave->diamond_value : + level.score[SC_EMERALD]); + int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? + game_bd.gems_still_needed > 0 : + level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->gems_needed > 0 : level.game_engine_type == GAME_ENGINE_TYPE_SP ? game_sp.infotrons_still_needed > 0 : @@ -2252,6 +2339,7 @@ static void UpdateGameControlValues(void) level.game_engine_type == GAME_ENGINE_TYPE_MM ? MM_HEALTH(game_mm.laser_overload_value) : game.health); + int sync_random_frame = INIT_GFX_RANDOM(); // random, but synchronized UpdatePlayfieldElementCount(); @@ -2260,6 +2348,9 @@ static void UpdateGameControlValues(void) // used instead of "level_nr" (for network games) game_panel_controls[GAME_PANEL_LEVEL_NUMBER].value = levelset.level_nr; game_panel_controls[GAME_PANEL_GEMS].value = gems; + game_panel_controls[GAME_PANEL_GEMS_NEEDED].value = gems_needed; + game_panel_controls[GAME_PANEL_GEMS_COLLECTED].value = gems_collected; + game_panel_controls[GAME_PANEL_GEMS_SCORE].value = gems_score; game_panel_controls[GAME_PANEL_INVENTORY_COUNT].value = 0; for (i = 0; i < MAX_NUM_KEYS; i++) @@ -2326,6 +2417,63 @@ static void UpdateGameControlValues(void) stored_player[player_nr].num_white_keys; } + // 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; + struct GamePanelControlInfo *gpc = &game_panel_controls[nr]; + struct TextPosInfo *pos = gpc->pos; + + // 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 = gpc->value; + } + else + { + // 2nd try: display key at the next free position in the key panel + for (k = 0; k < STD_NUM_KEYS; k++) + { + nr_new = GAME_PANEL_KEY_1 + k; + + if (game_panel_controls[nr_new].value == EL_EMPTY) + { + game_panel_controls[nr_new].value = gpc->value; + + break; + } + } + } + } + } + for (i = 0; i < NUM_PANEL_INVENTORY; i++) { game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value = @@ -2335,7 +2483,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; @@ -2479,11 +2627,15 @@ static void UpdateGameControlValues(void) int last_anim_random_frame = gfx.anim_random_frame; int element = gpc->value; int graphic = el2panelimg(element); + int init_gfx_random = (graphic_info[graphic].anim_global_sync ? + sync_random_frame : + graphic_info[graphic].anim_global_anim_sync ? + getGlobalAnimSyncFrame() : INIT_GFX_RANDOM()); if (gpc->value != gpc->last_value) { gpc->gfx_frame = 0; - gpc->gfx_random = INIT_GFX_RANDOM(); + gpc->gfx_random = init_gfx_random; } else { @@ -2491,7 +2643,7 @@ static void UpdateGameControlValues(void) if (ANIM_MODE(graphic) == ANIM_RANDOM && IS_NEXT_FRAME(gpc->gfx_frame, graphic)) - gpc->gfx_random = INIT_GFX_RANDOM(); + gpc->gfx_random = init_gfx_random; } if (ANIM_MODE(graphic) == ANIM_RANDOM) @@ -2500,8 +2652,7 @@ static void UpdateGameControlValues(void) if (ANIM_MODE(graphic) == ANIM_CE_SCORE) gpc->gfx_frame = element_info[element].collect_score; - gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value), - gpc->gfx_frame); + gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame); if (ANIM_MODE(graphic) == ANIM_RANDOM) gfx.anim_random_frame = last_anim_random_frame; @@ -2513,11 +2664,15 @@ static void UpdateGameControlValues(void) { int last_anim_random_frame = gfx.anim_random_frame; int graphic = gpc->graphic; + int init_gfx_random = (graphic_info[graphic].anim_global_sync ? + sync_random_frame : + graphic_info[graphic].anim_global_anim_sync ? + getGlobalAnimSyncFrame() : INIT_GFX_RANDOM()); if (gpc->value != gpc->last_value) { gpc->gfx_frame = 0; - gpc->gfx_random = INIT_GFX_RANDOM(); + gpc->gfx_random = init_gfx_random; } else { @@ -2525,7 +2680,7 @@ static void UpdateGameControlValues(void) if (ANIM_MODE(graphic) == ANIM_RANDOM && IS_NEXT_FRAME(gpc->gfx_frame, graphic)) - gpc->gfx_random = INIT_GFX_RANDOM(); + gpc->gfx_random = init_gfx_random; } if (ANIM_MODE(graphic) == ANIM_RANDOM) @@ -2590,21 +2745,35 @@ static void DisplayGameControlValues(void) if (PANEL_DEACTIVATED(pos)) continue; + if (pos->class == get_hash_from_string("extra_panel_items") && + !setup.prefer_extra_panel_items) + continue; + gpc->last_value = value; gpc->last_frame = frame; if (type == TYPE_INTEGER) { if (nr == GAME_PANEL_LEVEL_NUMBER || + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_SCORE || + nr == GAME_PANEL_HIGHSCORE || nr == GAME_PANEL_TIME) { boolean use_dynamic_size = (size == -1 ? TRUE : FALSE); if (use_dynamic_size) // use dynamic number of digits { - int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000); - int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3); - int size2 = size1 + 1; + int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_TIME ? 1000 : 100000); + int size_add = (nr == GAME_PANEL_LEVEL_NUMBER || + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_TIME ? 1 : 2); + int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_TIME ? 3 : 5); + int size2 = size1 + size_add; int font1 = pos->font; int font2 = pos->font_alt; @@ -2637,7 +2806,10 @@ static void DisplayGameControlValues(void) element = value; graphic = el2panelimg(value); - // printf("::: %d, '%s' [%d]\n", element, EL_NAME(element), size); +#if 0 + Debug("game:DisplayGameControlValues", "%d, '%s' [%d]", + element, EL_NAME(element), size); +#endif if (element >= EL_GRAPHIC_1 && element <= EL_GRAPHIC_8 && size == 0) size = TILESIZE; @@ -2665,7 +2837,7 @@ static void DisplayGameControlValues(void) int width, height; int dst_x = PANEL_XPOS(pos); int dst_y = PANEL_YPOS(pos); - boolean skip = (pos->class == get_hash_from_key("mm_engine_only") && + boolean skip = (pos->class == get_hash_from_string("mm_engine_only") && level.game_engine_type != GAME_ENGINE_TYPE_MM); if (graphic != IMG_UNDEFINED && !skip) @@ -2800,12 +2972,10 @@ void UpdateAndDisplayGameControlValues(void) DisplayGameControlValues(); } -#if 0 -static void UpdateGameDoorValues(void) +void UpdateGameDoorValues(void) { UpdateGameControlValues(); } -#endif void DrawGameDoorValues(void) { @@ -2843,16 +3013,16 @@ static void InitGameEngine(void) } #if 0 - printf("level %d: level.game_version == %06d\n", level_nr, - level.game_version); - printf(" tape.file_version == %06d\n", - tape.file_version); - printf(" tape.game_version == %06d\n", - tape.game_version); - printf(" tape.engine_version == %06d\n", - tape.engine_version); - printf(" => game.engine_version == %06d [tape mode: %s]\n", - game.engine_version, (tape.playing ? "PLAYING" : "RECORDING")); + Debug("game:init:level", "level %d: level.game_version == %06d", level_nr, + level.game_version); + Debug("game:init:level", " tape.file_version == %06d", + tape.file_version); + Debug("game:init:level", " tape.game_version == %06d", + tape.game_version); + Debug("game:init:level", " tape.engine_version == %06d", + tape.engine_version); + Debug("game:init:level", " => game.engine_version == %06d [tape mode: %s]", + game.engine_version, (tape.playing ? "PLAYING" : "RECORDING")); #endif // -------------------------------------------------------------------------- @@ -2987,9 +3157,15 @@ static void InitGameEngine(void) game_em.use_single_button = (game.engine_version > VERSION_IDENT(4,0,0,2)); + game_em.use_push_delay = + (game.engine_version > VERSION_IDENT(4,3,7,1)); + game_em.use_snap_key_bug = (game.engine_version < VERSION_IDENT(4,0,1,0)); + game_em.use_random_bug = + (tape.property_bits & TAPE_PROPERTY_EM_RANDOM_BUG); + boolean use_old_em_engine = (game.engine_version < VERSION_IDENT(4,2,0,0)); game_em.use_old_explosions = use_old_em_engine; @@ -3084,6 +3260,17 @@ static void InitGameEngine(void) SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE); } + // ---------- initialize if element can trigger global animations ----------- + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + { + struct ElementInfo *ei = &element_info[i]; + + ei->has_anim_event = FALSE; + } + + InitGlobalAnimEventsForCustomElements(); + // ---------- initialize internal run-time variables ------------------------ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) @@ -3149,12 +3336,16 @@ static void InitGameEngine(void) for (j = 0; j < ei->num_change_pages; j++) { - ei->change_page[j].actual_trigger_element = EL_EMPTY; - ei->change_page[j].actual_trigger_player = EL_EMPTY; - ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE; - ei->change_page[j].actual_trigger_side = CH_SIDE_NONE; - ei->change_page[j].actual_trigger_ce_value = 0; - ei->change_page[j].actual_trigger_ce_score = 0; + struct ElementChangeInfo *change = &ei->change_page[j]; + + change->actual_trigger_element = EL_EMPTY; + change->actual_trigger_player = EL_EMPTY; + change->actual_trigger_player_bits = CH_PLAYER_NONE; + change->actual_trigger_side = CH_SIDE_NONE; + change->actual_trigger_ce_value = 0; + change->actual_trigger_ce_score = 0; + change->actual_trigger_x = -1; + change->actual_trigger_y = -1; } } @@ -3172,16 +3363,18 @@ static void InitGameEngine(void) for (j = 0; j < ei->num_change_pages; j++) { - if (!ei->change_page[j].can_change_or_has_action) + struct ElementChangeInfo *change = &ei->change_page[j]; + + if (!change->can_change_or_has_action) continue; - if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION]) + if (change->has_event[CE_BY_OTHER_ACTION]) { - int trigger_element = ei->change_page[j].trigger_element; + int trigger_element = change->trigger_element; for (k = 0; k < NUM_CHANGE_EVENTS; k++) { - if (ei->change_page[j].has_event[k]) + if (change->has_event[k]) { if (IS_GROUP_ELEMENT(trigger_element)) { @@ -3348,8 +3541,9 @@ static void InitGameEngine(void) level.game_engine_type == GAME_ENGINE_TYPE_EM && !setup.forced_scroll_delay ? 0 : setup.scroll_delay ? setup.scroll_delay_value : 0); - game.scroll_delay_value = - MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY); + if (game.forced_scroll_delay_value == -1) + game.scroll_delay_value = + MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY); // ---------- initialize game engine snapshots ------------------------------ for (i = 0; i < MAX_PLAYERS; i++) @@ -3390,10 +3584,10 @@ static void InitGameEngine(void) { int element = EL_CUSTOM_START + i; - if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) || - HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) || - HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) || - HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X)) + if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) || + HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) || + HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) || + HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X)) game.use_mouse_actions = TRUE; } } @@ -3438,24 +3632,21 @@ static void DebugPrintPlayerStatus(char *message) if (!options.debug) return; - printf("%s:\n", message); + Debug("game:init:player", "%s:", message); for (i = 0; i < MAX_PLAYERS; i++) { struct PlayerInfo *player = &stored_player[i]; - printf("- player %d: present == %d, connected == %d [%d/%d], active == %d", - i + 1, - player->present, - player->connected, - player->connected_locally, - player->connected_network, - player->active); - - if (local_player == player) - printf(" (local player)"); - - printf("\n"); + Debug("game:init:player", + "- player %d: present == %d, connected == %d [%d/%d], active == %d%s", + i + 1, + player->present, + player->connected, + player->connected_locally, + player->connected_network, + player->active, + (local_player == player ? " (local player)" : "")); } } #endif @@ -3465,9 +3656,8 @@ void InitGame(void) int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0); int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0); int fade_mask = REDRAW_FIELD; - + boolean restarting = (game_status == GAME_MODE_PLAYING); boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found - boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found int initial_move_dir = MV_DOWN; int i, j, x, y; @@ -3478,7 +3668,15 @@ void InitGame(void) if (!game.restart_level) CloseDoor(DOOR_CLOSE_1); - SetGameStatus(GAME_MODE_PLAYING); + if (restarting) + { + // force fading out global animations displayed during game play + SetGameStatus(GAME_MODE_PSEUDO_RESTARTING); + } + else + { + SetGameStatus(GAME_MODE_PLAYING); + } if (level_editor_test_game) FadeSkipNextFadeOut(); @@ -3494,6 +3692,18 @@ void InitGame(void) FadeOut(fade_mask); + if (restarting) + { + // force restarting global animations displayed during game play + RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING); + + // this is required for "transforming" fade modes like cross-fading + // (else global animations will be stopped, but not restarted here) + SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING); + + SetGameStatus(GAME_MODE_PLAYING); + } + if (level_editor_test_game) FadeSkipNextFadeIn(); @@ -3509,11 +3719,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 @@ -3581,7 +3795,8 @@ void InitGame(void) player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr); - player->actual_frame_counter = 0; + player->actual_frame_counter.count = 0; + player->actual_frame_counter.value = 1; player->step_counter = 0; @@ -3646,6 +3861,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; @@ -3685,6 +3902,8 @@ void InitGame(void) TimeFrames = 0; TimePlayed = 0; TimeLeft = level.time; + + TapeTimeFrames = 0; TapeTime = 0; ScreenMovDir = MV_NONE; @@ -3715,9 +3934,12 @@ void InitGame(void) game.LevelSolved_CountingScore = 0; game.LevelSolved_CountingHealth = 0; + game.RestartGameRequested = FALSE; + game.panel.active = TRUE; - game.no_time_limit = (level.time == 0); + game.no_level_time_limit = (level.time == 0); + game.time_limit = (leveldir_current->time_limit && setup.time_limit); game.yamyam_content_nr = 0; game.robot_wheel_active = FALSE; @@ -3728,6 +3950,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; @@ -3751,6 +3976,9 @@ void InitGame(void) game.envelope_active = FALSE; + // special case: set custom artwork setting to initial value + game.use_masked_elements = game.use_masked_elements_initial; + for (i = 0; i < NUM_BELTS; i++) { game.belt_dir[i] = MV_NONE; @@ -3766,7 +3994,7 @@ void InitGame(void) SCAN_PLAYFIELD(x, y) { - Feld[x][y] = Last[x][y] = level.field[x][y]; + Tile[x][y] = Last[x][y] = level.field[x][y]; MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0; ChangeDelay[x][y] = 0; ChangePage[x][y] = -1; @@ -3792,7 +4020,9 @@ void InitGame(void) GfxFrame[x][y] = 0; GfxRandom[x][y] = INIT_GFX_RANDOM(); + GfxRandomStatic[x][y] = INIT_GFX_RANDOM(); GfxElement[x][y] = EL_UNDEFINED; + GfxElementEmpty[x][y] = EL_EMPTY; GfxAction[x][y] = ACTION_DEFAULT; GfxDir[x][y] = MV_NONE; GfxRedraw[x][y] = GFX_REDRAW_NONE; @@ -3800,11 +4030,11 @@ void InitGame(void) SCAN_PLAYFIELD(x, y) { - if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y])) + InitFieldForEngine(x, y); + + if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y])) emulate_bd = FALSE; - if (emulate_sb && !IS_SB_ELEMENT(Feld[x][y])) - emulate_sb = FALSE; - if (emulate_sp && !IS_SP_ELEMENT(Feld[x][y])) + if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y])) emulate_sp = FALSE; InitField(x, y, TRUE); @@ -3814,6 +4044,10 @@ void InitGame(void) InitBeltMovement(); + // required if level does not contain any "empty space" element + if (element_info[EL_EMPTY].use_gfx_element) + game.use_masked_elements = TRUE; + for (i = 0; i < MAX_PLAYERS; i++) { struct PlayerInfo *player = &stored_player[i]; @@ -3828,7 +4062,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 @@ -3931,8 +4164,7 @@ void InitGame(void) #endif #if DEBUG_INIT_PLAYER - if (options.debug) - printf("Reassigning players ...\n"); + Debug("game:init:player", "Reassigning players ..."); #endif // check if any connected player was not found in playfield @@ -3945,8 +4177,8 @@ void InitGame(void) struct PlayerInfo *field_player = NULL; #if DEBUG_INIT_PLAYER - if (options.debug) - printf("- looking for field player for player %d ...\n", i + 1); + Debug("game:init:player", + "- looking for field player for player %d ...", i + 1); #endif // assign first free player found that is present in the playfield @@ -3971,8 +4203,8 @@ void InitGame(void) int jx = field_player->jx, jy = field_player->jy; #if DEBUG_INIT_PLAYER - if (options.debug) - printf("- found player %d\n", field_player->index_nr + 1); + Debug("game:init:player", "- found player %d", + field_player->index_nr + 1); #endif player->present = FALSE; @@ -4002,9 +4234,8 @@ void InitGame(void) field_player->mapped = TRUE; #if DEBUG_INIT_PLAYER - if (options.debug) - printf("- map_player_action[%d] == %d\n", - field_player->index_nr + 1, i + 1); + Debug("game:init:player", "- map_player_action[%d] == %d", + field_player->index_nr + 1, i + 1); #endif } } @@ -4059,7 +4290,8 @@ void InitGame(void) #endif #if 0 - printf("::: local_player->present == %d\n", local_player->present); + Debug("game:init:player", "local_player->present == %d", + local_player->present); #endif // set focus to local player for network games, else to all players @@ -4092,13 +4324,13 @@ void InitGame(void) int jx = player->jx, jy = player->jy; #if DEBUG_INIT_PLAYER - if (options.debug) - printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy); + Debug("game:init:player", "Removing player %d at (%d, %d)", + i + 1, jx, jy); #endif player->active = FALSE; StorePlayer[jx][jy] = 0; - Feld[jx][jy] = EL_EMPTY; + Tile[jx][jy] = EL_EMPTY; } } } @@ -4115,7 +4347,7 @@ void InitGame(void) player->active = FALSE; StorePlayer[jx][jy] = 0; - Feld[jx][jy] = EL_EMPTY; + Tile[jx][jy] = EL_EMPTY; } } #endif @@ -4136,7 +4368,7 @@ void InitGame(void) player->present = FALSE; StorePlayer[jx][jy] = 0; - Feld[jx][jy] = EL_EMPTY; + Tile[jx][jy] = EL_EMPTY; } } } @@ -4202,7 +4434,7 @@ void InitGame(void) SCAN_PLAYFIELD(x, y) { - int element = Feld[x][y]; + int element = Tile[x][y]; int content; int xx, yy; boolean is_player; @@ -4227,7 +4459,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))) @@ -4245,7 +4477,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))) @@ -4266,7 +4498,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))) @@ -4290,8 +4522,17 @@ void InitGame(void) scroll_y = SCROLL_POSITION_Y(local_player->jy); } + if (game.forced_scroll_x != ARG_UNDEFINED_VALUE) + scroll_x = game.forced_scroll_x; + if (game.forced_scroll_y != ARG_UNDEFINED_VALUE) + scroll_y = game.forced_scroll_y; + // !!! FIX THIS (START) !!! - if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + if (level.game_engine_type == GAME_ENGINE_TYPE_BD) + { + InitGameEngine_BD(); + } + else if (level.game_engine_type == GAME_ENGINE_TYPE_EM) { InitGameEngine_EM(); } @@ -4380,7 +4621,6 @@ void InitGame(void) } game.restart_level = FALSE; - game.restart_game_message = NULL; game.request_active = FALSE; if (level.game_engine_type == GAME_ENGINE_TYPE_MM) @@ -4395,6 +4635,8 @@ void InitGame(void) if (setup.sound_music) PlayLevelMusic(); } + + SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions); } void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y, @@ -4413,7 +4655,7 @@ void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y, void InitMovDir(int x, int y) { - int i, element = Feld[x][y]; + int i, element = Tile[x][y]; static int xy[4][2] = { { 0, +1 }, @@ -4434,7 +4676,7 @@ void InitMovDir(int x, int y) case EL_BUG_UP: case EL_BUG_LEFT: case EL_BUG_DOWN: - Feld[x][y] = EL_BUG; + Tile[x][y] = EL_BUG; MovDir[x][y] = direction[0][element - EL_BUG_RIGHT]; break; @@ -4442,7 +4684,7 @@ void InitMovDir(int x, int y) case EL_SPACESHIP_UP: case EL_SPACESHIP_LEFT: case EL_SPACESHIP_DOWN: - Feld[x][y] = EL_SPACESHIP; + Tile[x][y] = EL_SPACESHIP; MovDir[x][y] = direction[0][element - EL_SPACESHIP_RIGHT]; break; @@ -4450,7 +4692,7 @@ void InitMovDir(int x, int y) case EL_BD_BUTTERFLY_UP: case EL_BD_BUTTERFLY_LEFT: case EL_BD_BUTTERFLY_DOWN: - Feld[x][y] = EL_BD_BUTTERFLY; + Tile[x][y] = EL_BD_BUTTERFLY; MovDir[x][y] = direction[0][element - EL_BD_BUTTERFLY_RIGHT]; break; @@ -4458,7 +4700,7 @@ void InitMovDir(int x, int y) case EL_BD_FIREFLY_UP: case EL_BD_FIREFLY_LEFT: case EL_BD_FIREFLY_DOWN: - Feld[x][y] = EL_BD_FIREFLY; + Tile[x][y] = EL_BD_FIREFLY; MovDir[x][y] = direction[0][element - EL_BD_FIREFLY_RIGHT]; break; @@ -4466,7 +4708,7 @@ void InitMovDir(int x, int y) case EL_PACMAN_UP: case EL_PACMAN_LEFT: case EL_PACMAN_DOWN: - Feld[x][y] = EL_PACMAN; + Tile[x][y] = EL_PACMAN; MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT]; break; @@ -4474,7 +4716,7 @@ void InitMovDir(int x, int y) case EL_YAMYAM_RIGHT: case EL_YAMYAM_UP: case EL_YAMYAM_DOWN: - Feld[x][y] = EL_YAMYAM; + Tile[x][y] = EL_YAMYAM; MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT]; break; @@ -4490,13 +4732,13 @@ void InitMovDir(int x, int y) case EL_MOLE_RIGHT: case EL_MOLE_UP: case EL_MOLE_DOWN: - Feld[x][y] = EL_MOLE; + Tile[x][y] = EL_MOLE; MovDir[x][y] = direction[2][element - EL_MOLE_LEFT]; break; case EL_SPRING_LEFT: case EL_SPRING_RIGHT: - Feld[x][y] = EL_SPRING; + Tile[x][y] = EL_SPRING; MovDir[x][y] = direction[2][element - EL_SPRING_LEFT]; break; @@ -4596,7 +4838,7 @@ void InitMovDir(int x, int y) void InitAmoebaNr(int x, int y) { int i; - int group_nr = AmoebeNachbarNr(x, y); + int group_nr = AmoebaNeighbourNr(x, y); if (group_nr == 0) { @@ -4615,6 +4857,40 @@ void InitAmoebaNr(int x, int y) AmoebaCnt2[group_nr]++; } +static void LevelSolved_SetFinalGameValues(void) +{ + game.time_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.time_played : + game.no_level_time_limit ? TimePlayed : TimeLeft); + game.score_time_final = (level.use_step_counter ? TimePlayed : + TimePlayed * FRAMES_PER_SECOND + TimeFrames); + + game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_BD ? game_bd.score : + 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.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 && @@ -4624,32 +4900,48 @@ static void LevelSolved(void) game.LevelSolved = TRUE; game.GameOver = TRUE; - 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); + tape.solved = TRUE; - game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft); - game.LevelSolved_CountingScore = game.score_final; - game.LevelSolved_CountingHealth = game.health_final; + // needed here to display correct panel values while player walks into exit + LevelSolved_SetFinalGameValues(); +} + +static boolean AdvanceToNextLevel(void) +{ + if (setup.increment_levels && + level_nr < leveldir_current->last_level && + !network_playing) + { + level_nr++; // advance to next level + TapeErase(); // start with empty tape + + if (setup.auto_play_next_level) + { + scores.continue_playing = TRUE; + scores.next_level_nr = level_nr; + + LoadLevel(level_nr); + + SaveLevelSetup_SeriesInfo(); + } + + return TRUE; + } + + return FALSE; } 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) { @@ -4659,6 +4951,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; @@ -4675,55 +4970,65 @@ void GameWon(void) TapeStop(); - game_over_delay_1 = 0; - game_over_delay_2 = 0; - game_over_delay_3 = game_over_delay_value_3; + game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time + game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health + game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game - time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft); + time = time_final = game.time_final; score = score_final = game.score_final; health = health_final = game.health_final; - if (level.score[SC_TIME_BONUS] > 0) + // update game panel values before (delayed) counting of score (if any) + LevelSolved_DisplayFinalGameValues(time, score, health); + + // if level has time score defined, calculate new final game values + if (time_score > 0) { + int time_final_max = 999; + int time_frames_final_max = time_final_max * FRAMES_PER_SECOND; + int time_frames = 0; + int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames; + int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames; + if (TimeLeft > 0) { time_final = 0; - score_final += TimeLeft * level.score[SC_TIME_BONUS]; + time_frames = time_frames_left; } - else if (game.no_time_limit && TimePlayed < 999) + else if (game.no_level_time_limit && TimePlayed < time_final_max) { - time_final = 999; - score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS]; + time_final = time_final_max; + time_frames = time_frames_final_max - time_frames_played; } - time_count_steps = MAX(1, ABS(time_final - time) / 100); + score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5; - game_over_delay_1 = game_over_delay_value_1; + time_count_steps = MAX(1, ABS(time_final - time) / 100); - if (level.game_engine_type == GAME_ENGINE_TYPE_MM) + if (level.game_engine_type == GAME_ENGINE_TYPE_BD) + { + // keep previous values (final values already processed here) + time_final = time; + score_final = score; + } + else 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 || + level.game_engine_type == GAME_ENGINE_TYPE_BD) { 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) @@ -4734,7 +5039,7 @@ void GameWon(void) { int x = game.exit_x; int y = game.exit_y; - int element = Feld[x][y]; + int element = Tile[x][y]; // close exit door after last player if ((game.all_players_gone && @@ -4745,7 +5050,7 @@ void GameWon(void) element == EL_EM_STEEL_EXIT_OPEN) { - Feld[x][y] = + Tile[x][y] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING : element == EL_EM_EXIT_OPEN ? EL_EM_EXIT_CLOSING : element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING: @@ -4776,72 +5081,67 @@ void GameWon(void) PlaySound(SND_GAME_WINNING); } - if (game_over_delay_1 > 0) - { - game_over_delay_1--; - - return; - } - - if (time != time_final) + if (setup.count_score_after_game) { - 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; @@ -4860,23 +5160,31 @@ 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; + // Important note: This function is not only called after "GameWon()", but also after + // "game over" (if automatically asking for restarting the game is disabled in setup) - if (game.LevelSolved_SaveTape) + if (game.LevelSolved) + game.LevelSolved_GameEnd = TRUE; + + if (game.LevelSolved_SaveTape && !score_info_tape_play) { // make sure that request dialog to save tape does not open door again if (!global.use_envelope_request) CloseDoor(DOOR_CLOSE_1); - SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape + // ask to save tape + tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr); + + // set unique basename for score tape (also saved in high score table) + strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name)); } // if no tape is to be saved, close both doors simultaneously CloseDoor(DOOR_CLOSE_ALL); - if (level_editor_test_game) + if (level_editor_test_game || score_info_tape_play) { SetGameStatus(GAME_MODE_MAIN); @@ -4885,7 +5193,7 @@ void GameEnd(void) return; } - if (!game.LevelSolved_SaveScore) + if (!game.GamePlayed || (!game.LevelSolved_SaveScore && !level.bd_intermission)) { SetGameStatus(GAME_MODE_MAIN); @@ -4901,32 +5209,20 @@ void GameEnd(void) SaveLevelSetup_SeriesInfo(); } - if (setup.increment_levels && - level_nr < leveldir_current->last_level && - !network_playing) - { - level_nr++; // advance to next level - TapeErase(); // start with empty tape - - if (setup.auto_play_next_level) - { - LoadLevel(level_nr); - - SaveLevelSetup_SeriesInfo(); - } - } + // save score and score tape before potentially erasing tape below + if (game.LevelSolved_SaveScore) + NewHighScore(last_level_nr, tape_saved); - hi_pos = NewHiScore(last_level_nr); + // increment and load next level (if possible and not configured otherwise) + AdvanceToNextLevel(); - if (hi_pos >= 0 && !setup.skip_scores_after_game) + if (game.LevelSolved_SaveScore && scores.last_added >= 0 && setup.show_scores_after_game) { SetGameStatus(GAME_MODE_SCORES); - DrawHallOfFame(last_level_nr, hi_pos); + DrawHallOfFame(last_level_nr); } - else if (setup.auto_play_next_level && setup.increment_levels && - last_level_nr < leveldir_current->last_level && - !network_playing) + else if (scores.continue_playing) { StartGameActions(network.enabled, setup.autorecord, level.random_seed); } @@ -4938,87 +5234,194 @@ void GameEnd(void) } } -int NewHiScore(int level_nr) +static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry, + boolean one_score_entry_per_name) { - int k, l; - int position = -1; - boolean one_score_entry_per_name = !program.many_scores_per_name; - - LoadScore(level_nr); + int i; - if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) || - game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score) + if (strEqual(new_entry->name, EMPTY_PLAYER_NAME)) return -1; - for (k = 0; k < MAX_SCORE_ENTRIES; k++) + for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - if (game.score_final > highscore[k].Score) + struct ScoreEntry *entry = &list->entry[i]; + boolean score_is_better = (new_entry->score > entry->score); + boolean score_is_equal = (new_entry->score == entry->score); + boolean time_is_better = (new_entry->time < entry->time); + boolean time_is_equal = (new_entry->time == entry->time); + boolean better_by_score = (score_is_better || + (score_is_equal && time_is_better)); + boolean better_by_time = (time_is_better || + (time_is_equal && score_is_better)); + boolean is_better = (level.rate_time_over_score ? better_by_time : + better_by_score); + boolean entry_is_empty = (entry->score == 0 && + entry->time == 0); + + // prevent adding server score entries if also existing in local score file + // (special case: historic score entries have an empty tape basename entry) + if (strEqual(new_entry->tape_basename, entry->tape_basename) && + !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME)) + { + // add fields from server score entry not stored in local score entry + // (currently, this means setting platform, version and country fields; + // in rare cases, this may also correct an invalid score value, as + // historic scores might have been truncated to 16-bit values locally) + *entry = *new_entry; + + return -1; + } + + if (is_better || entry_is_empty) { // player has made it to the hall of fame - if (k < MAX_SCORE_ENTRIES - 1) + if (i < MAX_SCORE_ENTRIES - 1) { int m = MAX_SCORE_ENTRIES - 1; + int l; if (one_score_entry_per_name) { - for (l = k; l < MAX_SCORE_ENTRIES; l++) - if (strEqual(setup.player_name, highscore[l].Name)) + for (l = i; l < MAX_SCORE_ENTRIES; l++) + if (strEqual(list->entry[l].name, new_entry->name)) m = l; - if (m == k) // player's new highscore overwrites his old one + if (m == i) // player's new highscore overwrites his old one goto put_into_list; } - for (l = m; l > k; l--) - { - strcpy(highscore[l].Name, highscore[l - 1].Name); - highscore[l].Score = highscore[l - 1].Score; - } + for (l = m; l > i; l--) + list->entry[l] = list->entry[l - 1]; } put_into_list: - strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN); - highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0'; - highscore[k].Score = game.score_final; - position = k; + *entry = *new_entry; - break; + return i; } else if (one_score_entry_per_name && - !strncmp(setup.player_name, highscore[k].Name, - MAX_PLAYER_NAME_LEN)) - break; // player already there with a higher score - } + strEqual(entry->name, new_entry->name)) + { + // player already in high score list with better score or time - if (position >= 0) - SaveScore(level_nr); + return -1; + } + } - return position; + // special case: new score is beyond the last high score list position + return MAX_SCORE_ENTRIES; } -static int getElementMoveStepsizeExt(int x, int y, int direction) +void NewHighScore(int level_nr, boolean tape_saved) { - int element = Feld[x][y]; - int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0); - int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0); - int horiz_move = (dx != 0); - int sign = (horiz_move ? dx : dy); - int step = sign * element_info[element].move_stepsize; + struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119) + boolean one_per_name = FALSE; - // special values for move stepsize for spring and things on conveyor belt - if (horiz_move) + strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN); + strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN); + + new_entry.score = game.score_final; + new_entry.time = game.score_time_final; + + LoadScore(level_nr); + + scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name); + + if (scores.last_added >= MAX_SCORE_ENTRIES) { - if (CAN_FALL(element) && - y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1])) - step = sign * MOVE_STEPSIZE_NORMAL / 2; - else if (element == EL_SPRING) - step = sign * MOVE_STEPSIZE_NORMAL * 2; - } + scores.last_added = MAX_SCORE_ENTRIES - 1; + scores.force_last_added = TRUE; - return step; -} + scores.entry[scores.last_added] = new_entry; + + // store last added local score entry (before merging server scores) + scores.last_added_local = scores.last_added; + + return; + } + + if (scores.last_added < 0) + return; + + SaveScore(level_nr); + + // store last added local score entry (before merging server scores) + scores.last_added_local = scores.last_added; + + if (!game.LevelSolved_SaveTape) + return; + + SaveScoreTape(level_nr); + + if (setup.ask_for_using_api_server) + { + setup.use_api_server = + Request("Upload your score and tape to the high score server?", REQ_ASK); + + if (!setup.use_api_server) + Request("Not using high score server! Use setup menu to enable again!", + REQ_CONFIRM); + + runtime.use_api_server = setup.use_api_server; + + // after asking for using API server once, do not ask again + setup.ask_for_using_api_server = FALSE; + + SaveSetup_ServerSetup(); + } + + 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) +{ + int element = Tile[x][y]; + int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0); + int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0); + int horiz_move = (dx != 0); + int sign = (horiz_move ? dx : dy); + int step = sign * element_info[element].move_stepsize; + + // special values for move stepsize for spring and things on conveyor belt + if (horiz_move) + { + if (CAN_FALL(element) && + y < lev_fieldy - 1 && IS_BELT_ACTIVE(Tile[x][y + 1])) + step = sign * MOVE_STEPSIZE_NORMAL / 2; + else if (element == EL_SPRING) + step = sign * MOVE_STEPSIZE_NORMAL * 2; + } + + return step; +} static int getElementMoveStepsize(int x, int y) { @@ -5042,11 +5445,13 @@ static void ResetGfxFrame(int x, int y) if (DrawingDeactivatedField()) return; - int element = Feld[x][y]; + int element = Tile[x][y]; int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); if (graphic_info[graphic].anim_global_sync) GfxFrame[x][y] = FrameCounter; + else if (graphic_info[graphic].anim_global_anim_sync) + GfxFrame[x][y] = getGlobalAnimSyncFrame(); else if (ANIM_MODE(graphic) == ANIM_CE_VALUE) GfxFrame[x][y] = CustomValue[x][y]; else if (ANIM_MODE(graphic) == ANIM_CE_SCORE) @@ -5071,7 +5476,7 @@ static void ResetRandomAnimationValue(int x, int y) static void InitMovingField(int x, int y, int direction) { - int element = Feld[x][y]; + int element = Tile[x][y]; int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0); int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0); int newx = x + dx; @@ -5100,8 +5505,8 @@ static void InitMovingField(int x, int y, int direction) if (is_moving_after) { - if (Feld[newx][newy] == EL_EMPTY) - Feld[newx][newy] = EL_BLOCKED; + if (Tile[newx][newy] == EL_EMPTY) + Tile[newx][newy] = EL_BLOCKED; MovDir[newx][newy] = MovDir[x][y]; @@ -5126,17 +5531,9 @@ void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y) void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y) { - int oldx = x, oldy = y; int direction = MovDir[x][y]; - - if (direction == MV_LEFT) - oldx++; - else if (direction == MV_RIGHT) - oldx--; - else if (direction == MV_UP) - oldy++; - else if (direction == MV_DOWN) - oldy--; + int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0); + int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0); *comes_from_x = oldx; *comes_from_y = oldy; @@ -5144,25 +5541,26 @@ void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y) static int MovingOrBlocked2Element(int x, int y) { - int element = Feld[x][y]; + int element = Tile[x][y]; if (element == EL_BLOCKED) { int oldx, oldy; Blocked2Moving(x, y, &oldx, &oldy); - return Feld[oldx][oldy]; + + return Tile[oldx][oldy]; } - else - return element; + + return element; } static int MovingOrBlocked2ElementIfNotLeaving(int x, int y) { // like MovingOrBlocked2Element(), but if element is moving - // and (x,y) is the field the moving element is just leaving, + // and (x, y) is the field the moving element is just leaving, // return EL_BLOCKED instead of the element value - int element = Feld[x][y]; + int element = Tile[x][y]; if (IS_MOVING(x, y)) { @@ -5171,7 +5569,7 @@ static int MovingOrBlocked2ElementIfNotLeaving(int x, int y) int oldx, oldy; Blocked2Moving(x, y, &oldx, &oldy); - return Feld[oldx][oldy]; + return Tile[oldx][oldy]; } else return EL_BLOCKED; @@ -5182,7 +5580,7 @@ static int MovingOrBlocked2ElementIfNotLeaving(int x, int y) static void RemoveField(int x, int y) { - Feld[x][y] = EL_EMPTY; + Tile[x][y] = EL_EMPTY; MovPos[x][y] = 0; MovDir[x][y] = 0; @@ -5203,7 +5601,7 @@ static void RemoveField(int x, int y) static void RemoveMovingField(int x, int y) { int oldx = x, oldy = y, newx = x, newy = y; - int element = Feld[x][y]; + int element = Tile[x][y]; int next_element = EL_UNDEFINED; if (element != EL_BLOCKED && !IS_MOVING(x, y)) @@ -5213,7 +5611,7 @@ static void RemoveMovingField(int x, int y) { Moving2Blocked(x, y, &newx, &newy); - if (Feld[newx][newy] != EL_BLOCKED) + if (Tile[newx][newy] != EL_BLOCKED) { // element is moving, but target field is not free (blocked), but // already occupied by something different (example: acid pool); @@ -5236,13 +5634,13 @@ static void RemoveMovingField(int x, int y) } if (element == EL_BLOCKED && - (Feld[oldx][oldy] == EL_QUICKSAND_EMPTYING || - Feld[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING || - Feld[oldx][oldy] == EL_MAGIC_WALL_EMPTYING || - Feld[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING || - Feld[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING || - Feld[oldx][oldy] == EL_AMOEBA_DROPPING)) - next_element = get_next_element(Feld[oldx][oldy]); + (Tile[oldx][oldy] == EL_QUICKSAND_EMPTYING || + Tile[oldx][oldy] == EL_QUICKSAND_FAST_EMPTYING || + Tile[oldx][oldy] == EL_MAGIC_WALL_EMPTYING || + Tile[oldx][oldy] == EL_BD_MAGIC_WALL_EMPTYING || + Tile[oldx][oldy] == EL_DC_MAGIC_WALL_EMPTYING || + Tile[oldx][oldy] == EL_AMOEBA_DROPPING)) + next_element = get_next_element(Tile[oldx][oldy]); RemoveField(oldx, oldy); RemoveField(newx, newy); @@ -5250,7 +5648,7 @@ static void RemoveMovingField(int x, int y) Store[oldx][oldy] = Store2[oldx][oldy] = 0; if (next_element != EL_UNDEFINED) - Feld[oldx][oldy] = next_element; + Tile[oldx][oldy] = next_element; TEST_DrawLevelField(oldx, oldy); TEST_DrawLevelField(newx, newy); @@ -5259,7 +5657,7 @@ static void RemoveMovingField(int x, int y) void DrawDynamite(int x, int y) { int sx = SCREENX(x), sy = SCREENY(y); - int graphic = el2img(Feld[x][y]); + int graphic = el2img(Tile[x][y]); int frame; if (!IN_SCR_FIELD(sx, sy) || IS_PLAYER(x, y)) @@ -5269,13 +5667,15 @@ void DrawDynamite(int x, int y) return; if (Back[x][y]) - DrawGraphic(sx, sy, el2img(Back[x][y]), 0); + DrawLevelElement(x, y, Back[x][y]); else if (Store[x][y]) - DrawGraphic(sx, sy, el2img(Store[x][y]), 0); + DrawLevelElement(x, y, Store[x][y]); + else if (game.use_masked_elements) + DrawLevelElement(x, y, EL_EMPTY); - frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]); + frame = getGraphicAnimationFrameXY(graphic, x, y); - if (Back[x][y] || Store[x][y]) + if (Back[x][y] || Store[x][y] || game.use_masked_elements) DrawGraphicThruMask(sx, sy, graphic, frame); else DrawGraphic(sx, sy, graphic, frame); @@ -5351,7 +5751,7 @@ static void setScreenCenteredToAllPlayers(int *sx, int *sy) *sy = (sy1 + sy2) / 2; } -static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir, +static void DrawRelocateScreen(int old_x, int old_y, int x, int y, boolean center_screen, boolean quick_relocation) { unsigned int frame_delay_value_old = GetVideoFrameDelay(); @@ -5381,14 +5781,47 @@ static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir, { // relocation _without_ centering of screen - int center_scroll_x = SCROLL_POSITION_X(old_x); - int center_scroll_y = SCROLL_POSITION_Y(old_y); - int offset_x = x + (scroll_x - center_scroll_x); - int offset_y = y + (scroll_y - center_scroll_y); + // apply distance between old and new player position to scroll position + int shifted_scroll_x = scroll_x + (x - old_x); + int shifted_scroll_y = scroll_y + (y - old_y); + + // make sure that shifted scroll position does not scroll beyond screen + new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX); + new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY); + + // special case for teleporting from one end of the playfield to the other + // (this kludge prevents the destination area to be shifted by half a tile + // against the source destination for even screen width or screen height; + // probably most useful when used with high "game.forced_scroll_delay_value" + // in combination with "game.forced_scroll_x" and "game.forced_scroll_y") + if (quick_relocation) + { + if (EVEN(SCR_FIELDX)) + { + // relocate (teleport) between left and right border (half or full) + if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1) + new_scroll_x = SBX_Right; + else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right) + new_scroll_x = SBX_Right - 1; + else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1) + new_scroll_x = SBX_Left; + else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left) + new_scroll_x = SBX_Left + 1; + } - // for new screen position, apply previous offset to center position - new_scroll_x = SCROLL_POSITION_X(offset_x); - new_scroll_y = SCROLL_POSITION_Y(offset_y); + if (EVEN(SCR_FIELDY)) + { + // relocate (teleport) between top and bottom border (half or full) + if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1) + new_scroll_y = SBY_Lower; + else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower) + new_scroll_y = SBY_Lower - 1; + else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1) + new_scroll_y = SBY_Upper; + else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper) + new_scroll_y = SBY_Upper + 1; + } + } } if (quick_relocation) @@ -5455,8 +5888,8 @@ static void RelocatePlayer(int jx, int jy, int el_player_raw) int wait_delay_value = (no_delay ? 0 : frame_delay_value); int old_jx = player->jx; int old_jy = player->jy; - int old_element = Feld[old_jx][old_jy]; - int element = Feld[jx][jy]; + int old_element = Tile[old_jx][old_jy]; + int element = Tile[jx][jy]; boolean player_relocated = (old_jx != jx || old_jy != jy); int move_dir_horiz = (jx < old_jx ? MV_LEFT : jx > old_jx ? MV_RIGHT : 0); @@ -5509,20 +5942,20 @@ static void RelocatePlayer(int jx, int jy, int el_player_raw) CE_PLAYER_LEAVES_X, player->index_bit, leave_side); - Feld[jx][jy] = el_player; + Tile[jx][jy] = el_player; InitPlayerField(jx, jy, el_player, TRUE); - /* "InitPlayerField()" above sets Feld[jx][jy] to EL_EMPTY, but it may be + /* "InitPlayerField()" above sets Tile[jx][jy] to EL_EMPTY, but it may be 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 { - Feld[jx][jy] = element; // restore previously existing element + Tile[jx][jy] = element; // restore previously existing element } // only visually relocate centered player - DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir, + DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, FALSE, level.instant_relocation); TestIfPlayerTouchesBadThing(jx, jy); @@ -5555,9 +5988,6 @@ static void Explode(int ex, int ey, int phase, int mode) int last_phase; int border_element; - // !!! eliminate this variable !!! - int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2); - if (game.explosions_delayed) { ExplodeField[ex][ey] = mode; @@ -5566,7 +5996,9 @@ static 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 center_element = Tile[ex][ey]; + int ce_value = CustomValue[ex][ey]; + int ce_score = element_info[center_element].collect_score; int artwork_element, explosion_element; // set these values later // remove things displayed in background while burning dynamite @@ -5578,7 +6010,7 @@ static void Explode(int ex, int ey, int phase, int mode) // put moving element to center field (and let it explode there) center_element = MovingOrBlocked2Element(ex, ey); RemoveMovingField(ex, ey); - Feld[ex][ey] = center_element; + Tile[ex][ey] = center_element; } // now "center_element" is finally determined -- set related values now @@ -5616,7 +6048,7 @@ static void Explode(int ex, int ey, int phase, int mode) (mode == EX_TYPE_CROSS && (x != ex && y != ey))) continue; - element = Feld[x][y]; + element = Tile[x][y]; if (IS_MOVING(x, y) || IS_BLOCKED(x, y)) { @@ -5647,7 +6079,7 @@ static void Explode(int ex, int ey, int phase, int mode) if (IS_ACTIVE_BOMB(element)) { // re-activate things under the bomb like gate or penguin - Feld[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY); + Tile[x][y] = (Back[x][y] ? Back[x][y] : EL_EMPTY); Back[x][y] = 0; } @@ -5686,7 +6118,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]; @@ -5705,11 +6137,19 @@ static void Explode(int ex, int ey, int phase, int mode) else Store[x][y] = EL_EMPTY; + if (IS_CUSTOM_ELEMENT(center_element)) + Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value : + Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score : + Store[x][y] >= EL_PREV_CE_8 && + Store[x][y] <= EL_NEXT_CE_8 ? + RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) : + Store[x][y]); + if (x != ex || y != ey || mode == EX_TYPE_BORDER || center_element == EL_AMOEBA_TO_DIAMOND) Store2[x][y] = element; - Feld[x][y] = EL_EXPLOSION; + Tile[x][y] = EL_EXPLOSION; GfxElement[x][y] = artwork_element; ExplodePhase[x][y] = 1; @@ -5759,14 +6199,14 @@ static void Explode(int ex, int ey, int phase, int mode) } else if (CAN_EXPLODE_BY_EXPLOSION(border_element)) { - Feld[x][y] = Store2[x][y]; + Tile[x][y] = Store2[x][y]; Store2[x][y] = 0; Bang(x, y); border_explosion = TRUE; } else if (border_element == EL_AMOEBA_TO_DIAMOND) { - AmoebeUmwandeln(x, y); + AmoebaToDiamond(x, y); Store2[x][y] = 0; border_explosion = TRUE; } @@ -5778,11 +6218,15 @@ static void Explode(int ex, int ey, int phase, int mode) return; } + // this can happen if the player was just killed by an explosion + if (GfxElement[x][y] == EL_UNDEFINED) + GfxElement[x][y] = EL_EMPTY; + if (phase == last_phase) { int element; - element = Feld[x][y] = Store[x][y]; + element = Tile[x][y] = Store[x][y]; Store[x][y] = Store2[x][y] = 0; GfxElement[x][y] = EL_UNDEFINED; @@ -5798,13 +6242,13 @@ static void Explode(int ex, int ey, int phase, int mode) if (level.use_explosion_element[player_nr]) explosion_element = level.explosion_element[player_nr]; - Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY : + Tile[x][y] = (stored_player[player_nr].active ? EL_EMPTY : element_info[explosion_element].content.e[xx][yy]); } // restore probably existing indestructible background element if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y])) - element = Feld[x][y] = Back[x][y]; + element = Tile[x][y] = Back[x][y]; Back[x][y] = 0; MovDir[x][y] = MovPos[x][y] = MovDelay[x][y] = 0; @@ -5826,15 +6270,15 @@ static void Explode(int ex, int ey, int phase, int mode) if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present) StorePlayer[x][y] = 0; - if (ELEM_IS_PLAYER(element)) + if (IS_PLAYER_ELEMENT(element)) RelocatePlayer(x, y, element); } else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y))) { int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING); - int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]); + int frame = getGraphicAnimationFrameXY(graphic, x, y); - if (phase == delay) + if (phase == 1) TEST_DrawLevelFieldCrumbled(x, y); if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY) @@ -5844,28 +6288,22 @@ static void Explode(int ex, int ey, int phase, int mode) } else if (IS_WALKABLE_UNDER(Back[x][y])) { - DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelGraphic(x, y, graphic, frame); DrawLevelElementThruMask(x, y, Back[x][y]); } else if (!IS_WALKABLE_INSIDE(Back[x][y])) - DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelGraphic(x, y, graphic, frame); } } static void DynaExplode(int ex, int ey) { int i, j; - int dynabomb_element = Feld[ex][ey]; + int dynabomb_element = Tile[ex][ey]; int dynabomb_size = 1; boolean dynabomb_xl = FALSE; struct PlayerInfo *player; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (IS_ACTIVE_BOMB(dynabomb_element)) { @@ -5881,14 +6319,14 @@ static void DynaExplode(int ex, int ey) { for (j = 1; j <= dynabomb_size; j++) { - int x = ex + j * xy[i][0]; - int y = ey + j * xy[i][1]; + int x = ex + j * xy[i].x; + int y = ey + j * xy[i].y; int element; - if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Feld[x][y])) + if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y])) break; - element = Feld[x][y]; + element = Tile[x][y]; // do not restart explosions of fields with active bombs if (element == EL_EXPLOSION && IS_ACTIVE_BOMB(Store2[x][y])) @@ -5912,7 +6350,7 @@ void Bang(int x, int y) { struct PlayerInfo *player = PLAYERINFO(x, y); - element = Feld[x][y] = player->initial_element; + element = Tile[x][y] = player->initial_element; if (level.use_explosion_element[player->index_nr]) { @@ -5982,12 +6420,12 @@ static void SplashAcid(int x, int y) if (IN_LEV_FIELD(x - 1, y - 1) && IS_FREE(x - 1, y - 1) && (!IN_LEV_FIELD(x - 1, y - 2) || !CAN_FALL(MovingOrBlocked2Element(x - 1, y - 2)))) - Feld[x - 1][y - 1] = EL_ACID_SPLASH_LEFT; + Tile[x - 1][y - 1] = EL_ACID_SPLASH_LEFT; if (IN_LEV_FIELD(x + 1, y - 1) && IS_FREE(x + 1, y - 1) && (!IN_LEV_FIELD(x + 1, y - 2) || !CAN_FALL(MovingOrBlocked2Element(x + 1, y - 2)))) - Feld[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT; + Tile[x + 1][y - 1] = EL_ACID_SPLASH_RIGHT; PlayLevelSound(x, y, SND_ACID_SPLASHING); } @@ -6037,7 +6475,7 @@ static void InitBeltMovement(void) SCAN_PLAYFIELD(x, y) { - int element = Feld[x][y]; + int element = Tile[x][y]; for (i = 0; i < NUM_BELTS; i++) { @@ -6048,9 +6486,9 @@ static void InitBeltMovement(void) if (e_belt_nr == belt_nr) { - int belt_part = Feld[x][y] - belt_base_element[belt_nr]; + int belt_part = Tile[x][y] - belt_base_element[belt_nr]; - Feld[x][y] = belt_base_active_element[belt_nr] + belt_part; + Tile[x][y] = belt_base_active_element[belt_nr] + belt_part; } } } @@ -6088,7 +6526,7 @@ static void ToggleBeltSwitch(int x, int y) MV_NONE, }; - int element = Feld[x][y]; + int element = Tile[x][y]; int belt_nr = getBeltNrFromBeltSwitchElement(element); int belt_dir_nr = (game.belt_dir_nr[belt_nr] + 1) % 4; int belt_dir = belt_move_dir[belt_dir_nr]; @@ -6124,7 +6562,7 @@ static void ToggleBeltSwitch(int x, int y) SCAN_PLAYFIELD(xx, yy) { - int element = Feld[xx][yy]; + int element = Tile[xx][yy]; if (IS_BELT_SWITCH(element)) { @@ -6132,7 +6570,7 @@ static void ToggleBeltSwitch(int x, int y) if (e_belt_nr == belt_nr) { - Feld[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr; + Tile[xx][yy] = belt_base_switch_element[belt_nr] + belt_dir_nr; TEST_DrawLevelField(xx, yy); } } @@ -6142,9 +6580,9 @@ static void ToggleBeltSwitch(int x, int y) if (e_belt_nr == belt_nr) { - int belt_part = Feld[xx][yy] - belt_base_element[belt_nr]; + int belt_part = Tile[xx][yy] - belt_base_element[belt_nr]; - Feld[xx][yy] = belt_base_active_element[belt_nr] + belt_part; + Tile[xx][yy] = belt_base_active_element[belt_nr] + belt_part; TEST_DrawLevelField(xx, yy); } } @@ -6154,16 +6592,16 @@ static void ToggleBeltSwitch(int x, int y) if (e_belt_nr == belt_nr) { - int belt_part = Feld[xx][yy] - belt_base_active_element[belt_nr]; + int belt_part = Tile[xx][yy] - belt_base_active_element[belt_nr]; - Feld[xx][yy] = belt_base_element[belt_nr] + belt_part; + Tile[xx][yy] = belt_base_element[belt_nr] + belt_part; TEST_DrawLevelField(xx, yy); } } } } -static void ToggleSwitchgateSwitch(int x, int y) +static void ToggleSwitchgateSwitch(void) { int xx, yy; @@ -6171,39 +6609,39 @@ static void ToggleSwitchgateSwitch(int x, int y) SCAN_PLAYFIELD(xx, yy) { - int element = Feld[xx][yy]; + int element = Tile[xx][yy]; if (element == EL_SWITCHGATE_SWITCH_UP) { - Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN; + Tile[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN; TEST_DrawLevelField(xx, yy); } else if (element == EL_SWITCHGATE_SWITCH_DOWN) { - Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP; + Tile[xx][yy] = EL_SWITCHGATE_SWITCH_UP; TEST_DrawLevelField(xx, yy); } else if (element == EL_DC_SWITCHGATE_SWITCH_UP) { - Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN; + Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN; TEST_DrawLevelField(xx, yy); } else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN) { - Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP; + Tile[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP; TEST_DrawLevelField(xx, yy); } else if (element == EL_SWITCHGATE_OPEN || element == EL_SWITCHGATE_OPENING) { - Feld[xx][yy] = EL_SWITCHGATE_CLOSING; + Tile[xx][yy] = EL_SWITCHGATE_CLOSING; PlayLevelSoundAction(xx, yy, ACTION_CLOSING); } else if (element == EL_SWITCHGATE_CLOSED || element == EL_SWITCHGATE_CLOSING) { - Feld[xx][yy] = EL_SWITCHGATE_OPENING; + Tile[xx][yy] = EL_SWITCHGATE_OPENING; PlayLevelSoundAction(xx, yy, ACTION_OPENING); } @@ -6232,30 +6670,30 @@ static void RedrawAllLightSwitchesAndInvisibleElements(void) SCAN_PLAYFIELD(x, y) { - int element = Feld[x][y]; + int element = Tile[x][y]; if (element == EL_LIGHT_SWITCH && game.light_time_left > 0) { - Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE; + Tile[x][y] = EL_LIGHT_SWITCH_ACTIVE; TEST_DrawLevelField(x, y); } else if (element == EL_LIGHT_SWITCH_ACTIVE && game.light_time_left == 0) { - Feld[x][y] = EL_LIGHT_SWITCH; + Tile[x][y] = EL_LIGHT_SWITCH; TEST_DrawLevelField(x, y); } else if (element == EL_EMC_DRIPPER && game.light_time_left > 0) { - Feld[x][y] = EL_EMC_DRIPPER_ACTIVE; + Tile[x][y] = EL_EMC_DRIPPER_ACTIVE; TEST_DrawLevelField(x, y); } else if (element == EL_EMC_DRIPPER_ACTIVE && game.light_time_left == 0) { - Feld[x][y] = EL_EMC_DRIPPER; + Tile[x][y] = EL_EMC_DRIPPER; TEST_DrawLevelField(x, y); } else if (element == EL_INVISIBLE_STEELWALL || @@ -6263,7 +6701,7 @@ static void RedrawAllLightSwitchesAndInvisibleElements(void) element == EL_INVISIBLE_SAND) { if (game.light_time_left > 0) - Feld[x][y] = getInvisibleActiveFromInvisibleElement(element); + Tile[x][y] = getInvisibleActiveFromInvisibleElement(element); TEST_DrawLevelField(x, y); @@ -6276,7 +6714,7 @@ static void RedrawAllLightSwitchesAndInvisibleElements(void) element == EL_INVISIBLE_SAND_ACTIVE) { if (game.light_time_left == 0) - Feld[x][y] = getInvisibleFromInvisibleActiveElement(element); + Tile[x][y] = getInvisibleFromInvisibleActiveElement(element); TEST_DrawLevelField(x, y); @@ -6293,18 +6731,18 @@ static void RedrawAllInvisibleElementsForLenses(void) SCAN_PLAYFIELD(x, y) { - int element = Feld[x][y]; + int element = Tile[x][y]; if (element == EL_EMC_DRIPPER && game.lenses_time_left > 0) { - Feld[x][y] = EL_EMC_DRIPPER_ACTIVE; + Tile[x][y] = EL_EMC_DRIPPER_ACTIVE; TEST_DrawLevelField(x, y); } else if (element == EL_EMC_DRIPPER_ACTIVE && game.lenses_time_left == 0) { - Feld[x][y] = EL_EMC_DRIPPER; + Tile[x][y] = EL_EMC_DRIPPER; TEST_DrawLevelField(x, y); } else if (element == EL_INVISIBLE_STEELWALL || @@ -6312,7 +6750,7 @@ static void RedrawAllInvisibleElementsForLenses(void) element == EL_INVISIBLE_SAND) { if (game.lenses_time_left > 0) - Feld[x][y] = getInvisibleActiveFromInvisibleElement(element); + Tile[x][y] = getInvisibleActiveFromInvisibleElement(element); TEST_DrawLevelField(x, y); @@ -6325,7 +6763,7 @@ static void RedrawAllInvisibleElementsForLenses(void) element == EL_INVISIBLE_SAND_ACTIVE) { if (game.lenses_time_left == 0) - Feld[x][y] = getInvisibleFromInvisibleActiveElement(element); + Tile[x][y] = getInvisibleFromInvisibleActiveElement(element); TEST_DrawLevelField(x, y); @@ -6342,24 +6780,24 @@ static void RedrawAllInvisibleElementsForMagnifier(void) SCAN_PLAYFIELD(x, y) { - int element = Feld[x][y]; + int element = Tile[x][y]; if (element == EL_EMC_FAKE_GRASS && game.magnify_time_left > 0) { - Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE; + Tile[x][y] = EL_EMC_FAKE_GRASS_ACTIVE; TEST_DrawLevelField(x, y); } else if (element == EL_EMC_FAKE_GRASS_ACTIVE && game.magnify_time_left == 0) { - Feld[x][y] = EL_EMC_FAKE_GRASS; + Tile[x][y] = EL_EMC_FAKE_GRASS; TEST_DrawLevelField(x, y); } else if (IS_GATE_GRAY(element) && game.magnify_time_left > 0) { - Feld[x][y] = (IS_RND_GATE_GRAY(element) ? + Tile[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 : @@ -6373,7 +6811,7 @@ static void RedrawAllInvisibleElementsForMagnifier(void) else if (IS_GATE_GRAY_ACTIVE(element) && game.magnify_time_left == 0) { - Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ? + Tile[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 : @@ -6389,7 +6827,7 @@ static void RedrawAllInvisibleElementsForMagnifier(void) static void ToggleLightSwitch(int x, int y) { - int element = Feld[x][y]; + int element = Tile[x][y]; game.light_time_left = (element == EL_LIGHT_SWITCH ? @@ -6406,26 +6844,26 @@ static void ActivateTimegateSwitch(int x, int y) SCAN_PLAYFIELD(xx, yy) { - int element = Feld[xx][yy]; + int element = Tile[xx][yy]; if (element == EL_TIMEGATE_CLOSED || element == EL_TIMEGATE_CLOSING) { - Feld[xx][yy] = EL_TIMEGATE_OPENING; + Tile[xx][yy] = EL_TIMEGATE_OPENING; PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING); } /* else if (element == EL_TIMEGATE_SWITCH_ACTIVE) { - Feld[xx][yy] = EL_TIMEGATE_SWITCH; + Tile[xx][yy] = EL_TIMEGATE_SWITCH; TEST_DrawLevelField(xx, yy); } */ } - Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE : + Tile[x][y] = (Tile[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE : EL_DC_TIMEGATE_SWITCH_ACTIVE); } @@ -6434,12 +6872,12 @@ static void Impact(int x, int y) boolean last_line = (y == lev_fieldy - 1); boolean object_hit = FALSE; boolean impact = (last_line || object_hit); - int element = Feld[x][y]; + int element = Tile[x][y]; int smashed = EL_STEELWALL; if (!last_line) // check if element below was hit { - if (Feld[x][y + 1] == EL_PLAYER_IS_LEAVING) + if (Tile[x][y + 1] == EL_PLAYER_IS_LEAVING) return; object_hit = (!IS_FREE(x, y + 1) && (!IS_MOVING(x, y + 1) || @@ -6452,21 +6890,21 @@ static void Impact(int x, int y) object_hit = FALSE; #if USE_QUICKSAND_IMPACT_BUGFIX - if (Feld[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE) + if (Tile[x][y + 1] == EL_QUICKSAND_EMPTYING && object_hit == FALSE) { RemoveMovingField(x, y + 1); - Feld[x][y + 1] = EL_QUICKSAND_EMPTY; - Feld[x][y + 2] = EL_ROCK; + Tile[x][y + 1] = EL_QUICKSAND_EMPTY; + Tile[x][y + 2] = EL_ROCK; TEST_DrawLevelField(x, y + 2); object_hit = TRUE; } - if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE) + if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTYING && object_hit == FALSE) { RemoveMovingField(x, y + 1); - Feld[x][y + 1] = EL_QUICKSAND_FAST_EMPTY; - Feld[x][y + 2] = EL_ROCK; + Tile[x][y + 1] = EL_QUICKSAND_FAST_EMPTY; + Tile[x][y + 2] = EL_ROCK; TEST_DrawLevelField(x, y + 2); object_hit = TRUE; @@ -6504,7 +6942,7 @@ static void Impact(int x, int y) { ResetGfxAnimation(x, y); - Feld[x][y] = EL_PEARL_BREAKING; + Tile[x][y] = EL_PEARL_BREAKING; PlayLevelSound(x, y, SND_PEARL_BREAKING); return; } @@ -6523,7 +6961,7 @@ static void Impact(int x, int y) Bang(x, y + 1); else { - Feld[x][y] = EL_AMOEBA_GROWING; + Tile[x][y] = EL_AMOEBA_GROWING; Store[x][y] = EL_AMOEBA_WET; ResetRandomAnimationValue(x, y); @@ -6548,8 +6986,8 @@ static void Impact(int x, int y) // activate magic wall / mill SCAN_PLAYFIELD(xx, yy) { - if (Feld[xx][yy] == smashed) - Feld[xx][yy] = activated_magic_wall; + if (Tile[xx][yy] == smashed) + Tile[xx][yy] = activated_magic_wall; } game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND; @@ -6615,7 +7053,7 @@ static void Impact(int x, int y) } else if (smashed == EL_NUT) { - Feld[x][y + 1] = EL_NUT_BREAKING; + Tile[x][y + 1] = EL_NUT_BREAKING; PlayLevelSound(x, y, SND_NUT_BREAKING); RaiseScoreElement(EL_NUT); return; @@ -6624,13 +7062,13 @@ static void Impact(int x, int y) { ResetGfxAnimation(x, y); - Feld[x][y + 1] = EL_PEARL_BREAKING; + Tile[x][y + 1] = EL_PEARL_BREAKING; PlayLevelSound(x, y, SND_PEARL_BREAKING); return; } else if (smashed == EL_DIAMOND) { - Feld[x][y + 1] = EL_DIAMOND_BREAKING; + Tile[x][y + 1] = EL_DIAMOND_BREAKING; PlayLevelSound(x, y, SND_DIAMOND_BREAKING); return; } @@ -6643,7 +7081,7 @@ static void Impact(int x, int y) smashed == EL_DC_SWITCHGATE_SWITCH_UP || smashed == EL_DC_SWITCHGATE_SWITCH_DOWN) { - ToggleSwitchgateSwitch(x, y + 1); + ToggleSwitchgateSwitch(); } else if (smashed == EL_LIGHT_SWITCH || smashed == EL_LIGHT_SWITCH_ACTIVE) @@ -6669,15 +7107,15 @@ static void Impact(int x, int y) // play sound of magic wall / mill if (!last_line && - (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE || - Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE || - Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)) + (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE || + Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE || + Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)) { - if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE) + if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE) PlayLevelSound(x, y, SND_MAGIC_WALL_FILLING); - else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE) + else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE) PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING); - else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE) + else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE) PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING); return; @@ -6719,7 +7157,7 @@ static void TurnRoundExt(int x, int y) { MV_RIGHT, MV_LEFT, MV_UP } }; - int element = Feld[x][y]; + int element = Tile[x][y]; int move_pattern = element_info[element].move_pattern; int old_move_dir = MovDir[x][y]; @@ -6887,7 +7325,7 @@ static void TurnRoundExt(int x, int y) yy = y + move_xy[MovDir[x][y]].dy; if (!IN_LEV_FIELD(xx, yy) || - (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy]))) + (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Tile[xx][yy]))) MovDir[x][y] = old_move_dir; MovDelay[x][y] = 0; @@ -6923,17 +7361,17 @@ static void TurnRoundExt(int x, int y) { boolean can_move_on = (MOLE_CAN_ENTER_FIELD(element, move_x, move_y, - IS_AMOEBOID(Feld[move_x][move_y]) || - Feld[move_x][move_y] == EL_AMOEBA_SHRINKING)); + IS_AMOEBOID(Tile[move_x][move_y]) || + Tile[move_x][move_y] == EL_AMOEBA_SHRINKING)); if (!can_move_on) { boolean can_turn_left = (MOLE_CAN_ENTER_FIELD(element, left_x, left_y, - IS_AMOEBOID(Feld[left_x][left_y]))); + IS_AMOEBOID(Tile[left_x][left_y]))); boolean can_turn_right = (MOLE_CAN_ENTER_FIELD(element, right_x, right_y, - IS_AMOEBOID(Feld[right_x][right_y]))); + IS_AMOEBOID(Tile[right_x][right_y]))); if (can_turn_left && can_turn_right) MovDir[x][y] = (RND(2) ? left_dir : right_dir); @@ -6958,7 +7396,7 @@ static void TurnRoundExt(int x, int y) if (SPRING_CAN_BUMP_FROM_FIELD(move_x, move_y) && !SPRING_CAN_ENTER_FIELD(element, x, y + 1)) { - Feld[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE; + Tile[move_x][move_y] = EL_EMC_SPRING_BUMPER_ACTIVE; ResetGfxAnimation(move_x, move_y); TEST_DrawLevelField(move_x, move_y); @@ -7007,7 +7445,7 @@ static void TurnRoundExt(int x, int y) if (element == EL_ROBOT && game.robot_wheel_x >= 0 && game.robot_wheel_y >= 0 && - (Feld[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE || + (Tile[game.robot_wheel_x][game.robot_wheel_y] == EL_ROBOT_WHEEL_ACTIVE || game.engine_version < VERSION_IDENT(3,1,0,0))) { attr_x = game.robot_wheel_x; @@ -7017,23 +7455,17 @@ static void TurnRoundExt(int x, int y) if (element == EL_PENGUIN) { int i; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - int ex = x + xy[i][0]; - int ey = y + xy[i][1]; + int ex = x + xy[i].x; + int ey = y + xy[i].y; - if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN || - Feld[ex][ey] == EL_EM_EXIT_OPEN || - Feld[ex][ey] == EL_STEEL_EXIT_OPEN || - Feld[ex][ey] == EL_EM_STEEL_EXIT_OPEN)) + if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN || + Tile[ex][ey] == EL_EM_EXIT_OPEN || + Tile[ex][ey] == EL_STEEL_EXIT_OPEN || + Tile[ex][ey] == EL_EM_STEEL_EXIT_OPEN)) { attr_x = ex; attr_y = ey; @@ -7196,7 +7628,7 @@ static void TurnRoundExt(int x, int y) element_info[element].move_leave_type = LEAVE_TYPE_LIMITED; element_info[element].move_leave_element = EL_TRIGGER_ELEMENT; - Store[x][y] = Feld[newx][newy]; + Store[x][y] = Tile[newx][newy]; can_clone = TRUE; @@ -7272,7 +7704,7 @@ static void TurnRoundExt(int x, int y) boolean can_turn_left = CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y); boolean can_turn_right = - CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y); + CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y); if (element_info[element].move_stepsize == 0) // "not moving" return; @@ -7427,25 +7859,13 @@ static void TurnRoundExt(int x, int y) } else if (move_pattern & MV_MAZE_RUNNER_STYLE) { - static int test_xy[7][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 }, - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - }; - static int test_dir[7] = + struct XY *test_xy = xy_topdown; + static int test_dir[4] = { MV_UP, MV_LEFT, MV_RIGHT, - MV_DOWN, - MV_UP, - MV_LEFT, - MV_RIGHT, + MV_DOWN }; boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER); int move_preference = -1000000; // start with very low preference @@ -7455,14 +7875,15 @@ static void TurnRoundExt(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int move_dir = test_dir[start_test + i]; + int j = (start_test + i) % 4; + int move_dir = test_dir[j]; int move_dir_preference; - xx = x + test_xy[start_test + i][0]; - yy = y + test_xy[start_test + i][1]; + xx = x + test_xy[j].x; + yy = y + test_xy[j].y; if (hunter_mode && IN_LEV_FIELD(xx, yy) && - (IS_PLAYER(xx, yy) || Feld[xx][yy] == EL_PLAYER_IS_LEAVING)) + (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING)) { new_move_dir = move_dir; @@ -7538,7 +7959,7 @@ static boolean JustBeingPushed(int x, int y) static void StartMoving(int x, int y) { boolean started_moving = FALSE; // some elements can fall _and_ move - int element = Feld[x][y]; + int element = Tile[x][y]; if (Stop[x][y]) return; @@ -7560,7 +7981,7 @@ static void StartMoving(int x, int y) InitMovingField(x, y, MV_DOWN); started_moving = TRUE; - Feld[x][y] = EL_QUICKSAND_EMPTYING; + Tile[x][y] = EL_QUICKSAND_EMPTYING; #if USE_QUICKSAND_BD_ROCK_BUGFIX if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK) Store[x][y] = EL_ROCK; @@ -7570,7 +7991,7 @@ static void StartMoving(int x, int y) PlayLevelSoundAction(x, y, ACTION_EMPTYING); } - else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY) + else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY) { if (!MovDelay[x][y]) { @@ -7590,14 +8011,14 @@ static void StartMoving(int x, int y) return; } - Feld[x][y] = EL_QUICKSAND_EMPTY; - Feld[x][y + 1] = EL_QUICKSAND_FULL; + Tile[x][y] = EL_QUICKSAND_EMPTY; + Tile[x][y + 1] = EL_QUICKSAND_FULL; Store[x][y + 1] = Store[x][y]; Store[x][y] = 0; PlayLevelSoundAction(x, y, ACTION_FILLING); } - else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY) + else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY) { if (!MovDelay[x][y]) { @@ -7617,8 +8038,8 @@ static void StartMoving(int x, int y) return; } - Feld[x][y] = EL_QUICKSAND_EMPTY; - Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL; + Tile[x][y] = EL_QUICKSAND_EMPTY; + Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL; Store[x][y + 1] = Store[x][y]; Store[x][y] = 0; @@ -7632,7 +8053,7 @@ static void StartMoving(int x, int y) InitMovingField(x, y, MV_DOWN); started_moving = TRUE; - Feld[x][y] = EL_QUICKSAND_FAST_EMPTYING; + Tile[x][y] = EL_QUICKSAND_FAST_EMPTYING; #if USE_QUICKSAND_BD_ROCK_BUGFIX if (Store[x][y] != EL_ROCK && Store[x][y] != EL_BD_ROCK) Store[x][y] = EL_ROCK; @@ -7642,7 +8063,7 @@ static void StartMoving(int x, int y) PlayLevelSoundAction(x, y, ACTION_EMPTYING); } - else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY) + else if (Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY) { if (!MovDelay[x][y]) { @@ -7662,14 +8083,14 @@ static void StartMoving(int x, int y) return; } - Feld[x][y] = EL_QUICKSAND_FAST_EMPTY; - Feld[x][y + 1] = EL_QUICKSAND_FAST_FULL; + Tile[x][y] = EL_QUICKSAND_FAST_EMPTY; + Tile[x][y + 1] = EL_QUICKSAND_FAST_FULL; Store[x][y + 1] = Store[x][y]; Store[x][y] = 0; PlayLevelSoundAction(x, y, ACTION_FILLING); } - else if (Feld[x][y + 1] == EL_QUICKSAND_EMPTY) + else if (Tile[x][y + 1] == EL_QUICKSAND_EMPTY) { if (!MovDelay[x][y]) { @@ -7689,8 +8110,8 @@ static void StartMoving(int x, int y) return; } - Feld[x][y] = EL_QUICKSAND_FAST_EMPTY; - Feld[x][y + 1] = EL_QUICKSAND_FULL; + Tile[x][y] = EL_QUICKSAND_FAST_EMPTY; + Tile[x][y + 1] = EL_QUICKSAND_FULL; Store[x][y + 1] = Store[x][y]; Store[x][y] = 0; @@ -7698,23 +8119,23 @@ static void StartMoving(int x, int y) } } else if ((element == EL_ROCK || element == EL_BD_ROCK) && - Feld[x][y + 1] == EL_QUICKSAND_EMPTY) + Tile[x][y + 1] == EL_QUICKSAND_EMPTY) { InitMovingField(x, y, MV_DOWN); started_moving = TRUE; - Feld[x][y] = EL_QUICKSAND_FILLING; + Tile[x][y] = EL_QUICKSAND_FILLING; Store[x][y] = element; PlayLevelSoundAction(x, y, ACTION_FILLING); } else if ((element == EL_ROCK || element == EL_BD_ROCK) && - Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY) + Tile[x][y + 1] == EL_QUICKSAND_FAST_EMPTY) { InitMovingField(x, y, MV_DOWN); started_moving = TRUE; - Feld[x][y] = EL_QUICKSAND_FAST_FILLING; + Tile[x][y] = EL_QUICKSAND_FAST_FILLING; Store[x][y] = element; PlayLevelSoundAction(x, y, ACTION_FILLING); @@ -7726,10 +8147,10 @@ static void StartMoving(int x, int y) InitMovingField(x, y, MV_DOWN); started_moving = TRUE; - Feld[x][y] = EL_MAGIC_WALL_EMPTYING; + Tile[x][y] = EL_MAGIC_WALL_EMPTYING; Store[x][y] = EL_CHANGED(Store[x][y]); } - else if (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE) + else if (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE) { if (!MovDelay[x][y]) MovDelay[x][y] = TILEY / 4 + 1; @@ -7741,8 +8162,8 @@ static void StartMoving(int x, int y) return; } - Feld[x][y] = EL_MAGIC_WALL_ACTIVE; - Feld[x][y + 1] = EL_MAGIC_WALL_FULL; + Tile[x][y] = EL_MAGIC_WALL_ACTIVE; + Tile[x][y + 1] = EL_MAGIC_WALL_FULL; Store[x][y + 1] = EL_CHANGED(Store[x][y]); Store[x][y] = 0; } @@ -7754,10 +8175,10 @@ static void StartMoving(int x, int y) InitMovingField(x, y, MV_DOWN); started_moving = TRUE; - Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING; + Tile[x][y] = EL_BD_MAGIC_WALL_EMPTYING; Store[x][y] = EL_CHANGED_BD(Store[x][y]); } - else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE) + else if (Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE) { if (!MovDelay[x][y]) MovDelay[x][y] = TILEY / 4 + 1; @@ -7769,8 +8190,8 @@ static void StartMoving(int x, int y) return; } - Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE; - Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL; + Tile[x][y] = EL_BD_MAGIC_WALL_ACTIVE; + Tile[x][y + 1] = EL_BD_MAGIC_WALL_FULL; Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]); Store[x][y] = 0; } @@ -7782,10 +8203,10 @@ static void StartMoving(int x, int y) InitMovingField(x, y, MV_DOWN); started_moving = TRUE; - Feld[x][y] = EL_DC_MAGIC_WALL_EMPTYING; + Tile[x][y] = EL_DC_MAGIC_WALL_EMPTYING; Store[x][y] = EL_CHANGED_DC(Store[x][y]); } - else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE) + else if (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE) { if (!MovDelay[x][y]) MovDelay[x][y] = TILEY / 4 + 1; @@ -7797,29 +8218,29 @@ static void StartMoving(int x, int y) return; } - Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE; - Feld[x][y + 1] = EL_DC_MAGIC_WALL_FULL; + Tile[x][y] = EL_DC_MAGIC_WALL_ACTIVE; + Tile[x][y + 1] = EL_DC_MAGIC_WALL_FULL; Store[x][y + 1] = EL_CHANGED_DC(Store[x][y]); Store[x][y] = 0; } } else if ((CAN_PASS_MAGIC_WALL(element) && - (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE || - Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) || + (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE || + Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) || (CAN_PASS_DC_MAGIC_WALL(element) && - (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))) + (Tile[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE))) { InitMovingField(x, y, MV_DOWN); started_moving = TRUE; - Feld[x][y] = - (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING : - Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING : + Tile[x][y] = + (Tile[x][y + 1] == EL_MAGIC_WALL_ACTIVE ? EL_MAGIC_WALL_FILLING : + Tile[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE ? EL_BD_MAGIC_WALL_FILLING : EL_DC_MAGIC_WALL_FILLING); Store[x][y] = element; } - else if (CAN_FALL(element) && Feld[x][y + 1] == EL_ACID) + else if (CAN_FALL(element) && Tile[x][y + 1] == EL_ACID) { SplashAcid(x, y + 1); @@ -7833,11 +8254,11 @@ static void StartMoving(int x, int y) CheckImpact[x][y] && !IS_FREE(x, y + 1)) || (game.engine_version >= VERSION_IDENT(3,0,7,0) && CAN_FALL(element) && WasJustFalling[x][y] && - (Feld[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) || + (Tile[x][y + 1] == EL_BLOCKED || IS_PLAYER(x, y + 1))) || (game.engine_version < VERSION_IDENT(2,2,0,7) && CAN_FALL(element) && WasJustMoving[x][y] && !Pushed[x][y + 1] && - (Feld[x][y + 1] == EL_BLOCKED))) + (Tile[x][y + 1] == EL_BLOCKED))) { /* this is needed for a special case not covered by calling "Impact()" from "ContinueMoving()": if an element moves to a tile directly below @@ -7862,7 +8283,7 @@ static void StartMoving(int x, int y) started_moving = TRUE; } } - else if (IS_FREE(x, y + 1) || Feld[x][y + 1] == EL_DIAMOND_BREAKING) + else if (IS_FREE(x, y + 1) || Tile[x][y + 1] == EL_DIAMOND_BREAKING) { if (WasJustFalling[x][y]) // prevent animation from being restarted MovDir[x][y] = MV_DOWN; @@ -7872,23 +8293,23 @@ static void StartMoving(int x, int y) } else if (element == EL_AMOEBA_DROP) { - Feld[x][y] = EL_AMOEBA_GROWING; + Tile[x][y] = EL_AMOEBA_GROWING; Store[x][y] = EL_AMOEBA_WET; } - else if (((IS_SLIPPERY(Feld[x][y + 1]) && !IS_PLAYER(x, y + 1)) || - (IS_EM_SLIPPERY_WALL(Feld[x][y + 1]) && IS_GEM(element))) && + else if (((IS_SLIPPERY(Tile[x][y + 1]) && !IS_PLAYER(x, y + 1)) || + (IS_EM_SLIPPERY_WALL(Tile[x][y + 1]) && IS_GEM(element))) && !IS_FALLING(x, y + 1) && !WasJustMoving[x][y + 1] && element != EL_DX_SUPABOMB && element != EL_SP_DISK_ORANGE) { boolean can_fall_left = (x > 0 && IS_FREE(x - 1, y) && (IS_FREE(x - 1, y + 1) || - Feld[x - 1][y + 1] == EL_ACID)); + Tile[x - 1][y + 1] == EL_ACID)); boolean can_fall_right = (x < lev_fieldx - 1 && IS_FREE(x + 1, y) && (IS_FREE(x + 1, y + 1) || - Feld[x + 1][y + 1] == EL_ACID)); + Tile[x + 1][y + 1] == EL_ACID)); boolean can_fall_any = (can_fall_left || can_fall_right); boolean can_fall_both = (can_fall_left && can_fall_right); - int slippery_type = element_info[Feld[x][y + 1]].slippery_type; + int slippery_type = element_info[Tile[x][y + 1]].slippery_type; if (can_fall_any && slippery_type != SLIPPERY_ANY_RANDOM) { @@ -7922,11 +8343,11 @@ static void StartMoving(int x, int y) started_moving = TRUE; } } - else if (IS_BELT_ACTIVE(Feld[x][y + 1])) + else if (IS_BELT_ACTIVE(Tile[x][y + 1])) { boolean left_is_free = (x > 0 && IS_FREE(x - 1, y)); boolean right_is_free = (x < lev_fieldx - 1 && IS_FREE(x + 1, y)); - int belt_nr = getBeltNrFromBeltActiveElement(Feld[x][y + 1]); + int belt_nr = getBeltNrFromBeltActiveElement(Tile[x][y + 1]); int belt_dir = game.belt_dir[belt_nr]; if ((belt_dir == MV_LEFT && left_is_free) || @@ -7968,7 +8389,7 @@ static void StartMoving(int x, int y) TestIfElementHitsCustomElement(x, y, MovDir[x][y]); - if (Feld[x][y] != element) // element has changed + if (Tile[x][y] != element) // element has changed return; } @@ -8021,7 +8442,7 @@ static void StartMoving(int x, int y) dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT : dir == MV_UP ? IMG_FLAMES_1_UP : dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY); - int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]); + int frame = getGraphicAnimationFrameXY(graphic, x, y); GfxAction[x][y] = ACTION_ATTACKING; @@ -8040,7 +8461,7 @@ static void StartMoving(int x, int y) int sy = SCREENY(yy); int flame_graphic = graphic + (i - 1); - if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Feld[xx][yy])) + if (!IN_LEV_FIELD(xx, yy) || IS_DRAGONFIRE_PROOF(Tile[xx][yy])) break; if (MovDelay[x][y]) @@ -8054,18 +8475,18 @@ static void StartMoving(int x, int y) ChangeDelay[xx][yy] = 0; - Feld[xx][yy] = EL_FLAMES; + Tile[xx][yy] = EL_FLAMES; if (IN_SCR_FIELD(sx, sy)) { TEST_DrawLevelFieldCrumbled(xx, yy); - DrawGraphic(sx, sy, flame_graphic, frame); + DrawScreenGraphic(sx, sy, flame_graphic, frame); } } else { - if (Feld[xx][yy] == EL_FLAMES) - Feld[xx][yy] = EL_EMPTY; + if (Tile[xx][yy] == EL_FLAMES) + Tile[xx][yy] = EL_EMPTY; TEST_DrawLevelField(xx, yy); } } @@ -8093,7 +8514,7 @@ static void StartMoving(int x, int y) } else if (CAN_MOVE_INTO_ACID(element) && - IN_LEV_FIELD(newx, newy) && Feld[newx][newy] == EL_ACID && + IN_LEV_FIELD(newx, newy) && Tile[newx][newy] == EL_ACID && !IS_MV_DIAGONAL(MovDir[x][y]) && (MovDir[x][y] == MV_DOWN || game.engine_version >= VERSION_IDENT(3,1,0,0))) @@ -8103,17 +8524,17 @@ static void StartMoving(int x, int y) } else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy)) { - if (Feld[newx][newy] == EL_EXIT_OPEN || - Feld[newx][newy] == EL_EM_EXIT_OPEN || - Feld[newx][newy] == EL_STEEL_EXIT_OPEN || - Feld[newx][newy] == EL_EM_STEEL_EXIT_OPEN) + if (Tile[newx][newy] == EL_EXIT_OPEN || + Tile[newx][newy] == EL_EM_EXIT_OPEN || + Tile[newx][newy] == EL_STEEL_EXIT_OPEN || + Tile[newx][newy] == EL_EM_STEEL_EXIT_OPEN) { RemoveField(x, y); TEST_DrawLevelField(x, y); PlayLevelSound(newx, newy, SND_PENGUIN_PASSING); if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy))) - DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0); + DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0); game.friends_still_needed--; if (!game.friends_still_needed && @@ -8123,9 +8544,9 @@ static void StartMoving(int x, int y) return; } - else if (IS_FOOD_PENGUIN(Feld[newx][newy])) + else if (IS_FOOD_PENGUIN(Tile[newx][newy])) { - if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING) + if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING) TEST_DrawLevelField(newx, newy); else GfxDir[x][y] = MovDir[x][y] = MV_NONE; @@ -8144,13 +8565,13 @@ static void StartMoving(int x, int y) } else if (element == EL_PIG && IN_LEV_FIELD(newx, newy)) { - if (IS_FOOD_PIG(Feld[newx][newy])) + if (IS_FOOD_PIG(Tile[newx][newy])) { if (IS_MOVING(newx, newy)) RemoveMovingField(newx, newy); else { - Feld[newx][newy] = EL_EMPTY; + Tile[newx][newy] = EL_EMPTY; TEST_DrawLevelField(newx, newy); } @@ -8176,7 +8597,7 @@ static void StartMoving(int x, int y) // check if element to clone is still there for (yy = y - 1; yy <= y + 1; yy++) for (xx = x - 1; xx <= x + 1; xx++) { - if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == Store[x][y]) + if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == Store[x][y]) { can_clone = TRUE; @@ -8208,13 +8629,16 @@ static void StartMoving(int x, int y) GfxDir[x][y] = diagonal_move_dir; ChangeDelay[x][y] = change_delay; + if (Store[x][y] == EL_EMPTY) + Store[x][y] = GfxElementEmpty[x][y]; + graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]); DrawLevelGraphicAnimation(x, y, graphic); PlayLevelSoundAction(x, y, ACTION_SHRINKING); - if (Feld[newx][newy] == EL_ACID) + if (Tile[newx][newy] == EL_ACID) { SplashAcid(newx, newy); @@ -8239,7 +8663,7 @@ static void StartMoving(int x, int y) } else { - Feld[newx][newy] = EL_EMPTY; + Tile[newx][newy] = EL_EMPTY; TEST_DrawLevelField(newx, newy); PlayLevelSoundAction(x, y, ACTION_DIGGING); @@ -8302,37 +8726,37 @@ static void StartMoving(int x, int y) MovDelay[x][y] = 50; - Feld[newx][newy] = EL_FLAMES; - if (IN_LEV_FIELD(newx1, newy1) && Feld[newx1][newy1] == EL_EMPTY) - Feld[newx1][newy1] = EL_FLAMES; - if (IN_LEV_FIELD(newx2, newy2) && Feld[newx2][newy2] == EL_EMPTY) - Feld[newx2][newy2] = EL_FLAMES; + Tile[newx][newy] = EL_FLAMES; + if (IN_LEV_FIELD(newx1, newy1) && Tile[newx1][newy1] == EL_EMPTY) + Tile[newx1][newy1] = EL_FLAMES; + if (IN_LEV_FIELD(newx2, newy2) && Tile[newx2][newy2] == EL_EMPTY) + Tile[newx2][newy2] = EL_FLAMES; return; } } } else if (element == EL_YAMYAM && IN_LEV_FIELD(newx, newy) && - Feld[newx][newy] == EL_DIAMOND) + Tile[newx][newy] == EL_DIAMOND) { if (IS_MOVING(newx, newy)) RemoveMovingField(newx, newy); else { - Feld[newx][newy] = EL_EMPTY; + Tile[newx][newy] = EL_EMPTY; TEST_DrawLevelField(newx, newy); } PlayLevelSound(x, y, SND_YAMYAM_DIGGING); } else if (element == EL_DARK_YAMYAM && IN_LEV_FIELD(newx, newy) && - IS_FOOD_DARK_YAMYAM(Feld[newx][newy])) + IS_FOOD_DARK_YAMYAM(Tile[newx][newy])) { if (AmoebaNr[newx][newy]) { AmoebaCnt2[AmoebaNr[newx][newy]]--; - if (Feld[newx][newy] == EL_AMOEBA_FULL || - Feld[newx][newy] == EL_BD_AMOEBA) + if (Tile[newx][newy] == EL_AMOEBA_FULL || + Tile[newx][newy] == EL_BD_AMOEBA) AmoebaCnt[AmoebaNr[newx][newy]]--; } @@ -8342,26 +8766,26 @@ static void StartMoving(int x, int y) } else { - Feld[newx][newy] = EL_EMPTY; + Tile[newx][newy] = EL_EMPTY; TEST_DrawLevelField(newx, newy); } PlayLevelSound(x, y, SND_DARK_YAMYAM_DIGGING); } else if ((element == EL_PACMAN || element == EL_MOLE) - && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Feld[newx][newy])) + && IN_LEV_FIELD(newx, newy) && IS_AMOEBOID(Tile[newx][newy])) { if (AmoebaNr[newx][newy]) { AmoebaCnt2[AmoebaNr[newx][newy]]--; - if (Feld[newx][newy] == EL_AMOEBA_FULL || - Feld[newx][newy] == EL_BD_AMOEBA) + if (Tile[newx][newy] == EL_AMOEBA_FULL || + Tile[newx][newy] == EL_BD_AMOEBA) AmoebaCnt[AmoebaNr[newx][newy]]--; } if (element == EL_MOLE) { - Feld[newx][newy] = EL_AMOEBA_SHRINKING; + Tile[newx][newy] = EL_AMOEBA_SHRINKING; PlayLevelSound(x, y, SND_MOLE_DIGGING); ResetGfxAnimation(x, y); @@ -8374,14 +8798,14 @@ static void StartMoving(int x, int y) } else // element == EL_PACMAN { - Feld[newx][newy] = EL_EMPTY; + Tile[newx][newy] = EL_EMPTY; TEST_DrawLevelField(newx, newy); PlayLevelSound(x, y, SND_PACMAN_DIGGING); } } else if (element == EL_MOLE && IN_LEV_FIELD(newx, newy) && - (Feld[newx][newy] == EL_AMOEBA_SHRINKING || - (Feld[newx][newy] == EL_EMPTY && Stop[newx][newy]))) + (Tile[newx][newy] == EL_AMOEBA_SHRINKING || + (Tile[newx][newy] == EL_EMPTY && Stop[newx][newy]))) { // wait for shrinking amoeba to completely disappear return; @@ -8412,7 +8836,7 @@ static void StartMoving(int x, int y) void ContinueMoving(int x, int y) { - int element = Feld[x][y]; + int element = Tile[x][y]; struct ElementInfo *ei = &element_info[element]; int direction = MovDir[x][y]; int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0); @@ -8423,11 +8847,33 @@ void ContinueMoving(int x, int y) boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y)); boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y)); boolean last_line = (newy == lev_fieldy - 1); + boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0); + + if (pushed_by_player) // special case: moving object pushed by player + { + MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos)); + } + else if (use_step_delay) // special case: moving object has step delay + { + if (!MovDelay[x][y]) + MovPos[x][y] += getElementMoveStepsize(x, y); - 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); - if (pushed_by_player) // special case: moving object pushed by player - MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos)); + return; // element is still waiting + } + } + else // normal case: generically moving object + { + MovPos[x][y] += getElementMoveStepsize(x, y); + } if (ABS(MovPos[x][y]) < TILEX) { @@ -8438,100 +8884,100 @@ void ContinueMoving(int x, int y) // element reached destination field - Feld[x][y] = EL_EMPTY; - Feld[newx][newy] = element; + Tile[x][y] = EL_EMPTY; + Tile[newx][newy] = element; MovPos[x][y] = 0; // force "not moving" for "crumbled sand" if (Store[x][y] == EL_ACID) // element is moving into acid pool { - element = Feld[newx][newy] = EL_ACID; + element = Tile[newx][newy] = EL_ACID; } else if (element == EL_MOLE) { - Feld[x][y] = EL_SAND; + Tile[x][y] = EL_SAND; TEST_DrawLevelFieldCrumbledNeighbours(x, y); } else if (element == EL_QUICKSAND_FILLING) { - element = Feld[newx][newy] = get_next_element(element); + element = Tile[newx][newy] = get_next_element(element); Store[newx][newy] = Store[x][y]; } else if (element == EL_QUICKSAND_EMPTYING) { - Feld[x][y] = get_next_element(element); - element = Feld[newx][newy] = Store[x][y]; + Tile[x][y] = get_next_element(element); + element = Tile[newx][newy] = Store[x][y]; } else if (element == EL_QUICKSAND_FAST_FILLING) { - element = Feld[newx][newy] = get_next_element(element); + element = Tile[newx][newy] = get_next_element(element); Store[newx][newy] = Store[x][y]; } else if (element == EL_QUICKSAND_FAST_EMPTYING) { - Feld[x][y] = get_next_element(element); - element = Feld[newx][newy] = Store[x][y]; + Tile[x][y] = get_next_element(element); + element = Tile[newx][newy] = Store[x][y]; } else if (element == EL_MAGIC_WALL_FILLING) { - element = Feld[newx][newy] = get_next_element(element); + element = Tile[newx][newy] = get_next_element(element); if (!game.magic_wall_active) - element = Feld[newx][newy] = EL_MAGIC_WALL_DEAD; + element = Tile[newx][newy] = EL_MAGIC_WALL_DEAD; Store[newx][newy] = Store[x][y]; } else if (element == EL_MAGIC_WALL_EMPTYING) { - Feld[x][y] = get_next_element(element); + Tile[x][y] = get_next_element(element); if (!game.magic_wall_active) - Feld[x][y] = EL_MAGIC_WALL_DEAD; - element = Feld[newx][newy] = Store[x][y]; + Tile[x][y] = EL_MAGIC_WALL_DEAD; + element = Tile[newx][newy] = Store[x][y]; InitField(newx, newy, FALSE); } else if (element == EL_BD_MAGIC_WALL_FILLING) { - element = Feld[newx][newy] = get_next_element(element); + element = Tile[newx][newy] = get_next_element(element); if (!game.magic_wall_active) - element = Feld[newx][newy] = EL_BD_MAGIC_WALL_DEAD; + element = Tile[newx][newy] = EL_BD_MAGIC_WALL_DEAD; Store[newx][newy] = Store[x][y]; } else if (element == EL_BD_MAGIC_WALL_EMPTYING) { - Feld[x][y] = get_next_element(element); + Tile[x][y] = get_next_element(element); if (!game.magic_wall_active) - Feld[x][y] = EL_BD_MAGIC_WALL_DEAD; - element = Feld[newx][newy] = Store[x][y]; + Tile[x][y] = EL_BD_MAGIC_WALL_DEAD; + element = Tile[newx][newy] = Store[x][y]; InitField(newx, newy, FALSE); } else if (element == EL_DC_MAGIC_WALL_FILLING) { - element = Feld[newx][newy] = get_next_element(element); + element = Tile[newx][newy] = get_next_element(element); if (!game.magic_wall_active) - element = Feld[newx][newy] = EL_DC_MAGIC_WALL_DEAD; + element = Tile[newx][newy] = EL_DC_MAGIC_WALL_DEAD; Store[newx][newy] = Store[x][y]; } else if (element == EL_DC_MAGIC_WALL_EMPTYING) { - Feld[x][y] = get_next_element(element); + Tile[x][y] = get_next_element(element); if (!game.magic_wall_active) - Feld[x][y] = EL_DC_MAGIC_WALL_DEAD; - element = Feld[newx][newy] = Store[x][y]; + Tile[x][y] = EL_DC_MAGIC_WALL_DEAD; + element = Tile[newx][newy] = Store[x][y]; InitField(newx, newy, FALSE); } else if (element == EL_AMOEBA_DROPPING) { - Feld[x][y] = get_next_element(element); - element = Feld[newx][newy] = Store[x][y]; + Tile[x][y] = get_next_element(element); + element = Tile[newx][newy] = Store[x][y]; } else if (element == EL_SOKOBAN_OBJECT) { if (Back[x][y]) - Feld[x][y] = Back[x][y]; + Tile[x][y] = Back[x][y]; if (Back[newx][newy]) - Feld[newx][newy] = EL_SOKOBAN_FIELD_FULL; + Tile[newx][newy] = EL_SOKOBAN_FIELD_FULL; Back[x][y] = Back[newx][newy] = 0; } @@ -8580,17 +9026,17 @@ void ContinueMoving(int x, int y) if (ei->move_leave_element == EL_TRIGGER_ELEMENT) move_leave_element = (stored == EL_ACID ? EL_EMPTY : stored); - Feld[x][y] = move_leave_element; + Tile[x][y] = move_leave_element; - if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS) + if (element_info[Tile[x][y]].move_direction_initial == MV_START_PREVIOUS) MovDir[x][y] = direction; InitField(x, y, FALSE); - if (GFX_CRUMBLED(Feld[x][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); } @@ -8667,7 +9113,7 @@ void ContinueMoving(int x, int y) CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER, player->index_bit, push_side); - CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X, + CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X, player->index_bit, push_side); } @@ -8687,60 +9133,48 @@ void ContinueMoving(int x, int y) MV_DIR_OPPOSITE(direction)); } -int AmoebeNachbarNr(int ax, int ay) +int AmoebaNeighbourNr(int ax, int ay) { int i; - int element = Feld[ax][ay]; + int element = Tile[ax][ay]; int group_nr = 0; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - int x = ax + xy[i][0]; - int y = ay + xy[i][1]; + int x = ax + xy[i].x; + int y = ay + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; - if (Feld[x][y] == element && AmoebaNr[x][y] > 0) + if (Tile[x][y] == element && AmoebaNr[x][y] > 0) group_nr = AmoebaNr[x][y]; } return group_nr; } -static void AmoebenVereinigen(int ax, int ay) +static void AmoebaMerge(int ax, int ay) { int i, x, y, xx, yy; int new_group_nr = AmoebaNr[ax][ay]; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (new_group_nr == 0) return; for (i = 0; i < NUM_DIRECTIONS; i++) { - x = ax + xy[i][0]; - y = ay + xy[i][1]; + x = ax + xy[i].x; + y = ay + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; - if ((Feld[x][y] == EL_AMOEBA_FULL || - Feld[x][y] == EL_BD_AMOEBA || - Feld[x][y] == EL_AMOEBA_DEAD) && + if ((Tile[x][y] == EL_AMOEBA_FULL || + Tile[x][y] == EL_BD_AMOEBA || + Tile[x][y] == EL_AMOEBA_DEAD) && AmoebaNr[x][y] != new_group_nr) { int old_group_nr = AmoebaNr[x][y]; @@ -8762,29 +9196,30 @@ static void AmoebenVereinigen(int ax, int ay) } } -void AmoebeUmwandeln(int ax, int ay) +void AmoebaToDiamond(int ax, int ay) { int i, x, y; - if (Feld[ax][ay] == EL_AMOEBA_DEAD) + if (Tile[ax][ay] == EL_AMOEBA_DEAD) { int group_nr = AmoebaNr[ax][ay]; #ifdef DEBUG if (group_nr == 0) { - printf("AmoebeUmwandeln(): ax = %d, ay = %d\n", ax, ay); - printf("AmoebeUmwandeln(): This should never happen!\n"); + Debug("game:playing:AmoebaToDiamond", "ax = %d, ay = %d", ax, ay); + Debug("game:playing:AmoebaToDiamond", "This should never happen!"); + return; } #endif SCAN_PLAYFIELD(x, y) { - if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr) + if (Tile[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr) { AmoebaNr[x][y] = 0; - Feld[x][y] = EL_AMOEBA_TO_DIAMOND; + Tile[x][y] = EL_AMOEBA_TO_DIAMOND; } } @@ -8795,23 +9230,17 @@ void AmoebeUmwandeln(int ax, int ay) } else { - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - x = ax + xy[i][0]; - y = ay + xy[i][1]; + x = ax + xy[i].x; + y = ay + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; - if (Feld[x][y] == EL_AMOEBA_TO_DIAMOND) + if (Tile[x][y] == EL_AMOEBA_TO_DIAMOND) { PlayLevelSound(x, y, (IS_GEM(level.amoeba_content) ? SND_AMOEBA_TURNING_TO_GEM : @@ -8822,7 +9251,7 @@ void AmoebeUmwandeln(int ax, int ay) } } -static void AmoebeUmwandelnBD(int ax, int ay, int new_element) +static void AmoebaToDiamondBD(int ax, int ay, int new_element) { int x, y; int group_nr = AmoebaNr[ax][ay]; @@ -8831,8 +9260,9 @@ static void AmoebeUmwandelnBD(int ax, int ay, int new_element) #ifdef DEBUG if (group_nr == 0) { - printf("AmoebeUmwandelnBD(): ax = %d, ay = %d\n", ax, ay); - printf("AmoebeUmwandelnBD(): This should never happen!\n"); + Debug("game:playing:AmoebaToDiamondBD", "ax = %d, ay = %d", ax, ay); + Debug("game:playing:AmoebaToDiamondBD", "This should never happen!"); + return; } #endif @@ -8840,12 +9270,12 @@ static void AmoebeUmwandelnBD(int ax, int ay, int new_element) SCAN_PLAYFIELD(x, y) { if (AmoebaNr[x][y] == group_nr && - (Feld[x][y] == EL_AMOEBA_DEAD || - Feld[x][y] == EL_BD_AMOEBA || - Feld[x][y] == EL_AMOEBA_GROWING)) + (Tile[x][y] == EL_AMOEBA_DEAD || + Tile[x][y] == EL_BD_AMOEBA || + Tile[x][y] == EL_AMOEBA_GROWING)) { AmoebaNr[x][y] = 0; - Feld[x][y] = new_element; + Tile[x][y] = new_element; InitField(x, y, FALSE); TEST_DrawLevelField(x, y); done = TRUE; @@ -8858,19 +9288,18 @@ static void AmoebeUmwandelnBD(int ax, int ay, int new_element) SND_BD_AMOEBA_TURNING_TO_GEM)); } -static void AmoebeWaechst(int x, int y) +static void AmoebaGrowing(int x, int y) { - static unsigned int sound_delay = 0; - static unsigned int sound_delay_value = 0; + static DelayCounter sound_delay = { 0 }; if (!MovDelay[x][y]) // start new growing cycle { MovDelay[x][y] = 7; - if (DelayReached(&sound_delay, sound_delay_value)) + if (DelayReached(&sound_delay)) { PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING); - sound_delay_value = 30; + sound_delay.value = 30; } } @@ -8882,29 +9311,28 @@ static void AmoebeWaechst(int x, int y) int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING, 6 - MovDelay[x][y]); - DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame); + DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame); } if (!MovDelay[x][y]) { - Feld[x][y] = Store[x][y]; + Tile[x][y] = Store[x][y]; Store[x][y] = 0; TEST_DrawLevelField(x, y); } } } -static void AmoebaDisappearing(int x, int y) +static void AmoebaShrinking(int x, int y) { - static unsigned int sound_delay = 0; - static unsigned int sound_delay_value = 0; + static DelayCounter sound_delay = { 0 }; if (!MovDelay[x][y]) // start new shrinking cycle { MovDelay[x][y] = 7; - if (DelayReached(&sound_delay, sound_delay_value)) - sound_delay_value = 30; + if (DelayReached(&sound_delay)) + sound_delay.value = 30; } if (MovDelay[x][y]) // wait some time before shrinking @@ -8915,12 +9343,12 @@ static void AmoebaDisappearing(int x, int y) int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING, 6 - MovDelay[x][y]); - DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame); + DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame); } if (!MovDelay[x][y]) { - Feld[x][y] = EL_EMPTY; + Tile[x][y] = EL_EMPTY; TEST_DrawLevelField(x, y); // don't let mole enter this field in this cycle; @@ -8930,24 +9358,18 @@ static void AmoebaDisappearing(int x, int y) } } -static void AmoebeAbleger(int ax, int ay) +static void AmoebaReproduce(int ax, int ay) { int i; - int element = Feld[ax][ay]; + int element = Tile[ax][ay]; int graphic = el2img(element); int newax = ax, neway = ay; boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER); - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (!level.amoeba_speed && element != EL_EMC_DRIPPER) { - Feld[ax][ay] = EL_AMOEBA_DEAD; + Tile[ax][ay] = EL_AMOEBA_DEAD; TEST_DrawLevelField(ax, ay); return; } @@ -8968,16 +9390,16 @@ static void AmoebeAbleger(int ax, int ay) if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER { int start = RND(4); - int x = ax + xy[start][0]; - int y = ay + xy[start][1]; + int x = ax + xy[start].x; + int y = ay + xy[start].y; if (!IN_LEV_FIELD(x, y)) return; if (IS_FREE(x, y) || - CAN_GROW_INTO(Feld[x][y]) || - Feld[x][y] == EL_QUICKSAND_EMPTY || - Feld[x][y] == EL_QUICKSAND_FAST_EMPTY) + CAN_GROW_INTO(Tile[x][y]) || + Tile[x][y] == EL_QUICKSAND_EMPTY || + Tile[x][y] == EL_QUICKSAND_FAST_EMPTY) { newax = x; neway = y; @@ -8994,16 +9416,16 @@ static void AmoebeAbleger(int ax, int ay) for (i = 0; i < NUM_DIRECTIONS; i++) { int j = (start + i) % 4; - int x = ax + xy[j][0]; - int y = ay + xy[j][1]; + int x = ax + xy[j].x; + int y = ay + xy[j].y; if (!IN_LEV_FIELD(x, y)) continue; if (IS_FREE(x, y) || - CAN_GROW_INTO(Feld[x][y]) || - Feld[x][y] == EL_QUICKSAND_EMPTY || - Feld[x][y] == EL_QUICKSAND_FAST_EMPTY) + CAN_GROW_INTO(Tile[x][y]) || + Tile[x][y] == EL_QUICKSAND_EMPTY || + Tile[x][y] == EL_QUICKSAND_FAST_EMPTY) { newax = x; neway = y; @@ -9017,16 +9439,16 @@ static void AmoebeAbleger(int ax, int ay) { if (i == 4 && (!waiting_for_player || element == EL_BD_AMOEBA)) { - Feld[ax][ay] = EL_AMOEBA_DEAD; + Tile[ax][ay] = EL_AMOEBA_DEAD; TEST_DrawLevelField(ax, ay); AmoebaCnt[AmoebaNr[ax][ay]]--; if (AmoebaCnt[AmoebaNr[ax][ay]] <= 0) // amoeba is completely dead { if (element == EL_AMOEBA_FULL) - AmoebeUmwandeln(ax, ay); + AmoebaToDiamond(ax, ay); else if (element == EL_BD_AMOEBA) - AmoebeUmwandelnBD(ax, ay, level.amoeba_content); + AmoebaToDiamondBD(ax, ay, level.amoeba_content); } } return; @@ -9040,8 +9462,10 @@ static void AmoebeAbleger(int ax, int ay) #ifdef DEBUG if (new_group_nr == 0) { - printf("AmoebeAbleger(): newax = %d, neway = %d\n", newax, neway); - printf("AmoebeAbleger(): This should never happen!\n"); + Debug("game:playing:AmoebaReproduce", "newax = %d, neway = %d", + newax, neway); + Debug("game:playing:AmoebaReproduce", "This should never happen!"); + return; } #endif @@ -9051,11 +9475,11 @@ static void AmoebeAbleger(int ax, int ay) AmoebaCnt2[new_group_nr]++; // if amoeba touches other amoeba(s) after growing, unify them - AmoebenVereinigen(newax, neway); + AmoebaMerge(newax, neway); if (element == EL_BD_AMOEBA && AmoebaCnt2[new_group_nr] >= 200) { - AmoebeUmwandelnBD(newax, neway, EL_BD_ROCK); + AmoebaToDiamondBD(newax, neway, EL_BD_ROCK); return; } } @@ -9064,19 +9488,19 @@ static void AmoebeAbleger(int ax, int ay) if (!can_drop || neway < ay || !IS_FREE(newax, neway) || (neway == lev_fieldy - 1 && newax != ax)) { - Feld[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba + Tile[newax][neway] = EL_AMOEBA_GROWING; // creation of new amoeba Store[newax][neway] = element; } else if (neway == ay || element == EL_EMC_DRIPPER) { - Feld[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba + Tile[newax][neway] = EL_AMOEBA_DROP; // drop left/right of amoeba PlayLevelSoundAction(newax, neway, ACTION_GROWING); } else { InitMovingField(ax, ay, MV_DOWN); // drop dripping from amoeba - Feld[ax][ay] = EL_AMOEBA_DROPPING; + Tile[ax][ay] = EL_AMOEBA_DROPPING; Store[ax][ay] = EL_AMOEBA_DROP; ContinueMoving(ax, ay); return; @@ -9089,7 +9513,7 @@ static void Life(int ax, int ay) { int x1, y1, x2, y2; int life_time = 40; - int element = Feld[ax][ay]; + int element = Tile[ax][ay]; int graphic = el2img(element); int *life_parameter = (element == EL_GAME_OF_LIFE ? level.game_of_life : level.biomaze); @@ -9113,8 +9537,8 @@ static void Life(int ax, int ay) for (y1 = -1; y1 < 2; y1++) for (x1 = -1; x1 < 2; x1++) { - int xx = ax+x1, yy = ay+y1; - int old_element = Feld[xx][yy]; + int xx = ax + x1, yy = ay + y1; + int old_element = Tile[xx][yy]; int num_neighbours = 0; if (!IN_LEV_FIELD(xx, yy)) @@ -9122,7 +9546,7 @@ static void Life(int ax, int ay) for (y2 = -1; y2 < 2; y2++) for (x2 = -1; x2 < 2; x2++) { - int x = xx+x2, y = yy+y2; + int x = xx + x2, y = yy + y2; if (!IN_LEV_FIELD(x, y) || (x == xx && y == yy)) continue; @@ -9132,7 +9556,7 @@ static void Life(int ax, int ay) if (level.use_life_bugs) is_neighbour = - (((Feld[x][y] == element || is_player_cell) && !Stop[x][y]) || + (((Tile[x][y] == element || is_player_cell) && !Stop[x][y]) || (IS_FREE(x, y) && Stop[x][y])); else is_neighbour = @@ -9154,21 +9578,21 @@ static void Life(int ax, int ay) if (num_neighbours < life_parameter[0] || num_neighbours > life_parameter[1]) { - Feld[xx][yy] = EL_EMPTY; - if (Feld[xx][yy] != old_element) + Tile[xx][yy] = EL_EMPTY; + if (Tile[xx][yy] != old_element) TEST_DrawLevelField(xx, yy); Stop[xx][yy] = TRUE; changed = TRUE; } } - else if (is_free || CAN_GROW_INTO(Feld[xx][yy])) + else if (is_free || CAN_GROW_INTO(Tile[xx][yy])) { // free border field if (num_neighbours >= life_parameter[2] && num_neighbours <= life_parameter[3]) { - Feld[xx][yy] = element; - MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1); - if (Feld[xx][yy] != old_element) + Tile[xx][yy] = element; + MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1); + if (Tile[xx][yy] != old_element) TEST_DrawLevelField(xx, yy); Stop[xx][yy] = TRUE; changed = TRUE; @@ -9231,7 +9655,7 @@ static void ActivateMagicBall(int bx, int by) x = bx - 1 + xx; y = by - 1 + yy; - if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY) + if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY) CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]); } else @@ -9241,7 +9665,7 @@ static void ActivateMagicBall(int bx, int by) int xx = x - bx + 1; int yy = y - by + 1; - if (IN_LEV_FIELD(x, y) && Feld[x][y] == EL_EMPTY) + if (IN_LEV_FIELD(x, y) && Tile[x][y] == EL_EMPTY) CreateField(x, y, level.ball_content[game.ball_content_nr].e[xx][yy]); } } @@ -9256,7 +9680,7 @@ static void CheckExit(int x, int y) game.sokoban_objects_still_needed > 0 || game.lights_still_needed > 0) { - int element = Feld[x][y]; + int element = Tile[x][y]; int graphic = el2img(element); if (IS_ANIMATED(graphic)) @@ -9269,7 +9693,7 @@ static void CheckExit(int x, int y) if (game.all_players_gone) return; - Feld[x][y] = EL_EXIT_OPENING; + Tile[x][y] = EL_EXIT_OPENING; PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING); } @@ -9281,7 +9705,7 @@ static void CheckExitEM(int x, int y) game.sokoban_objects_still_needed > 0 || game.lights_still_needed > 0) { - int element = Feld[x][y]; + int element = Tile[x][y]; int graphic = el2img(element); if (IS_ANIMATED(graphic)) @@ -9294,7 +9718,7 @@ static void CheckExitEM(int x, int y) if (game.all_players_gone) return; - Feld[x][y] = EL_EM_EXIT_OPENING; + Tile[x][y] = EL_EM_EXIT_OPENING; PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING); } @@ -9306,7 +9730,7 @@ static void CheckExitSteel(int x, int y) game.sokoban_objects_still_needed > 0 || game.lights_still_needed > 0) { - int element = Feld[x][y]; + int element = Tile[x][y]; int graphic = el2img(element); if (IS_ANIMATED(graphic)) @@ -9319,7 +9743,7 @@ static void CheckExitSteel(int x, int y) if (game.all_players_gone) return; - Feld[x][y] = EL_STEEL_EXIT_OPENING; + Tile[x][y] = EL_STEEL_EXIT_OPENING; PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING); } @@ -9331,7 +9755,7 @@ static void CheckExitSteelEM(int x, int y) game.sokoban_objects_still_needed > 0 || game.lights_still_needed > 0) { - int element = Feld[x][y]; + int element = Tile[x][y]; int graphic = el2img(element); if (IS_ANIMATED(graphic)) @@ -9344,7 +9768,7 @@ static void CheckExitSteelEM(int x, int y) if (game.all_players_gone) return; - Feld[x][y] = EL_EM_STEEL_EXIT_OPENING; + Tile[x][y] = EL_EM_STEEL_EXIT_OPENING; PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING); } @@ -9353,7 +9777,7 @@ static void CheckExitSP(int x, int y) { if (game.gems_still_needed > 0) { - int element = Feld[x][y]; + int element = Tile[x][y]; int graphic = el2img(element); if (IS_ANIMATED(graphic)) @@ -9366,7 +9790,7 @@ static void CheckExitSP(int x, int y) if (game.all_players_gone) return; - Feld[x][y] = EL_SP_EXIT_OPENING; + Tile[x][y] = EL_SP_EXIT_OPENING; PlayLevelSoundNearest(x, y, SND_CLASS_SP_EXIT_OPENING); } @@ -9377,11 +9801,11 @@ static void CloseAllOpenTimegates(void) SCAN_PLAYFIELD(x, y) { - int element = Feld[x][y]; + int element = Tile[x][y]; if (element == EL_TIMEGATE_OPEN || element == EL_TIMEGATE_OPENING) { - Feld[x][y] = EL_TIMEGATE_CLOSING; + Tile[x][y] = EL_TIMEGATE_CLOSING; PlayLevelSoundAction(x, y, ACTION_CLOSING); } @@ -9393,7 +9817,7 @@ static void DrawTwinkleOnField(int x, int y) if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y)) return; - if (Feld[x][y] == EL_BD_DIAMOND) + if (Tile[x][y] == EL_BD_DIAMOND) return; if (MovDelay[x][y] == 0) // next animation frame @@ -9403,7 +9827,7 @@ static void DrawTwinkleOnField(int x, int y) { MovDelay[x][y]--; - DrawLevelElementAnimation(x, y, Feld[x][y]); + DrawLevelElementAnimation(x, y, Tile[x][y]); if (MovDelay[x][y] != 0) { @@ -9415,7 +9839,7 @@ static void DrawTwinkleOnField(int x, int y) } } -static void MauerWaechst(int x, int y) +static void WallGrowing(int x, int y) { int delay = 6; @@ -9428,36 +9852,36 @@ static void MauerWaechst(int x, int y) if (IN_SCR_FIELD(SCREENX(x), SCREENY(y))) { - int graphic = el_dir2img(Feld[x][y], GfxDir[x][y]); + int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]); int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]); - DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelGraphic(x, y, graphic, frame); } if (!MovDelay[x][y]) { if (MovDir[x][y] == MV_LEFT) { - if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Feld[x - 1][y])) + if (IN_LEV_FIELD(x - 1, y) && IS_WALL(Tile[x - 1][y])) TEST_DrawLevelField(x - 1, y); } else if (MovDir[x][y] == MV_RIGHT) { - if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Feld[x + 1][y])) + if (IN_LEV_FIELD(x + 1, y) && IS_WALL(Tile[x + 1][y])) TEST_DrawLevelField(x + 1, y); } else if (MovDir[x][y] == MV_UP) { - if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Feld[x][y - 1])) + if (IN_LEV_FIELD(x, y - 1) && IS_WALL(Tile[x][y - 1])) TEST_DrawLevelField(x, y - 1); } else { - if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Feld[x][y + 1])) + if (IN_LEV_FIELD(x, y + 1) && IS_WALL(Tile[x][y + 1])) TEST_DrawLevelField(x, y + 1); } - Feld[x][y] = Store[x][y]; + Tile[x][y] = Store[x][y]; Store[x][y] = 0; GfxDir[x][y] = MovDir[x][y] = MV_NONE; TEST_DrawLevelField(x, y); @@ -9465,15 +9889,59 @@ static void MauerWaechst(int x, int y) } } -static void MauerAbleger(int ax, int ay) +static void CheckWallGrowing(int ax, int ay) { - int element = Feld[ax][ay]; + int element = Tile[ax][ay]; int graphic = el2img(element); - boolean oben_frei = FALSE, unten_frei = FALSE; - boolean links_frei = FALSE, rechts_frei = FALSE; - boolean oben_massiv = FALSE, unten_massiv = FALSE; - boolean links_massiv = FALSE, rechts_massiv = FALSE; - boolean new_wall = FALSE; + boolean free_top = FALSE; + boolean free_bottom = FALSE; + boolean free_left = FALSE; + boolean free_right = FALSE; + boolean stop_top = FALSE; + boolean stop_bottom = FALSE; + boolean stop_left = FALSE; + boolean stop_right = FALSE; + boolean new_wall = FALSE; + + boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_EXPANDABLE_STEELWALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_ANY); + + boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL || + element == EL_EXPANDABLE_WALL_ANY || + element == EL_EXPANDABLE_STEELWALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_ANY); + + boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL || + element == EL_EXPANDABLE_WALL_ANY || + element == EL_EXPANDABLE_WALL || + element == EL_BD_EXPANDABLE_WALL || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_EXPANDABLE_STEELWALL_ANY); + + boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_VERTICAL); + + boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL || + element == EL_EXPANDABLE_WALL || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL); + + int wall_growing = (is_steelwall ? + EL_EXPANDABLE_STEELWALL_GROWING : + EL_EXPANDABLE_WALL_GROWING); + + int gfx_wall_growing_up = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_UP : + IMG_EXPANDABLE_WALL_GROWING_UP); + int gfx_wall_growing_down = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_DOWN : + IMG_EXPANDABLE_WALL_GROWING_DOWN); + int gfx_wall_growing_left = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_LEFT : + IMG_EXPANDABLE_WALL_GROWING_LEFT); + int gfx_wall_growing_right = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT : + IMG_EXPANDABLE_WALL_GROWING_RIGHT); if (IS_ANIMATED(graphic)) DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic); @@ -9488,188 +9956,84 @@ static void MauerAbleger(int ax, int ay) return; } - if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1)) - oben_frei = TRUE; - if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1)) - unten_frei = TRUE; - if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay)) - links_frei = TRUE; - if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay)) - rechts_frei = TRUE; + if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1)) + free_top = TRUE; + if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1)) + free_bottom = TRUE; + if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay)) + free_left = TRUE; + if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay)) + free_right = TRUE; - if (element == EL_EXPANDABLE_WALL_VERTICAL || - element == EL_EXPANDABLE_WALL_ANY) + if (grow_vertical) { - if (oben_frei) - { - Feld[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING; - Store[ax][ay-1] = element; - GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1))) - DrawGraphic(SCREENX(ax), SCREENY(ay - 1), - IMG_EXPANDABLE_WALL_GROWING_UP, 0); - new_wall = TRUE; - } - if (unten_frei) + if (free_top) { - Feld[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING; - Store[ax][ay+1] = element; - GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1))) - DrawGraphic(SCREENX(ax), SCREENY(ay + 1), - IMG_EXPANDABLE_WALL_GROWING_DOWN, 0); - new_wall = TRUE; - } - } + Tile[ax][ay - 1] = wall_growing; + Store[ax][ay - 1] = element; + GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP; - if (element == EL_EXPANDABLE_WALL_HORIZONTAL || - element == EL_EXPANDABLE_WALL_ANY || - element == EL_EXPANDABLE_WALL || - element == EL_BD_EXPANDABLE_WALL) - { - if (links_frei) - { - Feld[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING; - Store[ax-1][ay] = element; - GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT; - if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay))) - DrawGraphic(SCREENX(ax - 1), SCREENY(ay), - IMG_EXPANDABLE_WALL_GROWING_LEFT, 0); - new_wall = TRUE; - } + if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1))) + DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0); - if (rechts_frei) - { - Feld[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING; - Store[ax+1][ay] = element; - GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT; - if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay))) - DrawGraphic(SCREENX(ax + 1), SCREENY(ay), - IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0); new_wall = TRUE; } - } - - if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei)) - TEST_DrawLevelField(ax, ay); - - if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1])) - oben_massiv = TRUE; - if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1])) - unten_massiv = TRUE; - if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay])) - links_massiv = TRUE; - if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay])) - rechts_massiv = TRUE; - - if (((oben_massiv && unten_massiv) || - element == EL_EXPANDABLE_WALL_HORIZONTAL || - element == EL_EXPANDABLE_WALL) && - ((links_massiv && rechts_massiv) || - element == EL_EXPANDABLE_WALL_VERTICAL)) - Feld[ax][ay] = EL_WALL; - - if (new_wall) - PlayLevelSoundAction(ax, ay, ACTION_GROWING); -} - -static void MauerAblegerStahl(int ax, int ay) -{ - int element = Feld[ax][ay]; - int graphic = el2img(element); - boolean oben_frei = FALSE, unten_frei = FALSE; - boolean links_frei = FALSE, rechts_frei = FALSE; - boolean oben_massiv = FALSE, unten_massiv = FALSE; - boolean links_massiv = FALSE, rechts_massiv = FALSE; - boolean new_wall = FALSE; - - if (IS_ANIMATED(graphic)) - DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic); - if (!MovDelay[ax][ay]) // start building new wall - MovDelay[ax][ay] = 6; - - if (MovDelay[ax][ay]) // wait some time before building new wall - { - MovDelay[ax][ay]--; - if (MovDelay[ax][ay]) - return; - } + if (free_bottom) + { + Tile[ax][ay + 1] = wall_growing; + Store[ax][ay + 1] = element; + GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN; - if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1)) - oben_frei = TRUE; - if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1)) - unten_frei = TRUE; - if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay)) - links_frei = TRUE; - if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay)) - rechts_frei = TRUE; + if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1))) + DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0); - if (element == EL_EXPANDABLE_STEELWALL_VERTICAL || - element == EL_EXPANDABLE_STEELWALL_ANY) - { - if (oben_frei) - { - Feld[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax][ay-1] = element; - GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1))) - DrawGraphic(SCREENX(ax), SCREENY(ay - 1), - IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0); - new_wall = TRUE; - } - if (unten_frei) - { - Feld[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax][ay+1] = element; - GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1))) - DrawGraphic(SCREENX(ax), SCREENY(ay + 1), - IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0); new_wall = TRUE; } } - if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || - element == EL_EXPANDABLE_STEELWALL_ANY) + if (grow_horizontal) { - if (links_frei) + if (free_left) { - Feld[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax-1][ay] = element; - GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT; - if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay))) - DrawGraphic(SCREENX(ax - 1), SCREENY(ay), - IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0); + Tile[ax - 1][ay] = wall_growing; + Store[ax - 1][ay] = element; + GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT; + + if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay))) + DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0); + new_wall = TRUE; } - if (rechts_frei) + if (free_right) { - Feld[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax+1][ay] = element; - GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT; - if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay))) - DrawGraphic(SCREENX(ax + 1), SCREENY(ay), - IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0); + Tile[ax + 1][ay] = wall_growing; + Store[ax + 1][ay] = element; + GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT; + + if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay))) + DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0); + new_wall = TRUE; } } - if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Feld[ax][ay-1])) - oben_massiv = TRUE; - if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Feld[ax][ay+1])) - unten_massiv = TRUE; - if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Feld[ax-1][ay])) - links_massiv = TRUE; - if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Feld[ax+1][ay])) - rechts_massiv = TRUE; + if (element == EL_EXPANDABLE_WALL && (free_left || free_right)) + TEST_DrawLevelField(ax, ay); + + if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1])) + stop_top = TRUE; + if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1])) + stop_bottom = TRUE; + if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay])) + stop_left = TRUE; + if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay])) + stop_right = TRUE; - if (((oben_massiv && unten_massiv) || - element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) && - ((links_massiv && rechts_massiv) || - element == EL_EXPANDABLE_STEELWALL_VERTICAL)) - Feld[ax][ay] = EL_STEELWALL; + if (((stop_top && stop_bottom) || stop_horizontal) && + ((stop_left && stop_right) || stop_vertical)) + Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL); if (new_wall) PlayLevelSoundAction(ax, ay, ACTION_GROWING); @@ -9679,24 +10043,19 @@ static void CheckForDragon(int x, int y) { int i, j; boolean dragon_found = FALSE; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { for (j = 0; j < 4; j++) { - int xx = x + j * xy[i][0], yy = y + j * xy[i][1]; + int xx = x + j * xy[i].x; + int yy = y + j * xy[i].y; if (IN_LEV_FIELD(xx, yy) && - (Feld[xx][yy] == EL_FLAMES || Feld[xx][yy] == EL_DRAGON)) + (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON)) { - if (Feld[xx][yy] == EL_DRAGON) + if (Tile[xx][yy] == EL_DRAGON) dragon_found = TRUE; } else @@ -9710,11 +10069,12 @@ static void CheckForDragon(int x, int y) { for (j = 0; j < 3; j++) { - int xx = x + j * xy[i][0], yy = y + j * xy[i][1]; - - if (IN_LEV_FIELD(xx, yy) && Feld[xx][yy] == EL_FLAMES) + int xx = x + j * xy[i].x; + int yy = y + j * xy[i].y; + + if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES) { - Feld[xx][yy] = EL_EMPTY; + Tile[xx][yy] = EL_EMPTY; TEST_DrawLevelField(xx, yy); } else @@ -9726,7 +10086,7 @@ static void CheckForDragon(int x, int y) static void InitBuggyBase(int x, int y) { - int element = Feld[x][y]; + int element = Tile[x][y]; int activating_delay = FRAMES_PER_SECOND / 4; ChangeDelay[x][y] = @@ -9741,18 +10101,12 @@ static void InitBuggyBase(int x, int y) static void WarnBuggyBase(int x, int y) { int i; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy)) { @@ -9955,7 +10309,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) DisplayGameControlValues(); - if (!TimeLeft && setup.time_limit) + if (!TimeLeft && game.time_limit) for (i = 0; i < MAX_PLAYERS; i++) KillPlayer(&stored_player[i]); } @@ -10333,7 +10687,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) */ SCAN_PLAYFIELD(xx, yy) { - if (Feld[xx][yy] == element) + if (Tile[xx][yy] == element) CheckElementChange(xx, yy, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO); } @@ -10360,7 +10714,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) SCAN_PLAYFIELD(xx, yy) { - if (Feld[xx][yy] == element) + if (Tile[xx][yy] == element) { if (reset_frame) { @@ -10391,12 +10745,12 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) static void CreateFieldExt(int x, int y, int element, boolean is_change) { - int old_element = Feld[x][y]; + int old_element = Tile[x][y]; int new_element = GetElementFromGroupElement(element); 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)); @@ -10408,7 +10762,7 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change) else RemoveField(x, y); - Feld[x][y] = new_element; + Tile[x][y] = new_element; if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS) MovDir[x][y] = previous_move_direction; @@ -10418,7 +10772,7 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change) InitField_WithBug1(x, y, FALSE); - new_element = Feld[x][y]; // element may have changed + new_element = Tile[x][y]; // element may have changed ResetGfxAnimation(x, y); ResetRandomAnimationValue(x, y); @@ -10427,17 +10781,26 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change) if (GFX_CRUMBLED(new_element)) TEST_DrawLevelFieldCrumbledNeighbours(x, y); - } - // check if element under the player changes from accessible to unaccessible - // (needed for special case of dropping element which then changes) - // (must be checked after creating new element for walkable group elements) - if (IS_PLAYER(x, y) && !player_explosion_protected && - IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) - { - Bang(x, y); + if (old_element == EL_EXPLOSION) + { + Store[x][y] = Store2[x][y] = 0; - return; + // check if new element replaces an exploding player, requiring cleanup + if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present) + StorePlayer[x][y] = 0; + } + + // check if element under the player changes from accessible to unaccessible + // (needed for special case of dropping element which then changes) + // (must be checked after creating new element for walkable group elements) + if (IS_PLAYER(x, y) && !player_explosion_protected && + IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) + { + KillPlayer(PLAYERINFO(x, y)); + + return; + } } // "ChangeCount" not set yet to allow "entered by player" change one time @@ -10463,7 +10826,7 @@ static void CreateElementFromChange(int x, int y, int element) if (game.engine_version >= VERSION_IDENT(3,2,0,7)) { - int old_element = Feld[x][y]; + int old_element = Tile[x][y]; // prevent changed element from moving in same engine frame // unless both old and new element can either fall or move @@ -10482,7 +10845,7 @@ static boolean ChangeElement(int x, int y, int element, int page) int ce_value = CustomValue[x][y]; int ce_score = ei->collect_score; int target_element; - int old_element = Feld[x][y]; + int old_element = Tile[x][y]; // always use default change event to prevent running into a loop if (ChangeEvent[x][y] == -1) @@ -10497,6 +10860,8 @@ static boolean ChangeElement(int x, int y, int element, int page) change->actual_trigger_side = CH_SIDE_NONE; change->actual_trigger_ce_value = 0; change->actual_trigger_ce_score = 0; + change->actual_trigger_x = -1; + change->actual_trigger_y = -1; } // do not change elements more than a specified maximum number of changes @@ -10505,6 +10870,11 @@ static boolean ChangeElement(int x, int y, int element, int page) ChangeCount[x][y]++; // count number of changes in the same frame + if (ei->has_anim_event) + HandleGlobalAnimEventByElementChange(element, page, x, y, + change->actual_trigger_x, + change->actual_trigger_y); + if (change->explode) { Bang(x, y); @@ -10551,7 +10921,7 @@ static boolean ChangeElement(int x, int y, int element, int page) continue; } - e = Feld[ex][ey]; + e = Tile[ex][ey]; if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey)) e = MovingOrBlocked2Element(ex, ey); @@ -10572,7 +10942,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; @@ -10634,6 +11004,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); @@ -10657,11 +11031,9 @@ static void HandleElementChange(int x, int y, int page) if (!CAN_CHANGE_OR_HAS_ACTION(element) && !CAN_CHANGE_OR_HAS_ACTION(Back[x][y])) { - printf("\n\n"); - printf("HandleElementChange(): %d,%d: element = %d ('%s')\n", - x, y, element, element_info[element].token_name); - printf("HandleElementChange(): This should never happen!\n"); - printf("\n\n"); + Debug("game:playing:HandleElementChange", "%d,%d: element = %d ('%s')", + x, y, element, element_info[element].token_name); + Debug("game:playing:HandleElementChange", "This should never happen!"); } #endif @@ -10737,13 +11109,14 @@ static void HandleElementChange(int x, int y, int page) if (ChangeDelay[x][y] != 0) // continue element change { - if (change->can_change) - { - int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); - if (IS_ANIMATED(graphic)) - DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + // also needed if CE can not change, but has CE delay with CE action + if (IS_ANIMATED(graphic)) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + if (change->can_change) + { if (change->change_function) change->change_function(x, y); } @@ -10830,6 +11203,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = trigger_x; + change->actual_trigger_y = trigger_y; if ((change->can_change && !change_done) || change->has_action) { @@ -10837,7 +11212,7 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, SCAN_PLAYFIELD(x, y) { - if (Feld[x][y] == element) + if (Tile[x][y] == element) { if (change->can_change && !change_done) { @@ -10899,15 +11274,15 @@ static boolean CheckElementChangeExt(int x, int y, !HAS_ANY_CHANGE_EVENT(element, trigger_event)) return FALSE; - if (Feld[x][y] == EL_BLOCKED) + if (Tile[x][y] == EL_BLOCKED) { Blocked2Moving(x, y, &x, &y); - element = Feld[x][y]; + element = Tile[x][y]; } // check if element has already changed or is about to change after moving if ((game.engine_version < VERSION_IDENT(3,2,0,7) && - Feld[x][y] != element) || + Tile[x][y] != element) || (game.engine_version >= VERSION_IDENT(3,2,0,7) && (ChangeCount[x][y] >= game.max_num_changes_per_frame || @@ -10925,7 +11300,8 @@ static boolean CheckElementChangeExt(int x, int y, different to element changes that affect other elements to change on the whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */ boolean check_trigger_element = - (trigger_event == CE_TOUCHING_X || + (trigger_event == CE_NEXT_TO_X || + trigger_event == CE_TOUCHING_X || trigger_event == CE_HITTING_X || trigger_event == CE_HIT_BY_X || trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3 @@ -10943,6 +11319,8 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[x][y]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = x; + change->actual_trigger_y = y; // special case: trigger element not at (x,y) position for some events if (check_trigger_element) @@ -10966,6 +11344,8 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_ce_value = CustomValue[xx][yy]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = xx; + change->actual_trigger_y = yy; } if (change->can_change && !change_done) @@ -11069,11 +11449,11 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) // special case for sleeping Murphy when leaning against non-free tile if (!IN_LEV_FIELD(player->jx - 1, player->jy) || - (Feld[player->jx - 1][player->jy] != EL_EMPTY && + (Tile[player->jx - 1][player->jy] != EL_EMPTY && !IS_MOVING(player->jx - 1, player->jy))) move_dir = MV_LEFT; else if (!IN_LEV_FIELD(player->jx + 1, player->jy) || - (Feld[player->jx + 1][player->jy] != EL_EMPTY && + (Tile[player->jx + 1][player->jy] != EL_EMPTY && !IS_MOVING(player->jx + 1, player->jy))) move_dir = MV_RIGHT; else @@ -11197,18 +11577,29 @@ static void CheckSaveEngineSnapshot(struct PlayerInfo *player) if (!player->is_dropping) player->was_dropping = FALSE; } + + static struct MouseActionInfo mouse_action_last = { 0 }; + struct MouseActionInfo mouse_action = player->effective_mouse_action; + boolean new_released = (!mouse_action.button && mouse_action_last.button); + + if (new_released) + CheckSaveEngineSnapshotToList(); + + mouse_action_last = mouse_action; } 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) - 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); @@ -11294,7 +11685,22 @@ static void SetTapeActionFromMouseAction(byte *tape_action, static void CheckLevelSolved(void) { - if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + if (level.game_engine_type == GAME_ENGINE_TYPE_BD) + { + if (game_bd.level_solved && + !game_bd.game_over) // game won + { + LevelSolved(); + + game_bd.game_over = TRUE; + + game.all_players_gone = TRUE; + } + + if (game_bd.game_over) // game lost + game.all_players_gone = TRUE; + } + else if (level.game_engine_type == GAME_ENGINE_TYPE_EM) { if (game_em.level_solved && !game_em.game_over) // game won @@ -11341,14 +11747,78 @@ static void CheckLevelSolved(void) } } +static void PlayTimeoutSound(int seconds_left) +{ + // will be played directly by BD engine (for classic bonus time sounds) + if (level.game_engine_type == GAME_ENGINE_TYPE_BD && checkBonusTime_BD()) + return; + + // try to use individual "running out of time" sound for each second left + int sound = SND_GAME_RUNNING_OUT_OF_TIME_0 - seconds_left; + + // if special sound per second not defined, use default sound + if (getSoundInfoEntryFilename(sound) == NULL) + sound = SND_GAME_RUNNING_OUT_OF_TIME; + + // if out of time, but player still alive, play special "timeout" sound, if defined + if (seconds_left == 0 && !checkGameFailed()) + if (getSoundInfoEntryFilename(SND_GAME_TIMEOUT) != NULL) + sound = SND_GAME_TIMEOUT; + + PlaySound(sound); +} + +static void CheckLevelTime_StepCounter(void) +{ + int i; + + TimePlayed++; + + if (TimeLeft > 0) + { + TimeLeft--; + + if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved) + PlayTimeoutSound(TimeLeft); + + game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; + + DisplayGameControlValues(); + + if (!TimeLeft && game.time_limit && !game.LevelSolved) + for (i = 0; i < MAX_PLAYERS; i++) + KillPlayer(&stored_player[i]); + } + else if (game.no_level_time_limit && !game.all_players_gone) + { + game_panel_controls[GAME_PANEL_TIME].value = TimePlayed; + + DisplayGameControlValues(); + } +} + static void CheckLevelTime(void) { + int frames_per_second = FRAMES_PER_SECOND; int i; - if (TimeFrames >= FRAMES_PER_SECOND) + if (level.game_engine_type == GAME_ENGINE_TYPE_BD) + { + // level time may be running slower in native BD engine + frames_per_second = getFramesPerSecond_BD(); + + // if native engine time changed, force main engine time change + if (getTimeLeft_BD() < TimeLeft) + TimeFrames = frames_per_second; + + // if last second running, wait for native engine time to exactly reach zero + if (getTimeLeft_BD() == 1 && TimeLeft == 1) + TimeFrames = frames_per_second - 1; + } + + if (TimeFrames >= frames_per_second) { TimeFrames = 0; - TapeTime++; for (i = 0; i < MAX_PLAYERS; i++) { @@ -11371,30 +11841,45 @@ static void CheckLevelTime(void) { TimeLeft--; - if (TimeLeft <= 10 && setup.time_limit) - PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); + if (TimeLeft <= 10 && game.time_limit) + PlayTimeoutSound(TimeLeft); /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value is reset from other values in UpdateGameDoorValues() -- FIX THIS */ game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; - if (!TimeLeft && setup.time_limit) + if (!TimeLeft && game.time_limit) { - if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + if (level.game_engine_type == GAME_ENGINE_TYPE_BD) + { + if (game_bd.game->cave->player_state == GD_PL_LIVING) + game_bd.game->cave->player_state = GD_PL_TIMEOUT; + } + else if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + { game_em.lev->killed_out_of_time = TRUE; + } else + { for (i = 0; i < MAX_PLAYERS; i++) KillPlayer(&stored_player[i]); + } } } - else if (game.no_time_limit && !game.all_players_gone) + else if (game.no_level_time_limit && !game.all_players_gone) { game_panel_controls[GAME_PANEL_TIME].value = TimePlayed; } - game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft); + game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft); } + } + + if (TapeTimeFrames >= FRAMES_PER_SECOND) + { + TapeTimeFrames = 0; + TapeTime++; if (tape.recording || tape.playing) DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime); @@ -11410,8 +11895,21 @@ void AdvanceFrameAndPlayerCounters(int player_nr) { int i; - // advance frame counters (global frame counter and time frame counter) + // handle game and tape time differently for native BD game engine + + // tape time is running in native BD engine even if player is not hatched yet + if (!checkGameRunning()) + return; + + // advance frame counters (global frame counter and tape time frame counter) FrameCounter++; + TapeTimeFrames++; + + // level time is running in native BD engine after player is being hatched + if (!checkGamePlaying()) + return; + + // advance time frame counter (used to control available time to solve level) TimeFrames++; // advance player counters (counters for move delay, move animation etc.) @@ -11455,14 +11953,75 @@ void AdvanceFrameAndPlayerCounters(int player_nr) } } -void StartGameActions(boolean init_network_game, boolean record_tape, - int random_seed) +void AdvanceFrameCounter(void) { - unsigned int new_random_seed = InitRND(random_seed); + FrameCounter++; +} + +void AdvanceGfxFrame(void) +{ + int x, y; + + SCAN_PLAYFIELD(x, y) + { + GfxFrame[x][y]++; + } +} + +static void HandleMouseAction(struct MouseActionInfo *mouse_action, + struct MouseActionInfo *mouse_action_last) +{ + if (mouse_action->button) + { + int new_button = (mouse_action->button && mouse_action_last->button == 0); + int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button); + int x = mouse_action->lx; + int y = mouse_action->ly; + int element = Tile[x][y]; + + if (new_button) + { + CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button); + CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X, + ch_button); + } + + CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button); + CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X, + ch_button); + + if (level.use_step_counter) + { + boolean counted_click = FALSE; + + // element clicked that can change when clicked/pressed + if (CAN_CHANGE_OR_HAS_ACTION(element) && + (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) || + HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE))) + counted_click = TRUE; + + // element clicked that can trigger change when clicked/pressed + if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] || + trigger_events[element][CE_MOUSE_PRESSED_ON_X]) + counted_click = TRUE; + + if (new_button && counted_click) + CheckLevelTime_StepCounter(); + } + } +} + +void StartGameActions(boolean init_network_game, boolean record_tape, + int random_seed) +{ + unsigned int new_random_seed = InitRND(random_seed); if (record_tape) TapeStartRecording(new_random_seed); + if (setup.auto_pause_on_start && !tape.pausing) + TapeTogglePause(TAPE_TOGGLE_MANUAL); + if (init_network_game) { SendToServer_LevelFile(); @@ -11492,10 +12051,10 @@ static void GameActionsExt(void) EL_NAME(recursion_loop_element), " caused endless loop! Quit the game?"); - Error(ERR_WARN, "element '%s' caused endless loop in game engine", - EL_NAME(recursion_loop_element)); + 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 @@ -11541,7 +12100,7 @@ static void GameActionsExt(void) int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value); - printf("::: skip == %d\n", skip); + Debug("game:playing:skip", "skip == %d", skip); #else // ---------- main game synchronization point ---------- @@ -11647,7 +12206,7 @@ static void GameActionsExt(void) TapeRecordAction(tape_action); // remember if game was played (especially after tape stopped playing) - if (!tape.playing && summarized_player_action) + if (!tape.playing && summarized_player_action && !checkGameFailed()) game.GamePlayed = TRUE; #if USE_NEW_PLAYER_ASSIGNMENTS @@ -11658,9 +12217,8 @@ static void GameActionsExt(void) byte mapped_action[MAX_PLAYERS]; #if DEBUG_PLAYER_ACTIONS - printf(":::"); for (i = 0; i < MAX_PLAYERS; i++) - printf(" %d, ", stored_player[i].effective_action); + DebugContinued("", "%d, ", stored_player[i].effective_action); #endif for (i = 0; i < MAX_PLAYERS; i++) @@ -11670,19 +12228,18 @@ static void GameActionsExt(void) stored_player[i].effective_action = mapped_action[i]; #if DEBUG_PLAYER_ACTIONS - printf(" =>"); + DebugContinued("", "=> "); for (i = 0; i < MAX_PLAYERS; i++) - printf(" %d, ", stored_player[i].effective_action); - printf("\n"); + DebugContinued("", "%d, ", stored_player[i].effective_action); + DebugContinued("game:playing:player", "\n"); #endif } #if DEBUG_PLAYER_ACTIONS else { - printf(":::"); for (i = 0; i < MAX_PLAYERS; i++) - printf(" %d, ", stored_player[i].effective_action); - printf("\n"); + DebugContinued("", "%d, ", stored_player[i].effective_action); + DebugContinued("game:playing:player", "\n"); } #endif #endif @@ -11702,7 +12259,11 @@ static void GameActionsExt(void) game.snapshot.last_action[i] = stored_player[i].effective_action; } - if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + if (level.game_engine_type == GAME_ENGINE_TYPE_BD) + { + GameActions_BD_Main(); + } + else if (level.game_engine_type == GAME_ENGINE_TYPE_EM) { GameActions_EM_Main(); } @@ -11769,28 +12330,37 @@ void GameActions(void) GameActions_CheckSaveEngineSnapshot(); } +void GameActions_BD_Main(void) +{ + byte effective_action[MAX_PLAYERS]; + int i; + + for (i = 0; i < MAX_PLAYERS; i++) + effective_action[i] = stored_player[i].effective_action; + + GameActions_BD(effective_action); +} + void GameActions_EM_Main(void) { byte effective_action[MAX_PLAYERS]; - boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing); int i; for (i = 0; i < MAX_PLAYERS; i++) effective_action[i] = stored_player[i].effective_action; - GameActions_EM(effective_action, warp_mode); + GameActions_EM(effective_action); } void GameActions_SP_Main(void) { byte effective_action[MAX_PLAYERS]; - boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing); int i; for (i = 0; i < MAX_PLAYERS; i++) effective_action[i] = stored_player[i].effective_action; - GameActions_SP(effective_action, warp_mode); + GameActions_SP(effective_action); for (i = 0; i < MAX_PLAYERS; i++) { @@ -11803,9 +12373,9 @@ void GameActions_SP_Main(void) void GameActions_MM_Main(void) { - boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing); + AdvanceGfxFrame(); - GameActions_MM(local_player->effective_mouse_action, warp_mode); + GameActions_MM(local_player->effective_mouse_action); } void GameActions_RND_Main(void) @@ -11869,10 +12439,14 @@ void GameActions_RND(void) game.centered_player_nr = game.centered_player_nr_next; game.set_centered_player = FALSE; - DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch); + DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch); DrawGameDoorValues(); } + // check single step mode (set flag and clear again if any player is active) + game.enter_single_step_mode = + (tape.single_step && tape.recording && !tape.pausing); + for (i = 0; i < MAX_PLAYERS; i++) { int actual_player_action = stored_player[i].effective_action; @@ -11897,6 +12471,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 @@ -11918,7 +12496,7 @@ void GameActions_RND(void) if (player->active && player->is_pushing && player->is_moving && IS_MOVING(x, y) && (game.engine_version < VERSION_IDENT(2,2,0,7) || - Feld[x][y] == EL_SPRING)) + Tile[x][y] == EL_SPRING)) { ContinueMoving(x, y); @@ -11931,36 +12509,47 @@ void GameActions_RND(void) SCAN_PLAYFIELD(x, y) { - Last[x][y] = Feld[x][y]; + Last[x][y] = Tile[x][y]; ChangeCount[x][y] = 0; ChangeEvent[x][y] = -1; // this must be handled before main playfield loop - if (Feld[x][y] == EL_PLAYER_IS_LEAVING) + if (Tile[x][y] == EL_PLAYER_IS_LEAVING) { MovDelay[x][y]--; if (MovDelay[x][y] <= 0) RemoveField(x, y); } - if (Feld[x][y] == EL_ELEMENT_SNAPPING) + if (Tile[x][y] == EL_ELEMENT_SNAPPING) { 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; } } #if DEBUG if (ChangePage[x][y] != -1 && ChangeDelay[x][y] != 1) { - printf("GameActions(): x = %d, y = %d: ChangePage != -1\n", x, y); - printf("GameActions(): This should never happen!\n"); + Debug("game:playing:GameActions_RND", "x = %d, y = %d: ChangePage != -1", + x, y); + Debug("game:playing:GameActions_RND", "This should never happen!"); ChangePage[x][y] = -1; } @@ -11994,39 +12583,26 @@ void GameActions_RND(void) Blocked2Moving(x, y, &oldx, &oldy); if (!IS_MOVING(oldx, oldy)) { - printf("GameActions(): (BLOCKED => MOVING) context corrupted!\n"); - printf("GameActions(): BLOCKED: x = %d, y = %d\n", x, y); - printf("GameActions(): !MOVING: oldx = %d, oldy = %d\n", oldx, oldy); - printf("GameActions(): This should never happen!\n"); + Debug("game:playing:GameActions_RND", "(BLOCKED => MOVING) context corrupted!"); + Debug("game:playing:GameActions_RND", "BLOCKED: x = %d, y = %d", x, y); + Debug("game:playing:GameActions_RND", "!MOVING: oldx = %d, oldy = %d", oldx, oldy); + Debug("game:playing:GameActions_RND", "This should never happen!"); } } #endif } - if (mouse_action.button) - { - int new_button = (mouse_action.button && mouse_action_last.button == 0); - - x = mouse_action.lx; - y = mouse_action.ly; - element = Feld[x][y]; - - if (new_button) - { - CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE); - CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X); - } - - CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE); - CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X); - } + HandleMouseAction(&mouse_action, &mouse_action_last); SCAN_PLAYFIELD(x, y) { - element = Feld[x][y]; + element = Tile[x][y]; graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); last_gfx_frame = GfxFrame[x][y]; + if (element == EL_EMPTY) + graphic = el2img(GfxElementEmpty[x][y]); + ResetGfxFrame(x, y); if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y]) @@ -12056,15 +12632,17 @@ void GameActions_RND(void) HandleElementChange(x, y, page); - element = Feld[x][y]; + element = Tile[x][y]; graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); } + CheckNextToConditions(x, y); + if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element))) { StartMoving(x, y); - element = Feld[x][y]; + element = Tile[x][y]; graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); if (IS_ANIMATED(graphic) && @@ -12097,13 +12675,13 @@ void GameActions_RND(void) else if (IS_ACTIVE_BOMB(element)) CheckDynamite(x, y); else if (element == EL_AMOEBA_GROWING) - AmoebeWaechst(x, y); + AmoebaGrowing(x, y); else if (element == EL_AMOEBA_SHRINKING) - AmoebaDisappearing(x, y); + AmoebaShrinking(x, y); #if !USE_NEW_AMOEBA_CODE else if (IS_AMOEBALIVE(element)) - AmoebeAbleger(x, y); + AmoebaReproduce(x, y); #endif else if (element == EL_GAME_OF_LIFE || element == EL_BIOMAZE) @@ -12120,17 +12698,16 @@ void GameActions_RND(void) CheckExitSP(x, y); else if (element == EL_EXPANDABLE_WALL_GROWING || element == EL_EXPANDABLE_STEELWALL_GROWING) - MauerWaechst(x, y); + WallGrowing(x, y); else if (element == EL_EXPANDABLE_WALL || element == EL_EXPANDABLE_WALL_HORIZONTAL || element == EL_EXPANDABLE_WALL_VERTICAL || element == EL_EXPANDABLE_WALL_ANY || - element == EL_BD_EXPANDABLE_WALL) - MauerAbleger(x, y); - else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_BD_EXPANDABLE_WALL || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || element == EL_EXPANDABLE_STEELWALL_VERTICAL || element == EL_EXPANDABLE_STEELWALL_ANY) - MauerAblegerStahl(x, y); + CheckWallGrowing(x, y); else if (element == EL_FLAMES) CheckForDragon(x, y); else if (element == EL_EXPLOSION) @@ -12139,7 +12716,7 @@ void GameActions_RND(void) element == EL_DIAGONAL_SHRINKING || element == EL_DIAGONAL_GROWING) { - graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]); + graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]); DrawLevelGraphicAnimationIfNeeded(x, y, graphic); } @@ -12182,9 +12759,9 @@ void GameActions_RND(void) { x = RND(lev_fieldx); y = RND(lev_fieldy); - element = Feld[x][y]; + element = Tile[x][y]; - if (!IS_PLAYER(x,y) && + if (!IS_PLAYER(x, y) && (element == EL_EMPTY || CAN_GROW_INTO(element) || element == EL_QUICKSAND_EMPTY || @@ -12192,11 +12769,11 @@ void GameActions_RND(void) element == EL_ACID_SPLASH_LEFT || element == EL_ACID_SPLASH_RIGHT)) { - if ((IN_LEV_FIELD(x, y-1) && Feld[x][y-1] == EL_AMOEBA_WET) || - (IN_LEV_FIELD(x-1, y) && Feld[x-1][y] == EL_AMOEBA_WET) || - (IN_LEV_FIELD(x+1, y) && Feld[x+1][y] == EL_AMOEBA_WET) || - (IN_LEV_FIELD(x, y+1) && Feld[x][y+1] == EL_AMOEBA_WET)) - Feld[x][y] = EL_AMOEBA_DROP; + if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) || + (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) || + (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) || + (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET)) + Tile[x][y] = EL_AMOEBA_DROP; } random = random * 129 + 1; @@ -12208,7 +12785,7 @@ void GameActions_RND(void) SCAN_PLAYFIELD(x, y) { - element = Feld[x][y]; + element = Tile[x][y]; if (ExplodeField[x][y]) Explode(x, y, EX_PHASE_START, ExplodeField[x][y]); @@ -12224,7 +12801,7 @@ void GameActions_RND(void) { if (!(game.magic_wall_time_left % 4)) { - int element = Feld[magic_wall_x][magic_wall_y]; + int element = Tile[magic_wall_x][magic_wall_y]; if (element == EL_BD_MAGIC_WALL_FULL || element == EL_BD_MAGIC_WALL_ACTIVE || @@ -12246,24 +12823,24 @@ void GameActions_RND(void) { SCAN_PLAYFIELD(x, y) { - element = Feld[x][y]; + element = Tile[x][y]; if (element == EL_MAGIC_WALL_ACTIVE || element == EL_MAGIC_WALL_FULL) { - Feld[x][y] = EL_MAGIC_WALL_DEAD; + Tile[x][y] = EL_MAGIC_WALL_DEAD; TEST_DrawLevelField(x, y); } else if (element == EL_BD_MAGIC_WALL_ACTIVE || element == EL_BD_MAGIC_WALL_FULL) { - Feld[x][y] = EL_BD_MAGIC_WALL_DEAD; + Tile[x][y] = EL_BD_MAGIC_WALL_DEAD; TEST_DrawLevelField(x, y); } else if (element == EL_DC_MAGIC_WALL_ACTIVE || element == EL_DC_MAGIC_WALL_FULL) { - Feld[x][y] = EL_DC_MAGIC_WALL_DEAD; + Tile[x][y] = EL_DC_MAGIC_WALL_DEAD; TEST_DrawLevelField(x, y); } } @@ -12369,6 +12946,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++) @@ -12384,7 +12963,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) @@ -12441,9 +13020,9 @@ static boolean canFallDown(struct PlayerInfo *player) return (IN_LEV_FIELD(jx, jy + 1) && (IS_FREE(jx, jy + 1) || - (Feld[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) && - IS_WALKABLE_FROM(Feld[jx][jy], MV_DOWN) && - !IS_WALKABLE_INSIDE(Feld[jx][jy])); + (Tile[jx][jy + 1] == EL_ACID && player->can_fall_into_acid)) && + IS_WALKABLE_FROM(Tile[jx][jy], MV_DOWN) && + !IS_WALKABLE_INSIDE(Tile[jx][jy])); } static boolean canPassField(int x, int y, int move_dir) @@ -12453,12 +13032,12 @@ static boolean canPassField(int x, int y, int move_dir) int dy = (move_dir & MV_UP ? -1 : move_dir & MV_DOWN ? +1 : 0); int nextx = x + dx; int nexty = y + dy; - int element = Feld[x][y]; + int element = Tile[x][y]; return (IS_PASSABLE_FROM(element, opposite_dir) && !CAN_MOVE(element) && IN_LEV_FIELD(nextx, nexty) && !IS_PLAYER(nextx, nexty) && - IS_WALKABLE_FROM(Feld[nextx][nexty], move_dir) && + IS_WALKABLE_FROM(Tile[nextx][nexty], move_dir) && (level.can_pass_to_walkable || IS_FREE(nextx, nexty))); } @@ -12471,9 +13050,9 @@ static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir) int newy = y + dy; return (IN_LEV_FIELD(newx, newy) && !IS_FREE_OR_PLAYER(newx, newy) && - IS_GRAVITY_REACHABLE(Feld[newx][newy]) && - (IS_DIGGABLE(Feld[newx][newy]) || - IS_WALKABLE_FROM(Feld[newx][newy], opposite_dir) || + IS_GRAVITY_REACHABLE(Tile[newx][newy]) && + (IS_DIGGABLE(Tile[newx][newy]) || + IS_WALKABLE_FROM(Tile[newx][newy], opposite_dir) || canPassField(newx, newy, move_dir))); } @@ -12507,9 +13086,9 @@ static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player) boolean field_under_player_is_free = (IN_LEV_FIELD(jx, jy + 1) && IS_FREE(jx, jy + 1)); boolean player_is_standing_on_valid_field = - (IS_WALKABLE_INSIDE(Feld[jx][jy]) || - (IS_WALKABLE(Feld[jx][jy]) && - !(element_info[Feld[jx][jy]].access_direction & MV_DOWN))); + (IS_WALKABLE_INSIDE(Tile[jx][jy]) || + (IS_WALKABLE(Tile[jx][jy]) && + !(element_info[Tile[jx][jy]].access_direction & MV_DOWN))); if (field_under_player_is_free && !player_is_standing_on_valid_field) player->programmed_action = MV_DOWN; @@ -12558,7 +13137,7 @@ boolean MovePlayerOneStep(struct PlayerInfo *player, !AllPlayersInSight(player, new_jx, new_jy)) return MP_NO_ACTION; - can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG); + can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG); if (can_move != MP_MOVING) return can_move; @@ -12638,8 +13217,9 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy) int original_move_delay_value = player->move_delay_value; #if DEBUG - printf("THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]\n", - tape.counter); + Debug("game:playing:MovePlayer", + "THIS SHOULD ONLY HAPPEN WITH PRE-1.2 LEVEL TAPES. [%d]", + tape.counter); #endif // scroll remaining steps with finest movement resolution @@ -12830,11 +13410,11 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) if (mode == SCROLL_INIT) { - player->actual_frame_counter = FrameCounter; + player->actual_frame_counter.count = FrameCounter; player->GfxPos = move_stepsize * (player->MovPos / move_stepsize); if ((player->block_last_field || player->block_delay_adjustment > 0) && - Feld[last_jx][last_jy] == EL_EMPTY) + Tile[last_jx][last_jy] == EL_EMPTY) { int last_field_block_delay = 0; // start with no blocking at all int block_delay_adjustment = player->block_delay_adjustment; @@ -12852,14 +13432,14 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) // add block delay adjustment (also possible when not blocking) last_field_block_delay += block_delay_adjustment; - Feld[last_jx][last_jy] = EL_PLAYER_IS_LEAVING; + Tile[last_jx][last_jy] = EL_PLAYER_IS_LEAVING; MovDelay[last_jx][last_jy] = last_field_block_delay + 1; } if (player->MovPos != 0) // player has not yet reached destination return; } - else if (!FrameReached(&player->actual_frame_counter, 1)) + else if (!FrameReached(&player->actual_frame_counter)) return; if (player->MovPos != 0) @@ -12888,26 +13468,26 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) } } - player->last_jx = jx; - player->last_jy = jy; - - if (Feld[jx][jy] == EL_EXIT_OPEN || - Feld[jx][jy] == EL_EM_EXIT_OPEN || - Feld[jx][jy] == EL_EM_EXIT_OPENING || - Feld[jx][jy] == EL_STEEL_EXIT_OPEN || - Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN || - Feld[jx][jy] == EL_EM_STEEL_EXIT_OPENING || - Feld[jx][jy] == EL_SP_EXIT_OPEN || - Feld[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case + if (Tile[jx][jy] == EL_EXIT_OPEN || + Tile[jx][jy] == EL_EM_EXIT_OPEN || + Tile[jx][jy] == EL_EM_EXIT_OPENING || + Tile[jx][jy] == EL_STEEL_EXIT_OPEN || + Tile[jx][jy] == EL_EM_STEEL_EXIT_OPEN || + Tile[jx][jy] == EL_EM_STEEL_EXIT_OPENING || + Tile[jx][jy] == EL_SP_EXIT_OPEN || + Tile[jx][jy] == EL_SP_EXIT_OPENING) // <-- special case { ExitPlayer(player); if (game.players_still_needed == 0 && (game.friends_still_needed == 0 || - IS_SP_ELEMENT(Feld[jx][jy]))) + IS_SP_ELEMENT(Tile[jx][jy]))) LevelSolved(); } + player->last_jx = jx; + player->last_jy = jy; + // this breaks one level: "machine", level 000 { int move_direction = player->MovDir; @@ -12915,8 +13495,8 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) int leave_side = move_direction; int old_jx = last_jx; int old_jy = last_jy; - int old_element = Feld[old_jx][old_jy]; - int new_element = Feld[jx][jy]; + int old_element = Tile[old_jx][old_jy]; + int new_element = Tile[jx][jy]; if (IS_CUSTOM_ELEMENT(old_element)) CheckElementChangeByPlayer(old_jx, old_jy, old_element, @@ -12927,13 +13507,18 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) CE_PLAYER_LEAVES_X, player->index_bit, leave_side); - if (IS_CUSTOM_ELEMENT(new_element)) - CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER, - player->index_bit, enter_side); + // needed because pushed element has not yet reached its destination, + // so it would trigger a change event at its previous field location + if (!player->is_pushing) + { + if (IS_CUSTOM_ELEMENT(new_element)) + CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER, + player->index_bit, enter_side); - CheckTriggeredElementChangeByPlayer(jx, jy, new_element, - CE_PLAYER_ENTERS_X, - player->index_bit, enter_side); + CheckTriggeredElementChangeByPlayer(jx, jy, new_element, + CE_PLAYER_ENTERS_X, + player->index_bit, enter_side); + } CheckTriggeredElementChangeBySide(jx, jy, player->initial_element, CE_MOVE_OF_X, move_direction); @@ -12944,44 +13529,33 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) TestIfPlayerTouchesBadThing(jx, jy); TestIfPlayerTouchesCustomElement(jx, jy); - /* needed because pushed element has not yet reached its destination, - so it would trigger a change event at its previous field location */ + // needed because pushed element has not yet reached its destination, + // so it would trigger a change event at its previous field location if (!player->is_pushing) TestIfElementTouchesCustomElement(jx, jy); // for empty space - if (!player->active) - RemovePlayer(player); - } - - if (!game.LevelSolved && level.use_step_counter) - { - int i; - - TimePlayed++; - - if (TimeLeft > 0) + if (level.finish_dig_collect && + (player->is_digging || player->is_collecting)) { - TimeLeft--; - - if (TimeLeft <= 10 && setup.time_limit) - PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); + 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); - game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; - - DisplayGameControlValues(); + CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event, + player->index_bit, enter_side); - if (!TimeLeft && setup.time_limit) - for (i = 0; i < MAX_PLAYERS; i++) - KillPlayer(&stored_player[i]); + player->last_removed_element = EL_UNDEFINED; } - else if (game.no_time_limit && !game.all_players_gone) - { - game_panel_controls[GAME_PANEL_TIME].value = TimePlayed; - DisplayGameControlValues(); - } + if (!player->active) + RemovePlayer(player); } + if (level.use_step_counter) + CheckLevelTime_StepCounter(); + if (tape.single_step && tape.recording && !tape.pausing && !player->programmed_action) TapeTogglePause(TAPE_TOGGLE_AUTOMATIC); @@ -12993,20 +13567,22 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) void ScrollScreen(struct PlayerInfo *player, int mode) { - static unsigned int screen_frame_counter = 0; + static DelayCounter screen_frame_counter = { 0 }; if (mode == SCROLL_INIT) { // set scrolling step size according to actual player's moving speed ScrollStepSize = TILEX / player->move_delay_value; - screen_frame_counter = FrameCounter; + screen_frame_counter.count = FrameCounter; + screen_frame_counter.value = 1; + ScreenMovDir = player->MovDir; ScreenMovPos = player->MovPos; ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize); return; } - else if (!FrameReached(&screen_frame_counter, 1)) + else if (!FrameReached(&screen_frame_counter)) return; if (ScreenMovPos) @@ -13019,15 +13595,73 @@ void ScrollScreen(struct PlayerInfo *player, int mode) ScreenMovDir = MV_NONE; } -void TestIfPlayerTouchesCustomElement(int x, int y) +void CheckNextToConditions(int x, int y) { - static int xy[4][2] = + int element = Tile[x][y]; + + if (IS_PLAYER(x, y)) + TestIfPlayerNextToCustomElement(x, y); + + if (CAN_CHANGE_OR_HAS_ACTION(element) && + HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X)) + TestIfElementNextToCustomElement(x, y); +} + +void TestIfPlayerNextToCustomElement(int x, int y) +{ + struct XY *xy = xy_topdown; + static int trigger_sides[4][2] = { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } + // center side border side + { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top + { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left + { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right + { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom }; + int i; + + if (!IS_PLAYER(x, y)) + return; + + struct PlayerInfo *player = PLAYERINFO(x, y); + + if (player->is_moving) + return; + + for (i = 0; i < NUM_DIRECTIONS; i++) + { + int xx = x + xy[i].x; + int yy = y + xy[i].y; + int border_side = trigger_sides[i][1]; + int border_element; + + if (!IN_LEV_FIELD(xx, yy)) + continue; + + if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy)) + continue; // center and border element not connected + + border_element = Tile[xx][yy]; + + CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER, + player->index_bit, border_side); + CheckTriggeredElementChangeByPlayer(xx, yy, border_element, + CE_PLAYER_NEXT_TO_X, + player->index_bit, border_side); + + /* use player element that is initially defined in the level playfield, + not the player element that corresponds to the runtime player number + (example: a level that contains EL_PLAYER_3 as the only player would + incorrectly give EL_PLAYER_1 for "player->element_nr") */ + + CheckElementChangeBySide(xx, yy, border_element, player->initial_element, + CE_NEXT_TO_X, border_side); + } +} + +void TestIfPlayerTouchesCustomElement(int x, int y) +{ + struct XY *xy = xy_topdown; static int trigger_sides[4][2] = { // center side border side @@ -13043,13 +13677,13 @@ void TestIfPlayerTouchesCustomElement(int x, int y) MV_UP | MV_DOWN, MV_LEFT | MV_RIGHT }; - int center_element = Feld[x][y]; // should always be non-moving! + int center_element = Tile[x][y]; // should always be non-moving! int i; for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int center_side = trigger_sides[i][0]; int border_side = trigger_sides[i][1]; int border_element; @@ -13062,9 +13696,9 @@ void TestIfPlayerTouchesCustomElement(int x, int y) struct PlayerInfo *player = PLAYERINFO(x, y); if (game.engine_version < VERSION_IDENT(3,0,7,0)) - border_element = Feld[xx][yy]; // may be moving! + border_element = Tile[xx][yy]; // may be moving! else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy)) - border_element = Feld[xx][yy]; + border_element = Tile[xx][yy]; else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching border_element = MovingOrBlocked2Element(xx, yy); else @@ -13083,8 +13717,9 @@ void TestIfPlayerTouchesCustomElement(int x, int y) incorrectly give EL_PLAYER_1 for "player->element_nr") */ int player_element = PLAYERINFO(x, y)->initial_element; + // as element "X" is the player here, check opposite (center) side CheckElementChangeBySide(xx, yy, border_element, player_element, - CE_TOUCHING_X, border_side); + CE_TOUCHING_X, center_side); } } else if (IS_PLAYER(xx, yy)) // player found at border element @@ -13110,8 +13745,9 @@ void TestIfPlayerTouchesCustomElement(int x, int y) incorrectly give EL_PLAYER_1 for "player->element_nr") */ int player_element = PLAYERINFO(xx, yy)->initial_element; + // as element "X" is the player here, check opposite (border) side CheckElementChangeBySide(x, y, center_element, player_element, - CE_TOUCHING_X, center_side); + CE_TOUCHING_X, border_side); } break; @@ -13119,15 +13755,48 @@ void TestIfPlayerTouchesCustomElement(int x, int y) } } -void TestIfElementTouchesCustomElement(int x, int y) +void TestIfElementNextToCustomElement(int x, int y) { - static int xy[4][2] = + struct XY *xy = xy_topdown; + static int trigger_sides[4][2] = { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } + // center side border side + { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top + { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left + { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right + { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom }; + int center_element = Tile[x][y]; // should always be non-moving! + int i; + + if (IS_MOVING(x, y) || IS_BLOCKED(x, y)) + return; + + for (i = 0; i < NUM_DIRECTIONS; i++) + { + int xx = x + xy[i].x; + int yy = y + xy[i].y; + int border_side = trigger_sides[i][1]; + int border_element; + + if (!IN_LEV_FIELD(xx, yy)) + continue; + + if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy)) + continue; // center and border element not connected + + border_element = Tile[xx][yy]; + + // check for change of center element (but change it only once) + if (CheckElementChangeBySide(x, y, center_element, border_element, + CE_NEXT_TO_X, border_side)) + break; + } +} + +void TestIfElementTouchesCustomElement(int x, int y) +{ + struct XY *xy = xy_topdown; static int trigger_sides[4][2] = { // center side border side @@ -13144,14 +13813,14 @@ void TestIfElementTouchesCustomElement(int x, int y) MV_LEFT | MV_RIGHT }; boolean change_center_element = FALSE; - int center_element = Feld[x][y]; // should always be non-moving! + int center_element = Tile[x][y]; // should always be non-moving! int border_element_old[NUM_DIRECTIONS]; int i; for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int border_element; border_element_old[i] = -1; @@ -13160,9 +13829,9 @@ void TestIfElementTouchesCustomElement(int x, int y) continue; if (game.engine_version < VERSION_IDENT(3,0,7,0)) - border_element = Feld[xx][yy]; // may be moving! + border_element = Tile[xx][yy]; // may be moving! else if (!IS_MOVING(xx, yy) && !IS_BLOCKED(xx, yy)) - border_element = Feld[xx][yy]; + border_element = Tile[xx][yy]; else if (MovDir[xx][yy] & touch_dir[i]) // elements are touching border_element = MovingOrBlocked2Element(xx, yy); else @@ -13173,8 +13842,8 @@ void TestIfElementTouchesCustomElement(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int center_side = trigger_sides[i][0]; int border_element = border_element_old[i]; @@ -13185,13 +13854,13 @@ void TestIfElementTouchesCustomElement(int x, int y) CheckElementChangeBySide(xx, yy, border_element, center_element, CE_TOUCHING_X, center_side); - // (center element cannot be player, so we dont have to check this here) + // (center element cannot be player, so we don't have to check this here) } for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int border_side = trigger_sides[i][1]; int border_element = border_element_old[i]; @@ -13212,6 +13881,7 @@ void TestIfElementTouchesCustomElement(int x, int y) incorrectly give EL_PLAYER_1 for "player->element_nr") */ int player_element = PLAYERINFO(xx, yy)->initial_element; + // as element "X" is the player here, check opposite (border) side CheckElementChangeBySide(x, y, center_element, player_element, CE_TOUCHING_X, border_side); } @@ -13223,7 +13893,7 @@ void TestIfElementHitsCustomElement(int x, int y, int direction) int dx = (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0); int dy = (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0); int hitx = x + dx, hity = y + dy; - int hitting_element = Feld[x][y]; + int hitting_element = Tile[x][y]; int touched_element; if (IN_LEV_FIELD(hitx, hity) && IS_FREE(hitx, hity)) @@ -13278,13 +13948,7 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir) int i, kill_x = -1, kill_y = -1; int bad_element = -1; - static int test_xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *test_xy = xy_topdown; static int test_dir[4] = { MV_UP, @@ -13297,8 +13961,8 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir) { int test_x, test_y, test_move_dir, test_element; - test_x = good_x + test_xy[i][0]; - test_y = good_y + test_xy[i][1]; + test_x = good_x + test_xy[i].x; + test_y = good_y + test_xy[i].y; if (!IN_LEV_FIELD(test_x, test_y)) continue; @@ -13342,14 +14006,8 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir) void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir) { int i, kill_x = -1, kill_y = -1; - int bad_element = Feld[bad_x][bad_y]; - static int test_xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + int bad_element = Tile[bad_x][bad_y]; + struct XY *test_xy = xy_topdown; static int touch_dir[4] = { MV_LEFT | MV_RIGHT, @@ -13372,8 +14030,8 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir) { int test_x, test_y, test_move_dir, test_element; - test_x = bad_x + test_xy[i][0]; - test_y = bad_y + test_xy[i][1]; + test_x = bad_x + test_xy[i].x; + test_y = bad_y + test_xy[i].y; if (!IN_LEV_FIELD(test_x, test_y)) continue; @@ -13381,7 +14039,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir) test_move_dir = (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE); - test_element = Feld[test_x][test_y]; + test_element = Tile[test_x][test_y]; /* 1st case: good thing is moving towards DONT_RUN_INTO style bad thing; 2nd case: DONT_TOUCH style bad thing does not move away from good thing @@ -13437,7 +14095,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir) void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir) { - int bad_element = Feld[bad_x][bad_y]; + int bad_element = Tile[bad_x][bad_y]; int dx = (bad_move_dir == MV_LEFT ? -1 : bad_move_dir == MV_RIGHT ? +1 : 0); int dy = (bad_move_dir == MV_UP ? -1 : bad_move_dir == MV_DOWN ? +1 : 0); int test_x = bad_x + dx, test_y = bad_y + dy; @@ -13450,7 +14108,7 @@ void TestIfGoodThingGetsHitByBadThing(int bad_x, int bad_y, int bad_move_dir) test_move_dir = (IS_MOVING(test_x, test_y) ? MovDir[test_x][test_y] : MV_NONE); - test_element = Feld[test_x][test_y]; + test_element = Tile[test_x][test_y]; if (test_move_dir != bad_move_dir) { @@ -13525,24 +14183,18 @@ void TestIfBadThingTouchesFriend(int x, int y) void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y) { int i, kill_x = bad_x, kill_y = bad_y; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { int x, y, element; - x = bad_x + xy[i][0]; - y = bad_y + xy[i][1]; + x = bad_x + xy[i].x; + y = bad_y + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; - element = Feld[x][y]; + element = Tile[x][y]; if (IS_AMOEBOID(element) || element == EL_GAME_OF_LIFE || element == EL_AMOEBA_GROWING || element == EL_AMOEBA_DROP) { @@ -13564,8 +14216,9 @@ void KillPlayer(struct PlayerInfo *player) return; #if 0 - printf("::: 0: killed == %d, active == %d, reanimated == %d\n", - player->killed, player->active, player->reanimated); + Debug("game:playing:KillPlayer", + "0: killed == %d, active == %d, reanimated == %d", + player->killed, player->active, player->reanimated); #endif /* the following code was introduced to prevent an infinite loop when calling @@ -13586,22 +14239,24 @@ void KillPlayer(struct PlayerInfo *player) player->killed = TRUE; // remove accessible field at the player's position - Feld[jx][jy] = EL_EMPTY; + RemoveField(jx, jy); // deactivate shield (else Bang()/Explode() would not work right) player->shield_normal_time_left = 0; player->shield_deadly_time_left = 0; #if 0 - printf("::: 1: killed == %d, active == %d, reanimated == %d\n", - player->killed, player->active, player->reanimated); + Debug("game:playing:KillPlayer", + "1: killed == %d, active == %d, reanimated == %d", + player->killed, player->active, player->reanimated); #endif Bang(jx, jy); #if 0 - printf("::: 2: killed == %d, active == %d, reanimated == %d\n", - player->killed, player->active, player->reanimated); + Debug("game:playing:KillPlayer", + "2: killed == %d, active == %d, reanimated == %d", + player->killed, player->active, player->reanimated); #endif if (player->reanimated) // killed player may have been reanimated @@ -13630,7 +14285,6 @@ void BuryPlayer(struct PlayerInfo *player) return; PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING); - PlayLevelSound(jx, jy, SND_GAME_LOSING); RemovePlayer(player); @@ -13680,7 +14334,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); @@ -13688,8 +14343,11 @@ static void setFieldForSnapping(int x, int y, int element, int direction) int action = (graphic_snapping != IMG_EMPTY_SPACE ? ACTION_SNAPPING : IS_DIGGABLE(element) ? ACTION_DIGGING : ACTION_COLLECTING); - Feld[x][y] = EL_ELEMENT_SNAPPING; + 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); @@ -13699,6 +14357,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() @@ -13724,7 +14400,7 @@ static boolean checkDiagonalPushing(struct PlayerInfo *player, xx = jx + (dx == 0 ? real_dx : 0); yy = jy + (dy == 0 ? real_dy : 0); - return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Feld[xx][yy])); + return (!IN_LEV_FIELD(xx, yy) || IS_SOLID_FOR_PUSHING(Tile[xx][yy])); } /* @@ -13753,7 +14429,7 @@ static int DigField(struct PlayerInfo *player, dy == +1 ? MV_DOWN : MV_NONE); int opposite_direction = MV_DIR_OPPOSITE(move_direction); int dig_side = MV_DIR_OPPOSITE(move_direction); - int old_element = Feld[jx][jy]; + int old_element = Tile[jx][jy]; int element = MovingOrBlocked2ElementIfNotLeaving(x, y); int collect_count; @@ -13776,7 +14452,6 @@ static int DigField(struct PlayerInfo *player, return MP_NO_ACTION; } } - if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0)) old_element = Back[jx][jy]; @@ -13788,14 +14463,14 @@ static int DigField(struct PlayerInfo *player, if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction)) return MP_NO_ACTION; // field has no opening in this direction - if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction)) + if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction)) return MP_NO_ACTION; // field has no opening in this direction if (player_can_move && element == EL_ACID && move_direction == MV_DOWN) { SplashAcid(x, y); - Feld[jx][jy] = player->artwork_element; + Tile[jx][jy] = player->artwork_element; InitMovingField(jx, jy, MV_DOWN); Store[jx][jy] = EL_ACID; ContinueMoving(jx, jy); @@ -13833,7 +14508,7 @@ static int DigField(struct PlayerInfo *player, if (element == EL_DC_LANDMINE) Bang(x, y); - if (Feld[x][y] != element) // field changed by snapping + if (Tile[x][y] != element) // field changed by snapping return MP_ACTION; return MP_NO_ACTION; @@ -13978,18 +14653,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, 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)) @@ -14016,9 +14701,13 @@ static int DigField(struct PlayerInfo *player, } else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY) { - player->shield_normal_time_left += level.shield_normal_time; + int shield_time = (element == EL_SHIELD_DEADLY ? + level.shield_deadly_time : + level.shield_normal_time); + + player->shield_normal_time_left += shield_time; if (element == EL_SHIELD_DEADLY) - player->shield_deadly_time_left += level.shield_deadly_time; + player->shield_deadly_time_left += shield_time; } else if (element == EL_DYNAMITE || element == EL_EM_DYNAMITE || @@ -14057,7 +14746,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) { @@ -14101,19 +14793,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, 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)) @@ -14168,7 +14869,7 @@ static int DigField(struct PlayerInfo *player, if (!(IN_LEV_FIELD(nextx, nexty) && (IS_FREE(nextx, nexty) || (IS_SB_ELEMENT(element) && - Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) || + Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) || (IS_CUSTOM_ELEMENT(element) && CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, nextx, nexty))))) return MP_NO_ACTION; @@ -14209,7 +14910,7 @@ static int DigField(struct PlayerInfo *player, IncrementSokobanObjectsNeeded(); } - if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) + if (Tile[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY) { Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY; @@ -14221,7 +14922,7 @@ static int DigField(struct PlayerInfo *player, sokoban_task_solved = TRUE; } - Feld[x][y] = EL_SOKOBAN_OBJECT; + Tile[x][y] = EL_SOKOBAN_OBJECT; if (Back[x][y] == Back[nextx][nexty]) PlayLevelSoundAction(x, y, ACTION_PUSHING); @@ -14235,13 +14936,13 @@ static int DigField(struct PlayerInfo *player, if (sokoban_task_solved && game.sokoban_fields_still_needed == 0 && game.sokoban_objects_still_needed == 0 && - (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban)) + level.auto_exit_sokoban) { game.players_still_needed = 0; LevelSolved(); - PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING); + PlaySound(SND_GAME_SOKOBAN_SOLVING); } } else @@ -14290,7 +14991,7 @@ static int DigField(struct PlayerInfo *player, if (element == EL_ROBOT_WHEEL) { - Feld[x][y] = EL_ROBOT_WHEEL_ACTIVE; + Tile[x][y] = EL_ROBOT_WHEEL_ACTIVE; game.robot_wheel_x = x; game.robot_wheel_y = y; @@ -14304,13 +15005,13 @@ static int DigField(struct PlayerInfo *player, SCAN_PLAYFIELD(xx, yy) { - if (Feld[xx][yy] == EL_SP_DISK_YELLOW) + if (Tile[xx][yy] == EL_SP_DISK_YELLOW) { Bang(xx, yy); } - else if (Feld[xx][yy] == EL_SP_TERMINAL) + else if (Tile[xx][yy] == EL_SP_TERMINAL) { - Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE; + Tile[xx][yy] = EL_SP_TERMINAL_ACTIVE; ResetGfxAnimation(xx, yy); TEST_DrawLevelField(xx, yy); @@ -14326,7 +15027,7 @@ static int DigField(struct PlayerInfo *player, element == EL_DC_SWITCHGATE_SWITCH_UP || element == EL_DC_SWITCHGATE_SWITCH_DOWN) { - ToggleSwitchgateSwitch(x, y); + ToggleSwitchgateSwitch(); } else if (element == EL_LIGHT_SWITCH || element == EL_LIGHT_SWITCH_ACTIVE) @@ -14354,7 +15055,7 @@ static int DigField(struct PlayerInfo *player, } else if (element == EL_LAMP) { - Feld[x][y] = EL_LAMP_ACTIVE; + Tile[x][y] = EL_LAMP_ACTIVE; game.lights_still_needed--; ResetGfxAnimation(x, y); @@ -14362,12 +15063,12 @@ static int DigField(struct PlayerInfo *player, } else if (element == EL_TIME_ORB_FULL) { - Feld[x][y] = EL_TIME_ORB_EMPTY; + Tile[x][y] = EL_TIME_ORB_EMPTY; if (level.time > 0 || level.use_time_orb_bug) { TimeLeft += level.time_orb_time; - game.no_time_limit = FALSE; + game.no_level_time_limit = FALSE; game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; @@ -14386,7 +15087,7 @@ static int DigField(struct PlayerInfo *player, SCAN_PLAYFIELD(xx, yy) { - int e = Feld[xx][yy]; + int e = Tile[xx][yy]; if (game.ball_active) { @@ -14447,10 +15148,12 @@ static int DigField(struct PlayerInfo *player, if (is_player) // function can also be called by EL_PENGUIN { - if (Feld[x][y] != element) // really digged/collected something + if (Tile[x][y] != element) // really digged/collected something { player->is_collecting = !player->is_digging; player->is_active = TRUE; + + player->last_removed_element = element; } } @@ -14459,7 +15162,7 @@ static int DigField(struct PlayerInfo *player, static boolean DigFieldByCE(int x, int y, int digging_element) { - int element = Feld[x][y]; + int element = Tile[x][y]; if (!IS_FREE(x, y)) { @@ -14592,7 +15295,7 @@ static boolean DropElement(struct PlayerInfo *player) pressed without moving, dropped element must move away before the next element can be dropped (this is especially important if the next element is dynamite, which can be placed on background for historical reasons) */ - if (PLAYER_DROPPING(player, dropx, dropy) && Feld[dropx][dropy] != EL_EMPTY) + if (PLAYER_DROPPING(player, dropx, dropy) && Tile[dropx][dropy] != EL_EMPTY) return MP_ACTION; if (IS_THROWABLE(drop_element)) @@ -14604,7 +15307,7 @@ static boolean DropElement(struct PlayerInfo *player) return FALSE; } - old_element = Feld[dropx][dropy]; // old element at dropping position + old_element = Tile[dropx][dropy]; // old element at dropping position new_element = drop_element; // default: no change when dropping // check if player is active, not moving and ready to drop @@ -14653,11 +15356,11 @@ static boolean DropElement(struct PlayerInfo *player) new_element = EL_SP_DISK_RED_ACTIVE; } - Feld[dropx][dropy] = new_element; + Tile[dropx][dropy] = new_element; if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy))) DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy), - el2img(Feld[dropx][dropy]), 0); + el2img(Tile[dropx][dropy]), 0); PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING); @@ -14676,19 +15379,19 @@ static boolean DropElement(struct PlayerInfo *player) { player->dynabombs_left--; - Feld[dropx][dropy] = new_element; + Tile[dropx][dropy] = new_element; if (IN_SCR_FIELD(SCREENX(dropx), SCREENY(dropy))) DrawGraphicThruMask(SCREENX(dropx), SCREENY(dropy), - el2img(Feld[dropx][dropy]), 0); + el2img(Tile[dropx][dropy]), 0); PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING); } - if (Feld[dropx][dropy] == new_element) // uninitialized unless CE change + if (Tile[dropx][dropy] == new_element) // uninitialized unless CE change InitField_WithBug1(dropx, dropy, FALSE); - new_element = Feld[dropx][dropy]; // element might have changed + new_element = Tile[dropx][dropy]; // element might have changed if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) && element_info[new_element].move_pattern == MV_WHEN_DROPPED) @@ -14732,15 +15435,15 @@ void InitPlayLevelSound(void) loop_sound_volume = checked_calloc(num_sounds * sizeof(int)); } -static void PlayLevelSound(int x, int y, int nr) +static void PlayLevelSoundExt(int x, int y, int nr, boolean is_loop_sound) { int sx = SCREENX(x), sy = SCREENY(y); int volume, stereo_position; int max_distance = 8; - int type = (IS_LOOP_SOUND(nr) ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND); + int type = (is_loop_sound ? SND_CTRL_PLAY_LOOP : SND_CTRL_PLAY_SOUND); - if ((!setup.sound_simple && !IS_LOOP_SOUND(nr)) || - (!setup.sound_loops && IS_LOOP_SOUND(nr))) + if ((!setup.sound_simple && !is_loop_sound) || + (!setup.sound_loops && is_loop_sound)) return; if (!IN_LEV_FIELD(x, y) || @@ -14762,7 +15465,7 @@ static void PlayLevelSound(int x, int y, int nr) (sx + max_distance) * SOUND_MAX_LEFT2RIGHT / (SCR_FIELDX + 2 * max_distance)); - if (IS_LOOP_SOUND(nr)) + if (is_loop_sound) { /* This assures that quieter loop sounds do not overwrite louder ones, while restarting sound volume comparison with each new game frame. */ @@ -14777,6 +15480,11 @@ static void PlayLevelSound(int x, int y, int nr) PlaySoundExt(nr, volume, stereo_position, type); } +static void PlayLevelSound(int x, int y, int nr) +{ + PlayLevelSoundExt(x, y, nr, IS_LOOP_SOUND(nr)); +} + static void PlayLevelSoundNearest(int x, int y, int sound_action) { PlayLevelSound(x < LEVELX(BX1) ? LEVELX(BX1) : @@ -14788,7 +15496,7 @@ static void PlayLevelSoundNearest(int x, int y, int sound_action) static void PlayLevelSoundAction(int x, int y, int action) { - PlayLevelSoundElementAction(x, y, Feld[x][y], action); + PlayLevelSoundElementAction(x, y, Tile[x][y], action); } static void PlayLevelSoundElementAction(int x, int y, int element, int action) @@ -14810,7 +15518,7 @@ static void PlayLevelSoundElementActionIfLoop(int x, int y, int element, static void PlayLevelSoundActionIfLoop(int x, int y, int action) { - int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action]; + int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action]; if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect)) PlayLevelSound(x, y, sound_effect); @@ -14818,7 +15526,7 @@ static void PlayLevelSoundActionIfLoop(int x, int y, int action) static void StopLevelSoundActionIfLoop(int x, int y, int action) { - int sound_effect = element_info[SND_ELEMENT(Feld[x][y])].sound[action]; + int sound_effect = element_info[SND_ELEMENT(Tile[x][y])].sound[action]; if (sound_effect != SND_UNDEFINED && IS_LOOP_SOUND(sound_effect)) StopSound(sound_effect); @@ -14826,10 +15534,12 @@ static void StopLevelSoundActionIfLoop(int x, int y, int action) static int getLevelMusicNr(void) { + int level_pos = level_nr - leveldir_current->first_level; + if (levelset.music[level_nr] != MUS_UNDEFINED) return levelset.music[level_nr]; // from config file else - return MAP_NOCONF_MUSIC(level_nr); // from music dir + return MAP_NOCONF_MUSIC(level_pos); // from music dir } static void FadeLevelSounds(void) @@ -14863,6 +15573,338 @@ static void PlayLevelMusic(void) PlayMusicLoop(music_nr); } +static int getSoundAction_BD(int sample) +{ + switch (sample) + { + case GD_S_STONE_PUSHING: + case GD_S_MEGA_STONE_PUSHING: + case GD_S_FLYING_STONE_PUSHING: + case GD_S_WAITING_STONE_PUSHING: + case GD_S_CHASING_STONE_PUSHING: + case GD_S_NUT_PUSHING: + case GD_S_NITRO_PACK_PUSHING: + case GD_S_BLADDER_PUSHING: + case GD_S_BOX_PUSHING: + return ACTION_PUSHING; + + case GD_S_STONE_FALLING: + case GD_S_MEGA_STONE_FALLING: + case GD_S_FLYING_STONE_FALLING: + case GD_S_NUT_FALLING: + case GD_S_DIRT_BALL_FALLING: + case GD_S_DIRT_LOOSE_FALLING: + case GD_S_NITRO_PACK_FALLING: + case GD_S_FALLING_WALL_FALLING: + return ACTION_FALLING; + + case GD_S_STONE_IMPACT: + case GD_S_MEGA_STONE_IMPACT: + case GD_S_FLYING_STONE_IMPACT: + case GD_S_NUT_IMPACT: + case GD_S_DIRT_BALL_IMPACT: + case GD_S_DIRT_LOOSE_IMPACT: + case GD_S_NITRO_PACK_IMPACT: + case GD_S_FALLING_WALL_IMPACT: + return ACTION_IMPACT; + + case GD_S_NUT_CRACKING: + return ACTION_BREAKING; + + case GD_S_EXPANDING_WALL: + case GD_S_WALL_REAPPEARING: + case GD_S_SLIME: + case GD_S_LAVA: + case GD_S_ACID_SPREADING: + return ACTION_GROWING; + + case GD_S_DIAMOND_COLLECTING: + case GD_S_FLYING_DIAMOND_COLLECTING: + case GD_S_SKELETON_COLLECTING: + case GD_S_PNEUMATIC_COLLECTING: + case GD_S_BOMB_COLLECTING: + case GD_S_CLOCK_COLLECTING: + case GD_S_SWEET_COLLECTING: + case GD_S_KEY_COLLECTING: + case GD_S_DIAMOND_KEY_COLLECTING: + return ACTION_COLLECTING; + + case GD_S_BOMB_PLACING: + case GD_S_REPLICATOR: + return ACTION_DROPPING; + + case GD_S_BLADDER_MOVING: + return ACTION_MOVING; + + case GD_S_BLADDER_SPENDER: + case GD_S_BLADDER_CONVERTING: + case GD_S_GRAVITY_CHANGING: + return ACTION_CHANGING; + + case GD_S_BITER_EATING: + return ACTION_EATING; + + case GD_S_DOOR_OPENING: + case GD_S_CRACKING: + return ACTION_OPENING; + + case GD_S_DIRT_WALKING: + return ACTION_DIGGING; + + case GD_S_EMPTY_WALKING: + return ACTION_WALKING; + + case GD_S_SWITCH_BITER: + case GD_S_SWITCH_CREATURES: + case GD_S_SWITCH_GRAVITY: + case GD_S_SWITCH_EXPANDING: + case GD_S_SWITCH_CONVEYOR: + case GD_S_SWITCH_REPLICATOR: + case GD_S_STIRRING: + return ACTION_ACTIVATING; + + case GD_S_TELEPORTER: + return ACTION_PASSING; + + case GD_S_EXPLODING: + case GD_S_BOMB_EXPLODING: + case GD_S_GHOST_EXPLODING: + case GD_S_VOODOO_EXPLODING: + case GD_S_NITRO_PACK_EXPLODING: + return ACTION_EXPLODING; + + case GD_S_COVERING: + case GD_S_AMOEBA: + case GD_S_MAGIC_WALL: + case GD_S_PNEUMATIC_HAMMER: + case GD_S_WATER: + return ACTION_ACTIVE; + + case GD_S_DIAMOND_FALLING_RANDOM: + case GD_S_DIAMOND_FALLING_1: + case GD_S_DIAMOND_FALLING_2: + case GD_S_DIAMOND_FALLING_3: + case GD_S_DIAMOND_FALLING_4: + case GD_S_DIAMOND_FALLING_5: + case GD_S_DIAMOND_FALLING_6: + case GD_S_DIAMOND_FALLING_7: + case GD_S_DIAMOND_FALLING_8: + case GD_S_DIAMOND_IMPACT_RANDOM: + case GD_S_DIAMOND_IMPACT_1: + case GD_S_DIAMOND_IMPACT_2: + case GD_S_DIAMOND_IMPACT_3: + case GD_S_DIAMOND_IMPACT_4: + case GD_S_DIAMOND_IMPACT_5: + case GD_S_DIAMOND_IMPACT_6: + case GD_S_DIAMOND_IMPACT_7: + case GD_S_DIAMOND_IMPACT_8: + case GD_S_FLYING_DIAMOND_FALLING_RANDOM: + case GD_S_FLYING_DIAMOND_FALLING_1: + case GD_S_FLYING_DIAMOND_FALLING_2: + case GD_S_FLYING_DIAMOND_FALLING_3: + case GD_S_FLYING_DIAMOND_FALLING_4: + case GD_S_FLYING_DIAMOND_FALLING_5: + case GD_S_FLYING_DIAMOND_FALLING_6: + case GD_S_FLYING_DIAMOND_FALLING_7: + case GD_S_FLYING_DIAMOND_FALLING_8: + case GD_S_FLYING_DIAMOND_IMPACT_RANDOM: + case GD_S_FLYING_DIAMOND_IMPACT_1: + case GD_S_FLYING_DIAMOND_IMPACT_2: + case GD_S_FLYING_DIAMOND_IMPACT_3: + case GD_S_FLYING_DIAMOND_IMPACT_4: + case GD_S_FLYING_DIAMOND_IMPACT_5: + case GD_S_FLYING_DIAMOND_IMPACT_6: + case GD_S_FLYING_DIAMOND_IMPACT_7: + case GD_S_FLYING_DIAMOND_IMPACT_8: + case GD_S_TIMEOUT_0: + case GD_S_TIMEOUT_1: + case GD_S_TIMEOUT_2: + case GD_S_TIMEOUT_3: + case GD_S_TIMEOUT_4: + case GD_S_TIMEOUT_5: + case GD_S_TIMEOUT_6: + case GD_S_TIMEOUT_7: + case GD_S_TIMEOUT_8: + case GD_S_TIMEOUT_9: + case GD_S_TIMEOUT_10: + case GD_S_BONUS_LIFE: + // trigger special post-processing (and force sound to be non-looping) + return ACTION_OTHER; + + case GD_S_AMOEBA_MAGIC: + case GD_S_FINISHED: + // trigger special post-processing (and force sound to be looping) + return ACTION_DEFAULT; + + default: + return ACTION_DEFAULT; + } +} + +static int getSoundEffect_BD(int element_bd, int sample) +{ + int sound_action = getSoundAction_BD(sample); + int sound_effect = element_info[SND_ELEMENT(element_bd)].sound[sound_action]; + int nr; + + // standard sounds + if (sound_action != ACTION_OTHER && + sound_action != ACTION_DEFAULT) + return sound_effect; + + // special post-processing for some sounds + switch (sample) + { + case GD_S_DIAMOND_FALLING_RANDOM: + case GD_S_DIAMOND_FALLING_1: + case GD_S_DIAMOND_FALLING_2: + case GD_S_DIAMOND_FALLING_3: + case GD_S_DIAMOND_FALLING_4: + case GD_S_DIAMOND_FALLING_5: + case GD_S_DIAMOND_FALLING_6: + case GD_S_DIAMOND_FALLING_7: + case GD_S_DIAMOND_FALLING_8: + nr = (sample == GD_S_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) : + sample - GD_S_DIAMOND_FALLING_1); + sound_effect = SND_BD_DIAMOND_FALLING_RANDOM_1 + nr; + + if (getSoundInfoEntryFilename(sound_effect) == NULL) + sound_effect = SND_BD_DIAMOND_FALLING; + break; + + case GD_S_DIAMOND_IMPACT_RANDOM: + case GD_S_DIAMOND_IMPACT_1: + case GD_S_DIAMOND_IMPACT_2: + case GD_S_DIAMOND_IMPACT_3: + case GD_S_DIAMOND_IMPACT_4: + case GD_S_DIAMOND_IMPACT_5: + case GD_S_DIAMOND_IMPACT_6: + case GD_S_DIAMOND_IMPACT_7: + case GD_S_DIAMOND_IMPACT_8: + nr = (sample == GD_S_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) : + sample - GD_S_DIAMOND_IMPACT_1); + sound_effect = SND_BD_DIAMOND_IMPACT_RANDOM_1 + nr; + + if (getSoundInfoEntryFilename(sound_effect) == NULL) + sound_effect = SND_BD_DIAMOND_IMPACT; + break; + + case GD_S_FLYING_DIAMOND_FALLING_RANDOM: + case GD_S_FLYING_DIAMOND_FALLING_1: + case GD_S_FLYING_DIAMOND_FALLING_2: + case GD_S_FLYING_DIAMOND_FALLING_3: + case GD_S_FLYING_DIAMOND_FALLING_4: + case GD_S_FLYING_DIAMOND_FALLING_5: + case GD_S_FLYING_DIAMOND_FALLING_6: + case GD_S_FLYING_DIAMOND_FALLING_7: + case GD_S_FLYING_DIAMOND_FALLING_8: + nr = (sample == GD_S_FLYING_DIAMOND_FALLING_RANDOM ? GetSimpleRandom(8) : + sample - GD_S_FLYING_DIAMOND_FALLING_1); + sound_effect = SND_BD_FLYING_DIAMOND_FALLING_RANDOM_1 + nr; + + if (getSoundInfoEntryFilename(sound_effect) == NULL) + sound_effect = SND_BD_FLYING_DIAMOND_FALLING; + break; + + case GD_S_FLYING_DIAMOND_IMPACT_RANDOM: + case GD_S_FLYING_DIAMOND_IMPACT_1: + case GD_S_FLYING_DIAMOND_IMPACT_2: + case GD_S_FLYING_DIAMOND_IMPACT_3: + case GD_S_FLYING_DIAMOND_IMPACT_4: + case GD_S_FLYING_DIAMOND_IMPACT_5: + case GD_S_FLYING_DIAMOND_IMPACT_6: + case GD_S_FLYING_DIAMOND_IMPACT_7: + case GD_S_FLYING_DIAMOND_IMPACT_8: + nr = (sample == GD_S_FLYING_DIAMOND_IMPACT_RANDOM ? GetSimpleRandom(8) : + sample - GD_S_FLYING_DIAMOND_IMPACT_1); + sound_effect = SND_BD_FLYING_DIAMOND_IMPACT_RANDOM_1 + nr; + + if (getSoundInfoEntryFilename(sound_effect) == NULL) + sound_effect = SND_BD_FLYING_DIAMOND_IMPACT; + break; + + case GD_S_TIMEOUT_0: + case GD_S_TIMEOUT_1: + case GD_S_TIMEOUT_2: + case GD_S_TIMEOUT_3: + case GD_S_TIMEOUT_4: + case GD_S_TIMEOUT_5: + case GD_S_TIMEOUT_6: + case GD_S_TIMEOUT_7: + case GD_S_TIMEOUT_8: + case GD_S_TIMEOUT_9: + case GD_S_TIMEOUT_10: + nr = sample - GD_S_TIMEOUT_0; + sound_effect = SND_GAME_RUNNING_OUT_OF_TIME_0 + nr; + + if (getSoundInfoEntryFilename(sound_effect) == NULL && sample != GD_S_TIMEOUT_0) + sound_effect = SND_GAME_RUNNING_OUT_OF_TIME; + break; + + case GD_S_BONUS_LIFE: + sound_effect = SND_GAME_HEALTH_BONUS; + break; + + case GD_S_AMOEBA_MAGIC: + sound_effect = SND_BD_AMOEBA_OTHER; + break; + + case GD_S_FINISHED: + sound_effect = SND_GAME_LEVELTIME_BONUS; + break; + + default: + sound_effect = SND_UNDEFINED; + break; + } + + return sound_effect; +} + +void PlayLevelSound_BD(int xx, int yy, int element_bd, int sample) +{ + int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0); + int sound_effect = getSoundEffect_BD(element, sample); + int sound_action = getSoundAction_BD(sample); + boolean is_loop_sound = IS_LOOP_SOUND(sound_effect); + int offset = 0; + int x = xx - offset; + int y = yy - offset; + + // some sound actions are always looping in native BD game engine + if (sound_action == ACTION_DEFAULT) + is_loop_sound = TRUE; + + // some sound actions are always non-looping in native BD game engine + if (sound_action == ACTION_FALLING || + sound_action == ACTION_MOVING || + sound_action == ACTION_OTHER) + is_loop_sound = FALSE; + + if (sound_effect != SND_UNDEFINED) + PlayLevelSoundExt(x, y, sound_effect, is_loop_sound); +} + +void StopSound_BD(int element_bd, int sample) +{ + int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0); + int sound_effect = getSoundEffect_BD(element, sample); + + if (sound_effect != SND_UNDEFINED) + StopSound(sound_effect); +} + +boolean isSoundPlaying_BD(int element_bd, int sample) +{ + int element = (element_bd > -1 ? map_element_BD_to_RND(element_bd) : 0); + int sound_effect = getSoundEffect_BD(element, sample); + + if (sound_effect != SND_UNDEFINED) + return isSoundPlaying(sound_effect); + + return FALSE; +} + void PlayLevelSound_EM(int xx, int yy, int element_em, int sample) { int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0); @@ -15171,19 +16213,26 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message) { if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED)) { - // closing door required in case of envelope style request dialogs - if (!skip_request) + if (!quick_quit) { // prevent short reactivation of overlay buttons while closing door SetOverlayActive(FALSE); + UnmapGameButtons(); - CloseDoor(DOOR_CLOSE_1); + // door may still be open due to skipped or envelope style request + CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1); } if (network.enabled) + { SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER); + } else { + // when using BD game engine, cover screen before fading out + if (!quick_quit && level.game_engine_type == GAME_ENGINE_TYPE_BD) + game_bd.cover_screen = TRUE; + if (quick_quit) FadeSkipNextFadeIn(); @@ -15204,70 +16253,138 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message) } } -void RequestQuitGame(boolean ask_if_really_quit) +void RequestQuitGame(boolean escape_key_pressed) { - boolean quick_quit = (!ask_if_really_quit || level_editor_test_game); - boolean skip_request = game.all_players_gone || quick_quit; + boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game); + boolean quick_quit = ((escape_key_pressed && !ask_on_escape) || + level_editor_test_game); + boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game || + quick_quit || score_info_tape_play); RequestQuitGameExt(skip_request, quick_quit, "Do you really want to quit the game?"); } -void RequestRestartGame(char *message) +static char *getRestartGameMessage(void) { - game.restart_game_message = NULL; + boolean play_again = hasStartedNetworkGame(); + static char message[MAX_OUTPUT_LINESIZE]; + char *game_over_text = "Game over!"; + char *play_again_text = " Play it again?"; + + if (level.game_engine_type == GAME_ENGINE_TYPE_MM && + game_mm.game_over_message != NULL) + game_over_text = game_mm.game_over_message; + + snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text, + (play_again ? play_again_text : "")); + + return message; +} +static void RequestRestartGame(void) +{ + char *message = getRestartGameMessage(); boolean has_started_game = hasStartedNetworkGame(); int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM); + int door_state = DOOR_CLOSE_1; + + boolean restart_wanted = (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game); - if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game) + // if no restart wanted, continue with next level for BD style intermission levels + if (!restart_wanted && !level_editor_test_game && level.bd_intermission) { + boolean success = AdvanceToNextLevel(); + + restart_wanted = (success && setup.auto_play_next_level); + } + + if (restart_wanted) + { + CloseDoor(door_state); + StartGameActions(network.enabled, setup.autorecord, level.random_seed); } else { + // if game was invoked from level editor, also close tape recorder door + if (level_editor_test_game) + door_state = DOOR_CLOSE_ALL; + + CloseDoor(door_state); + SetGameStatus(GAME_MODE_MAIN); DrawMainMenu(); } } -void CheckGameOver(void) +boolean CheckRestartGame(void) { - static boolean last_game_over = FALSE; static int game_over_delay = 0; int game_over_delay_value = 50; boolean game_over = checkGameFailed(); - // do not handle game over if request dialog is already active - if (game.request_active) - return; - - // do not ask to play again if game was never actually played - if (!game.GamePlayed) - return; - if (!game_over) { - last_game_over = FALSE; game_over_delay = game_over_delay_value; - return; + return FALSE; } if (game_over_delay > 0) { + if (game_over_delay == game_over_delay_value / 2) + PlaySound(SND_GAME_LOSING); + game_over_delay--; - return; + return FALSE; } - if (last_game_over != game_over) - game.restart_game_message = (hasStartedNetworkGame() ? - "Game over! Play it again?" : - "Game over!"); + // do not ask to play again if request dialog is already active + if (game.request_active) + return FALSE; + + // do not ask to play again if request dialog already handled + if (game.RestartGameRequested) + return FALSE; + + // do not ask to play again if game was never actually played + if (!game.GamePlayed) + return FALSE; + + // do not ask to play again if this was disabled in setup menu + if (!setup.ask_on_game_over) + return FALSE; + + game.RestartGameRequested = TRUE; + + RequestRestartGame(); + + return TRUE; +} + +boolean checkGameRunning(void) +{ + if (game_status != GAME_MODE_PLAYING) + return FALSE; + + if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGameRunning_BD()) + return FALSE; + + return TRUE; +} + +boolean checkGamePlaying(void) +{ + if (game_status != GAME_MODE_PLAYING) + return FALSE; + + if (level.game_engine_type == GAME_ENGINE_TYPE_BD && !checkGamePlaying_BD()) + return FALSE; - last_game_over = game_over; + return TRUE; } boolean checkGameSolved(void) @@ -15278,7 +16395,9 @@ boolean checkGameSolved(void) boolean checkGameFailed(void) { - if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + if (level.game_engine_type == GAME_ENGINE_TYPE_BD) + return (game_bd.game_over && !game_bd.level_solved); + else if (level.game_engine_type == GAME_ENGINE_TYPE_EM) return (game_em.game_over && !game_em.level_solved); else if (level.game_engine_type == GAME_ENGINE_TYPE_SP) return (game_sp.game_over && !game_sp.level_solved); @@ -15417,10 +16536,11 @@ static void LoadEngineSnapshotValues_RND(void) if (game.num_random_calls != num_random_calls) { - Error(ERR_INFO, "number of random calls out of sync"); - Error(ERR_INFO, "number of random calls should be %d", num_random_calls); - Error(ERR_INFO, "number of random calls is %d", game.num_random_calls); - Error(ERR_EXIT, "this should not happen -- please debug"); + Error("number of random calls out of sync"); + Error("number of random calls should be %d", num_random_calls); + Error("number of random calls is %d", game.num_random_calls); + + Fail("this should not happen -- please debug"); } } @@ -15450,7 +16570,7 @@ static ListNode *SaveEngineSnapshotBuffers(void) if (level.game_engine_type == GAME_ENGINE_TYPE_SP) SaveEngineSnapshotValues_SP(&buffers); if (level.game_engine_type == GAME_ENGINE_TYPE_MM) - SaveEngineSnapshotValues_MM(&buffers); + SaveEngineSnapshotValues_MM(); // save values stored in special snapshot structure @@ -15473,6 +16593,7 @@ static ListNode *SaveEngineSnapshotBuffers(void) SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTimeFrames)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir)); @@ -15484,7 +16605,7 @@ static ListNode *SaveEngineSnapshotBuffers(void) SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2)); - SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Tile)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay)); @@ -15515,6 +16636,7 @@ static ListNode *SaveEngineSnapshotBuffers(void) SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir)); @@ -15533,7 +16655,8 @@ static ListNode *SaveEngineSnapshotBuffers(void) node = node->next; } - printf("::: size of engine snapshot: %d bytes\n", num_bytes); + Debug("game:playing:SaveEngineSnapshotBuffers", + "size of engine snapshot: %d bytes", num_bytes); #endif return buffers; @@ -15686,6 +16809,11 @@ static struct GAME_CTRL_ID_LOAD, NULL, TRUE, FALSE, "load game" }, + { + IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart, + GAME_CTRL_ID_RESTART, NULL, + TRUE, FALSE, "restart game" + }, { IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop, GAME_CTRL_ID_PANEL_STOP, NULL, @@ -15701,6 +16829,11 @@ static struct GAME_CTRL_ID_PANEL_PLAY, NULL, FALSE, FALSE, "play game" }, + { + IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart, + GAME_CTRL_ID_PANEL_RESTART, NULL, + FALSE, FALSE, "restart game" + }, { IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop, GAME_CTRL_ID_TOUCH_STOP, NULL, @@ -15711,6 +16844,11 @@ static struct GAME_CTRL_ID_TOUCH_PAUSE, NULL, FALSE, TRUE, "pause game" }, + { + IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart, + GAME_CTRL_ID_TOUCH_RESTART, NULL, + FALSE, TRUE, "restart game" + }, { IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music, SOUND_CTRL_ID_MUSIC, &setup.sound_music, @@ -15773,6 +16911,10 @@ void CreateGameButtons(void) int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y)); int id = i; + // do not use touch buttons if overlay touch buttons are disabled + if (is_touch_button && !setup.touch.overlay_buttons) + continue; + if (gfx->bitmap == NULL) { game_gadget[id] = NULL; @@ -15786,7 +16928,10 @@ void CreateGameButtons(void) id == GAME_CTRL_ID_PLAY || id == GAME_CTRL_ID_PANEL_PLAY || id == GAME_CTRL_ID_SAVE || - id == GAME_CTRL_ID_LOAD) + id == GAME_CTRL_ID_LOAD || + id == GAME_CTRL_ID_RESTART || + id == GAME_CTRL_ID_PANEL_RESTART || + id == GAME_CTRL_ID_TOUCH_RESTART) { button_type = GD_TYPE_NORMAL_BUTTON; checked = FALSE; @@ -15828,7 +16973,7 @@ void CreateGameButtons(void) GDI_END); if (gi == NULL) - Error(ERR_EXIT, "cannot create gadget"); + Fail("cannot create gadget"); game_gadget[id] = gi; } @@ -15855,12 +17000,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); @@ -15873,17 +17024,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) @@ -15895,15 +17042,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[] = @@ -15916,6 +17054,10 @@ void ModifyPauseButtons(void) }; int i; + // do not redraw pause button on closed door (may happen when restarting game) + if (!(GetDoorState() & DOOR_OPEN_1)) + return; + for (i = 0; ids[i] > -1; i++) ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END); } @@ -15925,10 +17067,15 @@ static void MapGameButtonsExt(boolean on_tape) int i; for (i = 0; i < NUM_GAME_BUTTONS; i++) - if ((!on_tape || gamebutton_info[i].allowed_on_tape) && - i != GAME_CTRL_ID_UNDO && - i != GAME_CTRL_ID_REDO) + { + if ((i == GAME_CTRL_ID_UNDO || + i == GAME_CTRL_ID_REDO) && + game_status != GAME_MODE_PLAYING) + continue; + + if (!on_tape || gamebutton_info[i].allowed_on_tape) MapGadget(game_gadget[i]); + } UnmapGameButtonsAtSamePosition_All(); @@ -16019,6 +17166,8 @@ static void GameUndoRedoExt(void) DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter); DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0); + ModifyPauseButtons(); + BackToFront(); } @@ -16027,8 +17176,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(); } @@ -16037,8 +17190,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(); } @@ -16058,13 +17215,7 @@ static void HandleGameButtonsExt(int id, int button) case GAME_CTRL_ID_STOP: case GAME_CTRL_ID_PANEL_STOP: case GAME_CTRL_ID_TOUCH_STOP: - if (game_status == GAME_MODE_MAIN) - break; - - if (tape.playing) - TapeStop(); - else - RequestQuitGame(TRUE); + TapeStopGame(); break; @@ -16128,6 +17279,13 @@ static void HandleGameButtonsExt(int id, int button) TapeQuickLoad(); break; + case GAME_CTRL_ID_RESTART: + case GAME_CTRL_ID_PANEL_RESTART: + case GAME_CTRL_ID_TOUCH_RESTART: + TapeRestartGame(); + + break; + case SOUND_CTRL_ID_MUSIC: case SOUND_CTRL_ID_PANEL_MUSIC: if (setup.sound_music)