X-Git-Url: https://git.artsoft.org/?p=rocksndiamonds.git;a=blobdiff_plain;f=src%2Fgame.c;h=593a8bac409ac4b4a1f45cf0fc957ffe0d49ec56;hp=10a8a98e6a9ed52d484f148648fd645bbc1c7426;hb=884012f34f9f3c22bf75d1468e4ed57d7795a24b;hpb=c46bcfd8dd92fbf3ce134eed1e64078147d33b50 diff --git a/src/game.c b/src/game.c index 10a8a98e..593a8bac 100644 --- a/src/game.c +++ b/src/game.c @@ -1,7 +1,7 @@ /*********************************************************** * Rocks'n'Diamonds -- McDuffin Strikes Back! * *----------------------------------------------------------* -* (c) 1995-2002 Artsoft Entertainment * +* (c) 1995-2006 Artsoft Entertainment * * Holger Schemel * * Detmolder Strasse 189 * * 33604 Bielefeld * @@ -40,9 +40,28 @@ #define USE_NEW_SPRING_BUMPER (USE_NEW_STUFF * 1) #define USE_STOP_CHANGED_ELEMENTS (USE_NEW_STUFF * 1) #define USE_ELEMENT_TOUCHING_BUGFIX (USE_NEW_STUFF * 1) +#define USE_NEW_CONTINUOUS_SNAPPING (USE_NEW_STUFF * 1) +#define USE_GFX_RESET_GFX_ANIMATION (USE_NEW_STUFF * 1) +#define USE_BOTH_SWITCHGATE_SWITCHES (USE_NEW_STUFF * 1) +#define USE_PLAYER_GRAVITY (USE_NEW_STUFF * 1) +#define USE_FIXED_BORDER_RUNNING_GFX (USE_NEW_STUFF * 1) +#define USE_QUICKSAND_BD_ROCK_BUGFIX (USE_NEW_STUFF * 0) #define USE_QUICKSAND_IMPACT_BUGFIX (USE_NEW_STUFF * 0) +#define USE_CODE_THAT_BREAKS_SNAKE_BITE (USE_NEW_STUFF * 1) + +#define USE_UFAST_PLAYER_EXIT_BUGFIX (USE_NEW_STUFF * 1) + +#define USE_GFX_RESET_ONLY_WHEN_MOVING (USE_NEW_STUFF * 1) +#define USE_GFX_RESET_PLAYER_ARTWORK (USE_NEW_STUFF * 1) + +#define USE_FIX_KILLED_BY_NON_WALKABLE (USE_NEW_STUFF * 1) +#define USE_FIX_IMPACT_COLLISION (USE_NEW_STUFF * 1) + +#define USE_GFX_RESET_WHEN_NOT_MOVING (USE_NEW_STUFF * 1) + + /* for DigField() */ #define DF_NO_PUSH 0 #define DF_DIG 1 @@ -68,22 +87,32 @@ #define EX_TYPE_DYNA (1 << 4) #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER) +#define PANEL_OFF() (local_player->LevelSolved_PanelOff) +#define PANEL_DEACTIVATED(p) ((p)->x < 0 || (p)->y < 0 || PANEL_OFF()) +#define PANEL_XPOS(p) (DX + ALIGNED_TEXT_XPOS(p)) +#define PANEL_YPOS(p) (DY + ALIGNED_TEXT_YPOS(p)) + /* special positions in the game control window (relative to control window) */ -#define XX_LEVEL 37 -#define YY_LEVEL 20 -#define XX_EMERALDS 29 -#define YY_EMERALDS 54 -#define XX_DYNAMITE 29 -#define YY_DYNAMITE 89 -#define XX_KEYS 18 -#define YY_KEYS 123 -#define XX_SCORE 15 -#define YY_SCORE 159 -#define XX_TIME1 29 -#define XX_TIME2 30 -#define YY_TIME 194 +#define XX_LEVEL1 (PANEL_XPOS(game.panel.level)) +#define XX_LEVEL2 (PANEL_XPOS(game.panel.level) - 1) +#define XX_LEVEL (PANEL_XPOS(game.panel.level)) +#define YY_LEVEL (PANEL_YPOS(game.panel.level)) +#define XX_EMERALDS (PANEL_XPOS(game.panel.gems)) +#define YY_EMERALDS (PANEL_YPOS(game.panel.gems)) +#define XX_DYNAMITE (PANEL_XPOS(game.panel.inventory)) +#define YY_DYNAMITE (PANEL_YPOS(game.panel.inventory)) +#define XX_KEYS (PANEL_XPOS(game.panel.keys)) +#define YY_KEYS (PANEL_YPOS(game.panel.keys)) +#define XX_SCORE (PANEL_XPOS(game.panel.score)) +#define YY_SCORE (PANEL_YPOS(game.panel.score)) +#define XX_TIME1 (PANEL_XPOS(game.panel.time)) +#define XX_TIME2 (PANEL_XPOS(game.panel.time) + 1) +#define XX_TIME (PANEL_XPOS(game.panel.time)) +#define YY_TIME (PANEL_YPOS(game.panel.time)) /* special positions in the game control window (relative to main window) */ +#define DX_LEVEL1 (DX + XX_LEVEL1) +#define DX_LEVEL2 (DX + XX_LEVEL2) #define DX_LEVEL (DX + XX_LEVEL) #define DY_LEVEL (DY + YY_LEVEL) #define DX_EMERALDS (DX + XX_EMERALDS) @@ -96,8 +125,465 @@ #define DY_SCORE (DY + YY_SCORE) #define DX_TIME1 (DX + XX_TIME1) #define DX_TIME2 (DX + XX_TIME2) +#define DX_TIME (DX + XX_TIME) #define DY_TIME (DY + YY_TIME) +#if 0 +/* game panel display and control definitions */ + +#define GAME_CONTROL_LEVEL 0 +#define GAME_CONTROL_GEMS 1 +#define GAME_CONTROL_INVENTORY 2 +#define GAME_CONTROL_KEY_1 3 +#define GAME_CONTROL_KEY_2 4 +#define GAME_CONTROL_KEY_3 5 +#define GAME_CONTROL_KEY_4 6 +#define GAME_CONTROL_KEY_5 7 +#define GAME_CONTROL_KEY_6 8 +#define GAME_CONTROL_KEY_7 9 +#define GAME_CONTROL_KEY_8 10 +#define GAME_CONTROL_KEY_WHITE 11 +#define GAME_CONTROL_KEY_WHITE_COUNT 12 +#define GAME_CONTROL_SCORE 13 +#define GAME_CONTROL_TIME 14 +#define GAME_CONTROL_TIME_HH 15 +#define GAME_CONTROL_TIME_MM 16 +#define GAME_CONTROL_TIME_SS 17 +#define GAME_CONTROL_DROP_NEXT_1 18 +#define GAME_CONTROL_DROP_NEXT_2 19 +#define GAME_CONTROL_DROP_NEXT_3 20 +#define GAME_CONTROL_DROP_NEXT_4 21 +#define GAME_CONTROL_DROP_NEXT_5 22 +#define GAME_CONTROL_DROP_NEXT_6 23 +#define GAME_CONTROL_DROP_NEXT_7 24 +#define GAME_CONTROL_DROP_NEXT_8 25 +#define GAME_CONTROL_SHIELD_NORMAL 26 +#define GAME_CONTROL_SHIELD_NORMAL_TIME 27 +#define GAME_CONTROL_SHIELD_DEADLY 28 +#define GAME_CONTROL_SHIELD_DEADLY_TIME 29 +#define GAME_CONTROL_EXIT 30 +#define GAME_CONTROL_EM_EXIT 31 +#define GAME_CONTROL_SP_EXIT 32 +#define GAME_CONTROL_STEEL_EXIT 33 +#define GAME_CONTROL_EM_STEEL_EXIT 34 +#define GAME_CONTROL_EMC_MAGIC_BALL 35 +#define GAME_CONTROL_EMC_MAGIC_BALL_TIME 36 +#define GAME_CONTROL_LIGHT_SWITCH 37 +#define GAME_CONTROL_LIGHT_SWITCH_TIME 38 +#define GAME_CONTROL_TIMEGATE_SWITCH 39 +#define GAME_CONTROL_TIMEGATE_SWITCH_TIME 40 +#define GAME_CONTROL_SWITCHGATE_SWITCH 41 +#define GAME_CONTROL_EMC_LENSES 42 +#define GAME_CONTROL_EMC_LENSES_TIME 43 +#define GAME_CONTROL_EMC_MAGNIFIER 44 +#define GAME_CONTROL_EMC_MAGNIFIER_TIME 45 +#define GAME_CONTROL_BALLOON_SWITCH 46 +#define GAME_CONTROL_DYNABOMB_NUMBER 47 +#define GAME_CONTROL_DYNABOMB_SIZE 48 +#define GAME_CONTROL_DYNABOMB_POWER 49 +#define GAME_CONTROL_PENGUINS 50 +#define GAME_CONTROL_SOKOBAN_OBJECTS 51 +#define GAME_CONTROL_SOKOBAN_FIELDS 52 +#define GAME_CONTROL_ROBOT_WHEEL 53 +#define GAME_CONTROL_CONVEYOR_BELT_1 54 +#define GAME_CONTROL_CONVEYOR_BELT_1_SWITCH 55 +#define GAME_CONTROL_CONVEYOR_BELT_2 56 +#define GAME_CONTROL_CONVEYOR_BELT_2_SWITCH 57 +#define GAME_CONTROL_CONVEYOR_BELT_3 58 +#define GAME_CONTROL_CONVEYOR_BELT_3_SWITCH 59 +#define GAME_CONTROL_CONVEYOR_BELT_4 60 +#define GAME_CONTROL_CONVEYOR_BELT_4_SWITCH 61 +#define GAME_CONTROL_MAGIC_WALL 62 +#define GAME_CONTROL_MAGIC_WALL_TIME 63 +#define GAME_CONTROL_BD_MAGIC_WALL 64 +#define GAME_CONTROL_DC_MAGIC_WALL 65 +#define GAME_CONTROL_PLAYER_NAME 66 +#define GAME_CONTROL_LEVEL_NAME 67 +#define GAME_CONTROL_LEVEL_AUTHOR 68 + +struct GameControlInfo +{ + int nr; + + struct TextPosInfo *pos_text; + int type; + void *ptr; +}; + +static struct GameControlInfo game_controls[] = +{ + { + GAME_CONTROL_LEVEL, + &game.panel.level, + TYPE_INTEGER, + }, + { + GAME_CONTROL_GEMS, + &game.panel.gems, + TYPE_INTEGER, + }, + { + GAME_CONTROL_INVENTORY, + &game.panel.inventory, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEYS, + &game.panel.keys, + TYPE_INTEGER, + }, + { + GAME_CONTROL_SCORE, + &game.panel.score, + TYPE_INTEGER, + }, + { + GAME_CONTROL_TIME, + &game.panel.time, + TYPE_INTEGER, + }, + { + GAME_CONTROL_TIME_HH, + &game.panel.time_hh, + TYPE_INTEGER, + }, + { + GAME_CONTROL_TIME_MM, + &game.panel.time_mm, + TYPE_INTEGER, + }, + { + GAME_CONTROL_TIME_SS, + &game.panel.time_ss, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DROP_NEXT_1, + &game.panel.drop_next_1, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DROP_NEXT_2, + &game.panel.drop_next_2, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DROP_NEXT_3, + &game.panel.drop_next_3, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DROP_NEXT_4, + &game.panel.drop_next_4, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DROP_NEXT_5, + &game.panel.drop_next_5, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DROP_NEXT_6, + &game.panel.drop_next_6, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DROP_NEXT_7, + &game.panel.drop_next_7, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DROP_NEXT_8, + &game.panel.drop_next_8, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EMC_KEYS, + &game.panel.emc_keys, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_1, + &game.panel.key_1, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_2, + &game.panel.key_2, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_3, + &game.panel.key_3, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_4, + &game.panel.key_4, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_5, + &game.panel.key_5, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_6, + &game.panel.key_6, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_7, + &game.panel.key_7, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_8, + &game.panel.key_8, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_WHITE, + &game.panel.key_white, + TYPE_INTEGER, + }, + { + GAME_CONTROL_KEY_WHITE_COUNT, + &game.panel.key_white_count, + TYPE_INTEGER, + }, + { + GAME_CONTROL_SHIELD_NORMAL, + &game.panel.shield_normal, + TYPE_INTEGER, + }, + { + GAME_CONTROL_SHIELD_NORMAL_TIME, + &game.panel.shield_normal_time, + TYPE_INTEGER, + }, + { + GAME_CONTROL_SHIELD_DEADLY, + &game.panel.shield_deadly, + TYPE_INTEGER, + }, + { + GAME_CONTROL_SHIELD_DEADLY_TIME, + &game.panel.shield_deadly_time, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EXIT, + &game.panel.exit, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EM_EXIT, + &game.panel.em_exit, + TYPE_INTEGER, + }, + { + GAME_CONTROL_SP_EXIT, + &game.panel.sp_exit, + TYPE_INTEGER, + }, + { + GAME_CONTROL_STEEL_EXIT, + &game.panel.steel_exit, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EM_STEEL_EXIT, + &game.panel.em_steel_exit, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EMC_MAGIC_BALL, + &game.panel.emc_magic_ball, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EMC_MAGIC_BALL_TIME, + &game.panel.emc_magic_ball_time, + TYPE_INTEGER, + }, + { + GAME_CONTROL_LIGHT_SWITCH, + &game.panel.light_switch, + TYPE_INTEGER, + }, + { + GAME_CONTROL_LIGHT_SWITCH_TIME, + &game.panel.light_switch_time, + TYPE_INTEGER, + }, + { + GAME_CONTROL_TIMEGATE_SWITCH, + &game.panel.timegate_switch, + TYPE_INTEGER, + }, + { + GAME_CONTROL_TIMEGATE_SWITCH_TIME, + &game.panel.timegate_switch_time, + TYPE_INTEGER, + }, + { + GAME_CONTROL_SWITCHGATE_SWITCH, + &game.panel.switchgate_switch, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EMC_LENSES, + &game.panel.emc_lenses, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EMC_LENSES_TIME, + &game.panel.emc_lenses_time, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EMC_MAGNIFIER, + &game.panel.emc_magnifier, + TYPE_INTEGER, + }, + { + GAME_CONTROL_EMC_MAGNIFIER_TIME, + &game.panel.emc_magnifier_time, + TYPE_INTEGER, + }, + { + GAME_CONTROL_BALLOON_SWITCH, + &game.panel.balloon_switch, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DYNABOMB_NUMBER, + &game.panel.dynabomb_number, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DYNABOMB_SIZE, + &game.panel.dynabomb_size, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DYNABOMB_POWER, + &game.panel.dynabomb_power, + TYPE_INTEGER, + }, + { + GAME_CONTROL_PENGUINS, + &game.panel.penguins, + TYPE_INTEGER, + }, + { + GAME_CONTROL_SOKOBAN_OBJECTS, + &game.panel.sokoban_objects, + TYPE_INTEGER, + }, + { + GAME_CONTROL_SOKOBAN_FIELDS, + &game.panel.sokoban_fields, + TYPE_INTEGER, + }, + { + GAME_CONTROL_ROBOT_WHEEL, + &game.panel.robot_wheel, + TYPE_INTEGER, + }, + { + GAME_CONTROL_CONVEYOR_BELT_1, + &game.panel.conveyor_belt_1, + TYPE_INTEGER, + }, + { + GAME_CONTROL_CONVEYOR_BELT_1_SWITCH, + &game.panel.conveyor_belt_1_switch, + TYPE_INTEGER, + }, + { + GAME_CONTROL_CONVEYOR_BELT_2, + &game.panel.conveyor_belt_2, + TYPE_INTEGER, + }, + { + GAME_CONTROL_CONVEYOR_BELT_2_SWITCH, + &game.panel.conveyor_belt_2_switch, + TYPE_INTEGER, + }, + { + GAME_CONTROL_CONVEYOR_BELT_3, + &game.panel.conveyor_belt_3, + TYPE_INTEGER, + }, + { + GAME_CONTROL_CONVEYOR_BELT_3_SWITCH, + &game.panel.conveyor_belt_3_switch, + TYPE_INTEGER, + }, + { + GAME_CONTROL_CONVEYOR_BELT_4, + &game.panel.conveyor_belt_4, + TYPE_INTEGER, + }, + { + GAME_CONTROL_CONVEYOR_BELT_4_SWITCH, + &game.panel.conveyor_belt_4_switch, + TYPE_INTEGER, + }, + { + GAME_CONTROL_MAGIC_WALL, + &game.panel.magic_wall, + TYPE_INTEGER, + }, + { + GAME_CONTROL_MAGIC_WALL_TIME, + &game.panel.magic_wall_time, + TYPE_INTEGER, + }, + { + GAME_CONTROL_BD_MAGIC_WALL, + &game.panel.bd_magic_wall, + TYPE_INTEGER, + }, + { + GAME_CONTROL_DC_MAGIC_WALL, + &game.panel.dc_magic_wall, + TYPE_INTEGER, + }, + { + GAME_CONTROL_PLAYER_NAME, + &game.panel.player_name, + TYPE_INTEGER, + }, + { + GAME_CONTROL_LEVEL_NAME, + &game.panel.level_name, + TYPE_INTEGER, + }, + { + GAME_CONTROL_LEVEL_AUTHOR, + &game.panel.level_author, + TYPE_INTEGER, + }, + + { + -1, + NULL, + -1, + NULL + } +}; +#endif + + +/* values for delayed check of falling and moving elements and for collision */ +#define CHECK_DELAY_MOVING 3 +#define CHECK_DELAY_FALLING CHECK_DELAY_MOVING +#define CHECK_DELAY_COLLISION 2 +#define CHECK_DELAY_IMPACT CHECK_DELAY_COLLISION + /* values for initial player move delay (initial delay counter value) */ #define INITIAL_MOVE_DELAY_OFF -1 #define INITIAL_MOVE_DELAY_ON 0 @@ -108,14 +594,10 @@ #define MOVE_DELAY_HIGH_SPEED 4 #define MOVE_DELAY_MAX_SPEED 1 -#if 0 -#define DOUBLE_MOVE_DELAY(x) (x = (x <= MOVE_DELAY_HIGH_SPEED ? x * 2 : x)) -#define HALVE_MOVE_DELAY(x) (x = (x >= MOVE_DELAY_HIGH_SPEED ? x / 2 : x)) -#else #define DOUBLE_MOVE_DELAY(x) (x = (x < MOVE_DELAY_MIN_SPEED ? x * 2 : x)) #define HALVE_MOVE_DELAY(x) (x = (x > MOVE_DELAY_MAX_SPEED ? x / 2 : x)) -#endif -#define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY((p)->move_delay_value)) + +#define DOUBLE_PLAYER_SPEED(p) (HALVE_MOVE_DELAY( (p)->move_delay_value)) #define HALVE_PLAYER_SPEED(p) (DOUBLE_MOVE_DELAY((p)->move_delay_value)) /* values for other actions */ @@ -126,7 +608,7 @@ #define GET_DX_FROM_DIR(d) ((d) == MV_LEFT ? -1 : (d) == MV_RIGHT ? 1 : 0) #define GET_DY_FROM_DIR(d) ((d) == MV_UP ? -1 : (d) == MV_DOWN ? 1 : 0) -#define INIT_GFX_RANDOM() (SimpleRND(1000000)) +#define INIT_GFX_RANDOM() (GetSimpleRandom(1000000)) #define GET_NEW_PUSH_DELAY(e) ( (element_info[e].push_delay_fixed) + \ RND(element_info[e].push_delay_random)) @@ -136,25 +618,33 @@ 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_CUSTOM_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\ +#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)) #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \ RND((c)->delay_random * (c)->delay_frames)) #define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \ RND((c)->delay_random)) -#if 1 + #define GET_VALID_RUNTIME_ELEMENT(e) \ ((e) >= NUM_RUNTIME_ELEMENTS ? EL_UNKNOWN : (e)) -#else -#define GET_VALID_FILE_ELEMENT(e) \ - ((e) >= NUM_FILE_ELEMENTS ? EL_UNKNOWN : (e)) -#endif -#define GET_TARGET_ELEMENT(e, ch) \ - ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \ - (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \ - (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : (e)) +#define RESOLVED_REFERENCE_ELEMENT(be, e) \ + ((be) + (e) - EL_SELF < EL_CUSTOM_START ? EL_CUSTOM_START : \ + (be) + (e) - EL_SELF > EL_CUSTOM_END ? EL_CUSTOM_END : \ + (be) + (e) - EL_SELF) + +#define GET_TARGET_ELEMENT(be, e, ch, cv, cs) \ + ((e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : \ + (e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \ + (e) == EL_TRIGGER_CE_VALUE ? (ch)->actual_trigger_ce_value : \ + (e) == EL_TRIGGER_CE_SCORE ? (ch)->actual_trigger_ce_score : \ + (e) == EL_CURRENT_CE_VALUE ? (cv) : \ + (e) == EL_CURRENT_CE_SCORE ? (cs) : \ + (e) >= EL_PREV_CE_8 && (e) <= EL_NEXT_CE_8 ? \ + RESOLVED_REFERENCE_ELEMENT(be, e) : \ + (e)) #define CAN_GROW_INTO(e) \ ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable)) @@ -213,7 +703,10 @@ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[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 ||\ + 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]))) #define DRAGON_CAN_ENTER_FIELD(e, x, y) \ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0) @@ -228,15 +721,6 @@ (IN_LEV_FIELD(x, y) && (Feld[x][y] == EL_EMC_SPRING_BUMPER || \ Feld[x][y] == EL_EMC_SPRING_BUMPER_ACTIVE)) -#if 0 -#define GROUP_NR(e) ((e) - EL_GROUP_START) -#define IS_IN_GROUP(e, nr) (element_info[e].in_group[nr] == TRUE) -#define IS_IN_GROUP_EL(e, ge) (IS_IN_GROUP(e, (ge) - EL_GROUP_START)) - -#define IS_EQUAL_OR_IN_GROUP(e, ge) \ - (IS_GROUP_ELEMENT(ge) ? IS_IN_GROUP(e, GROUP_NR(ge)) : (e) == (ge)) -#endif - #define MOVE_ENTER_EL(e) (element_info[e].move_enter_element) #define CE_ENTER_FIELD_COND(e, x, y) \ @@ -327,8 +811,63 @@ static void PlayLevelMusic(); static void MapGameButtons(); static void HandleGameButtons(struct GadgetInfo *); +int AmoebeNachbarNr(int, int); +void AmoebeUmwandeln(int, int); +void ContinueMoving(int, int); +void Bang(int, int); +void InitMovDir(int, int); +void InitAmoebaNr(int, int); +int NewHiScore(void); + +void TestIfGoodThingHitsBadThing(int, int, int); +void TestIfBadThingHitsGoodThing(int, int, int); +void TestIfPlayerTouchesBadThing(int, int); +void TestIfPlayerRunsIntoBadThing(int, int, int); +void TestIfBadThingTouchesPlayer(int, int); +void TestIfBadThingRunsIntoPlayer(int, int, int); +void TestIfFriendTouchesBadThing(int, int); +void TestIfBadThingTouchesFriend(int, int); +void TestIfBadThingTouchesOtherBadThing(int, int); + +void KillPlayer(struct PlayerInfo *); +void BuryPlayer(struct PlayerInfo *); +void RemovePlayer(struct PlayerInfo *); + +boolean SnapField(struct PlayerInfo *, int, int); +boolean DropElement(struct PlayerInfo *); + +static int getInvisibleActiveFromInvisibleElement(int); +static int getInvisibleFromInvisibleActiveElement(int); + static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS]; +/* for detection of endless loops, caused by custom element programming */ +/* (using maximal playfield width x 10 is just a rough approximation) */ +#define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH * 10) + +#define RECURSION_LOOP_DETECTION_START(e, rc) \ +{ \ + if (recursion_loop_detected) \ + return (rc); \ + \ + if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \ + { \ + recursion_loop_detected = TRUE; \ + recursion_loop_element = (e); \ + } \ + \ + recursion_loop_depth++; \ +} + +#define RECURSION_LOOP_DETECTION_END() \ +{ \ + recursion_loop_depth--; \ +} + +static int recursion_loop_depth; +static boolean recursion_loop_detected; +static boolean recursion_loop_element; + /* ------------------------------------------------------------------------- */ /* definition of elements that automatically change to other elements after */ @@ -353,8 +892,6 @@ static void RunTimegateWheel(int, int); static void InitMagicBallDelay(int, int); static void ActivateMagicBall(int, int); -static void InitDiagonalMovingElement(int, int); - struct ChangingElementInfo { int element; @@ -399,6 +936,62 @@ static struct ChangingElementInfo change_delay_list[] = NULL, NULL }, + { + EL_STEEL_EXIT_OPENING, + EL_STEEL_EXIT_OPEN, + 29, + NULL, + NULL, + NULL + }, + { + EL_STEEL_EXIT_CLOSING, + EL_STEEL_EXIT_CLOSED, + 29, + NULL, + NULL, + NULL + }, + { + EL_EM_EXIT_OPENING, + EL_EM_EXIT_OPEN, + 29, + NULL, + NULL, + NULL + }, + { + EL_EM_EXIT_CLOSING, +#if 1 + EL_EMPTY, +#else + EL_EM_EXIT_CLOSED, +#endif + 29, + NULL, + NULL, + NULL + }, + { + EL_EM_STEEL_EXIT_OPENING, + EL_EM_STEEL_EXIT_OPEN, + 29, + NULL, + NULL, + NULL + }, + { + EL_EM_STEEL_EXIT_CLOSING, +#if 1 + EL_STEELWALL, +#else + EL_EM_STEEL_EXIT_CLOSED, +#endif + 29, + NULL, + NULL, + NULL + }, { EL_SP_EXIT_OPENING, EL_SP_EXIT_OPEN, @@ -520,6 +1113,14 @@ static struct ChangingElementInfo change_delay_list[] = RunTimegateWheel, NULL }, + { + EL_DC_TIMEGATE_SWITCH_ACTIVE, + EL_DC_TIMEGATE_SWITCH, + 0, + InitTimegateWheel, + RunTimegateWheel, + NULL + }, { EL_EMC_MAGIC_BALL_ACTIVE, EL_EMC_MAGIC_BALL_ACTIVE, @@ -550,7 +1151,7 @@ static struct ChangingElementInfo change_delay_list[] = 0, NULL, NULL, - InitDiagonalMovingElement + NULL, }, { @@ -592,10 +1193,14 @@ move_stepsize_list[] = { EL_AMOEBA_DROPPING, 2 }, { EL_QUICKSAND_FILLING, 1 }, { EL_QUICKSAND_EMPTYING, 1 }, + { EL_QUICKSAND_FAST_FILLING, 2 }, + { EL_QUICKSAND_FAST_EMPTYING, 2 }, { EL_MAGIC_WALL_FILLING, 2 }, - { EL_BD_MAGIC_WALL_FILLING, 2 }, { EL_MAGIC_WALL_EMPTYING, 2 }, + { EL_BD_MAGIC_WALL_FILLING, 2 }, { EL_BD_MAGIC_WALL_EMPTYING, 2 }, + { EL_DC_MAGIC_WALL_FILLING, 2 }, + { EL_DC_MAGIC_WALL_EMPTYING, 2 }, { EL_UNDEFINED, 0 }, }; @@ -682,7 +1287,19 @@ static int playfield_scan_delta_y = 1; (y) += playfield_scan_delta_y) \ for ((x) = playfield_scan_start_x; \ (x) >= 0 && (x) <= lev_fieldx - 1; \ - (x) += playfield_scan_delta_x) \ + (x) += playfield_scan_delta_x) + +#ifdef DEBUG +void DEBUG_SetMaximumDynamite() +{ + int i; + + for (i = 0; i < MAX_INVENTORY_SIZE; i++) + if (local_player->inventory_size < MAX_INVENTORY_SIZE) + local_player->inventory_element[local_player->inventory_size++] = + EL_DYNAMITE; +} +#endif static void InitPlayfieldScanModeVars() { @@ -726,6 +1343,7 @@ static int get_move_delay_from_stepsize(int move_stepsize) static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize, boolean init_game) { + int player_nr = player->index_nr; int move_delay = get_move_delay_from_stepsize(move_stepsize); boolean cannot_move = (move_stepsize == STEPSIZE_NOT_MOVING ? TRUE : FALSE); @@ -737,8 +1355,8 @@ static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize, if (init_game) { - player->move_delay = game.initial_move_delay; - player->move_delay_value = game.initial_move_delay_value; + player->move_delay = game.initial_move_delay[player_nr]; + player->move_delay_value = game.initial_move_delay_value[player_nr]; player->move_delay_value_next = -1; @@ -748,6 +1366,8 @@ static void SetPlayerMoveSpeed(struct PlayerInfo *player, int move_stepsize, void GetPlayerConfig() { + GameFrameDelay = setup.game_frame_delay; + if (!audio.sound_available) setup.sound_simple = FALSE; @@ -766,58 +1386,33 @@ void GetPlayerConfig() InitJoysticks(); } -static int getBeltNrFromBeltElement(int element) +int GetElementFromGroupElement(int element) { - return (element < EL_CONVEYOR_BELT_2_LEFT ? 0 : - element < EL_CONVEYOR_BELT_3_LEFT ? 1 : - element < EL_CONVEYOR_BELT_4_LEFT ? 2 : 3); -} + if (IS_GROUP_ELEMENT(element)) + { + struct ElementGroupInfo *group = element_info[element].group; + int last_anim_random_frame = gfx.anim_random_frame; + int element_pos; -static int getBeltNrFromBeltActiveElement(int element) -{ - return (element < EL_CONVEYOR_BELT_2_LEFT_ACTIVE ? 0 : - element < EL_CONVEYOR_BELT_3_LEFT_ACTIVE ? 1 : - element < EL_CONVEYOR_BELT_4_LEFT_ACTIVE ? 2 : 3); -} + if (group->choice_mode == ANIM_RANDOM) + gfx.anim_random_frame = RND(group->num_elements_resolved); -static int getBeltNrFromBeltSwitchElement(int element) -{ - return (element < EL_CONVEYOR_BELT_2_SWITCH_LEFT ? 0 : - element < EL_CONVEYOR_BELT_3_SWITCH_LEFT ? 1 : - element < EL_CONVEYOR_BELT_4_SWITCH_LEFT ? 2 : 3); + element_pos = getAnimationFrame(group->num_elements_resolved, 1, + group->choice_mode, 0, + group->choice_pos); + + if (group->choice_mode == ANIM_RANDOM) + gfx.anim_random_frame = last_anim_random_frame; + + group->choice_pos++; + + element = group->element_resolved[element_pos]; + } + + return element; } -static int getBeltDirNrFromBeltSwitchElement(int element) -{ - static int belt_base_element[4] = - { - EL_CONVEYOR_BELT_1_SWITCH_LEFT, - EL_CONVEYOR_BELT_2_SWITCH_LEFT, - EL_CONVEYOR_BELT_3_SWITCH_LEFT, - EL_CONVEYOR_BELT_4_SWITCH_LEFT - }; - - int belt_nr = getBeltNrFromBeltSwitchElement(element); - int belt_dir_nr = element - belt_base_element[belt_nr]; - - return (belt_dir_nr % 3); -} - -static int getBeltDirFromBeltSwitchElement(int element) -{ - static int belt_move_dir[3] = - { - MV_LEFT, - MV_NONE, - MV_RIGHT - }; - - int belt_dir_nr = getBeltDirNrFromBeltSwitchElement(element); - - return belt_move_dir[belt_dir_nr]; -} - -static void InitPlayerField(int x, int y, int element, boolean init_game) +static void InitPlayerField(int x, int y, int element, boolean init_game) { if (element == EL_SP_MURPHY) { @@ -832,6 +1427,9 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) else { stored_player[0].use_murphy = TRUE; + + if (!level.use_artwork_element[0]) + stored_player[0].artwork_element = EL_SP_MURPHY; } Feld[x][y] = EL_PLAYER_1; @@ -927,41 +1525,45 @@ static void InitField(int x, int y, boolean init_game) Feld[x][y] = EL_ACID_POOL_BOTTOMRIGHT; break; + case EL_BUG: case EL_BUG_RIGHT: case EL_BUG_UP: case EL_BUG_LEFT: case EL_BUG_DOWN: - case EL_BUG: + case EL_SPACESHIP: case EL_SPACESHIP_RIGHT: case EL_SPACESHIP_UP: case EL_SPACESHIP_LEFT: case EL_SPACESHIP_DOWN: - case EL_SPACESHIP: + case EL_BD_BUTTERFLY: case EL_BD_BUTTERFLY_RIGHT: case EL_BD_BUTTERFLY_UP: case EL_BD_BUTTERFLY_LEFT: case EL_BD_BUTTERFLY_DOWN: - case EL_BD_BUTTERFLY: + case EL_BD_FIREFLY: case EL_BD_FIREFLY_RIGHT: case EL_BD_FIREFLY_UP: case EL_BD_FIREFLY_LEFT: case EL_BD_FIREFLY_DOWN: - case EL_BD_FIREFLY: case EL_PACMAN_RIGHT: case EL_PACMAN_UP: case EL_PACMAN_LEFT: case EL_PACMAN_DOWN: case EL_YAMYAM: + case EL_YAMYAM_LEFT: + case EL_YAMYAM_RIGHT: + case EL_YAMYAM_UP: + case EL_YAMYAM_DOWN: case EL_DARK_YAMYAM: case EL_ROBOT: case EL_PACMAN: case EL_SP_SNIKSNAK: case EL_SP_ELECTRON: + case EL_MOLE: case EL_MOLE_LEFT: case EL_MOLE_RIGHT: case EL_MOLE_UP: case EL_MOLE_DOWN: - case EL_MOLE: InitMovDir(x, y); break; @@ -1034,16 +1636,31 @@ static void InitField(int x, int y, boolean init_game) } break; +#if !USE_BOTH_SWITCHGATE_SWITCHES case EL_SWITCHGATE_SWITCH_DOWN: /* always start with same switch pos */ if (init_game) Feld[x][y] = EL_SWITCHGATE_SWITCH_UP; break; + case EL_DC_SWITCHGATE_SWITCH_DOWN: /* always start with same switch pos */ + if (init_game) + Feld[x][y] = EL_DC_SWITCHGATE_SWITCH_UP; + break; +#endif + case EL_LIGHT_SWITCH_ACTIVE: if (init_game) game.light_time_left = level.time_light * FRAMES_PER_SECOND; break; + case EL_INVISIBLE_STEELWALL: + case EL_INVISIBLE_WALL: + case EL_INVISIBLE_SAND: + if (game.light_time_left > 0 || + game.lenses_time_left > 0) + Feld[x][y] = getInvisibleActiveFromInvisibleElement(element); + break; + case EL_EMC_MAGIC_BALL: if (game.ball_state) Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE; @@ -1055,7 +1672,6 @@ static void InitField(int x, int y, boolean init_game) break; default: -#if 1 if (IS_CUSTOM_ELEMENT(element)) { if (CAN_MOVE(element)) @@ -1063,56 +1679,21 @@ static void InitField(int x, int y, boolean init_game) #if USE_NEW_CUSTOM_VALUE if (!element_info[element].use_last_ce_value || init_game) - CustomValue[x][y] = GET_NEW_CUSTOM_VALUE(Feld[x][y]); + CustomValue[x][y] = GET_NEW_CE_VALUE(Feld[x][y]); #endif } -#else - if (IS_CUSTOM_ELEMENT(element) && CAN_MOVE(element)) - InitMovDir(x, y); -#endif else if (IS_GROUP_ELEMENT(element)) { - struct ElementGroupInfo *group = element_info[element].group; - int last_anim_random_frame = gfx.anim_random_frame; - int element_pos; - - if (group->choice_mode == ANIM_RANDOM) - gfx.anim_random_frame = RND(group->num_elements_resolved); - - element_pos = getAnimationFrame(group->num_elements_resolved, 1, - group->choice_mode, 0, - group->choice_pos); - - if (group->choice_mode == ANIM_RANDOM) - gfx.anim_random_frame = last_anim_random_frame; - - group->choice_pos++; - - Feld[x][y] = group->element_resolved[element_pos]; + Feld[x][y] = GetElementFromGroupElement(element); InitField(x, y, init_game); } + break; } -#if 1 if (!init_game) CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X); -#endif - -#if 0 - -#if USE_NEW_CUSTOM_VALUE - -#if 1 - CustomValue[x][y] = GET_NEW_CUSTOM_VALUE(Feld[x][y]); -#else - CustomValue[x][y] = element_info[Feld[x][y]].custom_value_initial; -#endif - -#endif - -#endif } static inline void InitField_WithBug1(int x, int y, boolean init_game) @@ -1146,76 +1727,354 @@ static inline void InitField_WithBug2(int x, int y, boolean init_game) */ } -inline void DrawGameValue_Emeralds(int value) +#if 1 + +void DrawGameValue_Emeralds(int value) +{ + struct TextPosInfo *pos = &game.panel.gems; +#if 1 + int font_nr = pos->font; +#else + int font_nr = FONT_TEXT_2; +#endif + int font_width = getFontWidth(font_nr); + int chars = pos->chars; + + if (PANEL_DEACTIVATED(pos)) + return; + + pos->width = chars * font_width; + + DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr); +} + +void DrawGameValue_Dynamite(int value) +{ + struct TextPosInfo *pos = &game.panel.inventory; +#if 1 + int font_nr = pos->font; +#else + int font_nr = FONT_TEXT_2; +#endif + int font_width = getFontWidth(font_nr); + int chars = pos->chars; + + if (PANEL_DEACTIVATED(pos)) + return; + + pos->width = chars * font_width; + + DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr); +} + +void DrawGameValue_Score(int value) +{ + struct TextPosInfo *pos = &game.panel.score; +#if 1 + int font_nr = pos->font; +#else + int font_nr = FONT_TEXT_2; +#endif + int font_width = getFontWidth(font_nr); + int chars = pos->chars; + + if (PANEL_DEACTIVATED(pos)) + return; + + pos->width = chars * font_width; + + DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr); +} + +void DrawGameValue_Time(int value) { - DrawText(DX_EMERALDS, DY_EMERALDS, int2str(value, 3), FONT_TEXT_2); + struct TextPosInfo *pos = &game.panel.time; + static int last_value = -1; + int chars1 = 3; + int chars2 = 4; + int chars = pos->chars; +#if 1 + int font1_nr = pos->font; + int font2_nr = pos->font_alt; +#else + int font1_nr = FONT_TEXT_2; + int font2_nr = FONT_TEXT_1; +#endif + int font_nr = font1_nr; + boolean use_dynamic_chars = (chars == -1 ? TRUE : FALSE); + + if (PANEL_DEACTIVATED(pos)) + return; + + if (use_dynamic_chars) /* use dynamic number of chars */ + { + chars = (value < 1000 ? chars1 : chars2); + font_nr = (value < 1000 ? font1_nr : font2_nr); + } + + /* clear background if value just changed its size (dynamic chars only) */ + if (use_dynamic_chars && (last_value < 1000) != (value < 1000)) + { + int width1 = chars1 * getFontWidth(font1_nr); + int width2 = chars2 * getFontWidth(font2_nr); + int max_width = MAX(width1, width2); + int max_height = MAX(getFontHeight(font1_nr), getFontHeight(font2_nr)); + + pos->width = max_width; + + ClearRectangleOnBackground(drawto, PANEL_XPOS(pos), PANEL_YPOS(pos), + max_width, max_height); + } + + pos->width = chars * getFontWidth(font_nr); + + DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr); + + last_value = value; } -inline void DrawGameValue_Dynamite(int value) +void DrawGameValue_Level(int value) { - DrawText(DX_DYNAMITE, DY_DYNAMITE, int2str(value, 3), FONT_TEXT_2); + struct TextPosInfo *pos = &game.panel.level_number; + int chars1 = 2; + int chars2 = 3; + int chars = pos->chars; +#if 1 + int font1_nr = pos->font; + int font2_nr = pos->font_alt; +#else + int font1_nr = FONT_TEXT_2; + int font2_nr = FONT_TEXT_1; +#endif + int font_nr = font1_nr; + boolean use_dynamic_chars = (chars == -1 ? TRUE : FALSE); + + if (PANEL_DEACTIVATED(pos)) + return; + + if (use_dynamic_chars) /* use dynamic number of chars */ + { + chars = (level_nr < 100 ? chars1 : chars2); + font_nr = (level_nr < 100 ? font1_nr : font2_nr); + } + + pos->width = chars * getFontWidth(font_nr); + + DrawText(PANEL_XPOS(pos), PANEL_YPOS(pos), int2str(value, chars), font_nr); } -inline void DrawGameValue_Keys(int key[MAX_NUM_KEYS]) +void DrawGameValue_Keys(int key[MAX_NUM_KEYS]) { +#if 0 + struct TextPosInfo *pos = &game.panel.keys; +#endif +#if 0 + int base_key_graphic = EL_KEY_1; +#endif int i; +#if 0 + if (PANEL_DEACTIVATED(pos)) + return; +#endif + +#if 0 + if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + base_key_graphic = EL_EM_KEY_1; +#endif + +#if 0 + pos->width = 4 * MINI_TILEX; +#endif + +#if 1 + for (i = 0; i < MAX_NUM_KEYS; i++) +#else /* currently only 4 of 8 possible keys are displayed */ for (i = 0; i < STD_NUM_KEYS; i++) +#endif { +#if 1 + struct TextPosInfo *pos = &game.panel.key[i]; +#endif + int src_x = DOOR_GFX_PAGEX5 + 18; + int src_y = DOOR_GFX_PAGEY1 + 123; +#if 1 + int dst_x = PANEL_XPOS(pos); + int dst_y = PANEL_YPOS(pos); +#else + int dst_x = PANEL_XPOS(pos) + i * MINI_TILEX; + int dst_y = PANEL_YPOS(pos); +#endif + +#if 1 + int element = (i >= STD_NUM_KEYS ? EL_EMC_KEY_5 - 4 : + level.game_engine_type == GAME_ENGINE_TYPE_EM ? EL_EM_KEY_1 : + EL_KEY_1) + i; + int graphic = el2edimg(element); +#endif + +#if 1 + if (PANEL_DEACTIVATED(pos)) + continue; +#endif + +#if 0 + /* masked blit with tiles from half-size scaled bitmap does not work yet + (no mask bitmap created for these sizes after loading and scaling) -- + solution: load without creating mask, scale, then create final mask */ + + BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, src_x, src_y, + MINI_TILEX, MINI_TILEY, dst_x, dst_y); + + if (key[i]) + { +#if 0 + int graphic = el2edimg(base_key_graphic + i); +#endif + Bitmap *src_bitmap; + int src_x, src_y; + + getMiniGraphicSource(graphic, &src_bitmap, &src_x, &src_y); + + SetClipOrigin(src_bitmap, src_bitmap->stored_clip_gc, + dst_x - src_x, dst_y - src_y); + BlitBitmapMasked(src_bitmap, drawto, src_x, src_y, MINI_TILEX, MINI_TILEY, + dst_x, dst_y); + } +#else +#if 1 if (key[i]) - DrawMiniGraphicExt(drawto, DX_KEYS + i * MINI_TILEX, DY_KEYS, - el2edimg(EL_KEY_1 + i)); + DrawMiniGraphicExt(drawto, dst_x, dst_y, graphic); else - BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, - DOOR_GFX_PAGEX5 + XX_KEYS + i * MINI_TILEX, YY_KEYS, - MINI_TILEX, MINI_TILEY, DX_KEYS + i * MINI_TILEX, DY_KEYS); + BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, src_x, src_y, + MINI_TILEX, MINI_TILEY, dst_x, dst_y); +#else + if (key[i]) + DrawMiniGraphicExt(drawto, dst_x, dst_y, el2edimg(base_key_graphic + i)); + else + BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, src_x, src_y, + MINI_TILEX, MINI_TILEY, dst_x, dst_y); +#endif +#endif } } -inline void DrawGameValue_Score(int value) +#else + +void DrawGameValue_Emeralds(int value) +{ + int font_nr = FONT_TEXT_2; + int xpos = (3 * 14 - 3 * getFontWidth(font_nr)) / 2; + + if (PANEL_DEACTIVATED(game.panel.gems)) + return; + + DrawText(DX_EMERALDS + xpos, DY_EMERALDS, int2str(value, 3), font_nr); +} + +void DrawGameValue_Dynamite(int value) +{ + int font_nr = FONT_TEXT_2; + int xpos = (3 * 14 - 3 * getFontWidth(font_nr)) / 2; + + if (PANEL_DEACTIVATED(game.panel.inventory)) + return; + + DrawText(DX_DYNAMITE + xpos, DY_DYNAMITE, int2str(value, 3), font_nr); +} + +void DrawGameValue_Score(int value) { - DrawText(DX_SCORE, DY_SCORE, int2str(value, 5), FONT_TEXT_2); + int font_nr = FONT_TEXT_2; + int xpos = (5 * 14 - 5 * getFontWidth(font_nr)) / 2; + + if (PANEL_DEACTIVATED(game.panel.score)) + return; + + DrawText(DX_SCORE + xpos, DY_SCORE, int2str(value, 5), font_nr); } -inline void DrawGameValue_Time(int value) +void DrawGameValue_Time(int value) { + int font1_nr = FONT_TEXT_2; +#if 1 + int font2_nr = FONT_TEXT_1; +#else + int font2_nr = FONT_LEVEL_NUMBER; +#endif + int xpos3 = (3 * 14 - 3 * getFontWidth(font1_nr)) / 2; + int xpos4 = (4 * 10 - 4 * getFontWidth(font2_nr)) / 2; + + if (PANEL_DEACTIVATED(game.panel.time)) + return; + + /* clear background if value just changed its size */ + if (value == 999 || value == 1000) + ClearRectangleOnBackground(drawto, DX_TIME1, DY_TIME, 14 * 3, 14); + if (value < 1000) - DrawText(DX_TIME1, DY_TIME, int2str(value, 3), FONT_TEXT_2); + DrawText(DX_TIME1 + xpos3, DY_TIME, int2str(value, 3), font1_nr); else - DrawText(DX_TIME2, DY_TIME, int2str(value, 4), FONT_LEVEL_NUMBER); + DrawText(DX_TIME2 + xpos4, DY_TIME, int2str(value, 4), font2_nr); } -inline void DrawGameValue_Level(int value) +void DrawGameValue_Level(int value) { + int font1_nr = FONT_TEXT_2; +#if 1 + int font2_nr = FONT_TEXT_1; +#else + int font2_nr = FONT_LEVEL_NUMBER; +#endif + + if (PANEL_DEACTIVATED(game.panel.level)) + return; + if (level_nr < 100) - DrawText(DX_LEVEL, DY_LEVEL, int2str(value, 2), FONT_TEXT_2); + DrawText(DX_LEVEL1, DY_LEVEL, int2str(value, 2), font1_nr); else - { - /* misuse area for displaying emeralds to draw bigger level number */ - DrawTextExt(drawto, DX_EMERALDS, DY_EMERALDS, - int2str(value, 3), FONT_LEVEL_NUMBER, BLIT_OPAQUE); + DrawText(DX_LEVEL2, DY_LEVEL, int2str(value, 3), font2_nr); +} + +void DrawGameValue_Keys(int key[MAX_NUM_KEYS]) +{ + int base_key_graphic = EL_KEY_1; + int i; + + if (PANEL_DEACTIVATED(game.panel.keys)) + return; - /* now copy it to the area for displaying level number */ - BlitBitmap(drawto, drawto, - DX_EMERALDS, DY_EMERALDS + 1, - getFontWidth(FONT_LEVEL_NUMBER) * 3, - getFontHeight(FONT_LEVEL_NUMBER) - 1, - DX_LEVEL - 1, DY_LEVEL + 1); + if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + base_key_graphic = EL_EM_KEY_1; - /* restore the area for displaying emeralds */ - DrawGameValue_Emeralds(local_player->gems_still_needed); + /* currently only 4 of 8 possible keys are displayed */ + for (i = 0; i < STD_NUM_KEYS; i++) + { + int x = XX_KEYS + i * MINI_TILEX; + int y = YY_KEYS; - /* yes, this is all really ugly :-) */ + if (key[i]) + DrawMiniGraphicExt(drawto, DX + x,DY + y, el2edimg(base_key_graphic + i)); + else + BlitBitmap(graphic_info[IMG_GLOBAL_DOOR].bitmap, drawto, + DOOR_GFX_PAGEX5 + x, y, MINI_TILEX, MINI_TILEY, DX + x,DY + y); } } +#endif + void DrawAllGameValues(int emeralds, int dynamite, int score, int time, int key_bits) { int key[MAX_NUM_KEYS]; int i; + /* prevent EM engine from updating time/score values parallel to GameWon() */ + if (level.game_engine_type == GAME_ENGINE_TYPE_EM && + local_player->LevelSolved) + return; + for (i = 0; i < MAX_NUM_KEYS; i++) key[i] = key_bits & (1 << i); @@ -1231,7 +2090,13 @@ void DrawAllGameValues(int emeralds, int dynamite, int score, int time, void DrawGameDoorValues() { - int i; + int time_value = (level.time == 0 ? TimePlayed : TimeLeft); + int dynamite_value = 0; + int score_value = (local_player->LevelSolved ? local_player->score_final : + local_player->score); + int gems_value = local_player->gems_still_needed; + int key_bits = 0; + int i, j; if (level.game_engine_type == GAME_ENGINE_TYPE_EM) { @@ -1240,62 +2105,32 @@ void DrawGameDoorValues() return; } - DrawGameValue_Level(level_nr); - - DrawGameValue_Emeralds(local_player->gems_still_needed); - DrawGameValue_Dynamite(local_player->inventory_size); - DrawGameValue_Score(local_player->score); - DrawGameValue_Time(TimeLeft); - - for (i = 0; i < MAX_PLAYERS; i++) - DrawGameValue_Keys(stored_player[i].key); -} - -#if 0 -static void resolve_group_element(int group_element, int recursion_depth) -{ - static int group_nr; - static struct ElementGroupInfo *group; - struct ElementGroupInfo *actual_group = element_info[group_element].group; - int i; - - if (recursion_depth > NUM_GROUP_ELEMENTS) /* recursion too deep */ - { - Error(ERR_WARN, "recursion too deep when resolving group element %d", - group_element - EL_GROUP_START + 1); - - /* replace element which caused too deep recursion by question mark */ - group->element_resolved[group->num_elements_resolved++] = EL_UNKNOWN; - - return; - } - - if (recursion_depth == 0) /* initialization */ + if (game.centered_player_nr == -1) { - group = element_info[group_element].group; - group_nr = group_element - EL_GROUP_START; + for (i = 0; i < MAX_PLAYERS; i++) + { + for (j = 0; j < MAX_NUM_KEYS; j++) + if (stored_player[i].key[j]) + key_bits |= (1 << j); - group->num_elements_resolved = 0; - group->choice_pos = 0; + dynamite_value += stored_player[i].inventory_size; + } } - - for (i = 0; i < actual_group->num_elements; i++) + else { - int element = actual_group->element[i]; + int player_nr = game.centered_player_nr; - if (group->num_elements_resolved == NUM_FILE_ELEMENTS) - break; + for (i = 0; i < MAX_NUM_KEYS; i++) + if (stored_player[player_nr].key[i]) + key_bits |= (1 << i); - if (IS_GROUP_ELEMENT(element)) - resolve_group_element(element, recursion_depth + 1); - else - { - group->element_resolved[group->num_elements_resolved++] = element; - element_info[element].in_group[group_nr] = TRUE; - } + dynamite_value = stored_player[player_nr].inventory_size; } + + DrawAllGameValues(gems_value, dynamite_value, score_value, time_value, + key_bits); } -#endif + /* ============================================================================= @@ -1427,32 +2262,18 @@ static void InitGameEngine() printf(" => game.engine_version == %06d\n", game.engine_version); #endif -#if 0 - /* ---------- recursively resolve group elements ------------------------- */ + /* ---------- initialize player's initial move delay --------------------- */ - for (i = 0; i < MAX_NUM_ELEMENTS; i++) - for (j = 0; j < NUM_GROUP_ELEMENTS; j++) - element_info[i].in_group[j] = FALSE; - - for (i = 0; i < NUM_GROUP_ELEMENTS; i++) - resolve_group_element(EL_GROUP_START + i, 0); -#endif - - /* ---------- initialize player's initial move delay --------------------- */ - -#if 1 - /* dynamically adjust player properties according to level information */ - game.initial_move_delay_value = - get_move_delay_from_stepsize(level.initial_player_stepsize); -#else - /* dynamically adjust player properties according to level information */ - game.initial_move_delay_value = - (level.double_speed ? MOVE_DELAY_HIGH_SPEED : MOVE_DELAY_NORMAL_SPEED); -#endif + /* dynamically adjust player properties according to level information */ + for (i = 0; i < MAX_PLAYERS; i++) + game.initial_move_delay_value[i] = + get_move_delay_from_stepsize(level.initial_player_stepsize[i]); /* dynamically adjust player properties according to game engine version */ - game.initial_move_delay = (game.engine_version <= VERSION_IDENT(2,0,1,0) ? - game.initial_move_delay_value : 0); + for (i = 0; i < MAX_PLAYERS; i++) + game.initial_move_delay[i] = + (game.engine_version <= VERSION_IDENT(2,0,1,0) ? + game.initial_move_delay_value[i] : 0); /* ---------- initialize player's initial push delay --------------------- */ @@ -1559,6 +2380,7 @@ static void InitGameEngine() ei->change_page[j].actual_trigger_player = EL_PLAYER_1; 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; } } @@ -1595,6 +2417,9 @@ static void InitGameEngine() for (l = 0; l < group->num_elements_resolved; l++) trigger_events[group->element_resolved[l]][k] = TRUE; } + else if (trigger_element == EL_ANY_ELEMENT) + for (l = 0; l < MAX_NUM_ELEMENTS; l++) + trigger_events[l][k] = TRUE; else trigger_events[trigger_element][k] = TRUE; } @@ -1610,8 +2435,17 @@ static void InitGameEngine() { if (!IS_CUSTOM_ELEMENT(i)) { - element_info[i].push_delay_fixed = game.default_push_delay_fixed; - element_info[i].push_delay_random = game.default_push_delay_random; + /* set default push delay values (corrected since version 3.0.7-1) */ + if (game.engine_version < VERSION_IDENT(3,0,7,1)) + { + element_info[i].push_delay_fixed = 2; + element_info[i].push_delay_random = 8; + } + else + { + element_info[i].push_delay_fixed = 8; + element_info[i].push_delay_random = 8; + } } } @@ -1718,6 +2552,11 @@ static void InitGameEngine() EL_EMPTY); } } + + /* ---------- initialize recursion detection ------------------------------ */ + recursion_loop_depth = 0; + recursion_loop_detected = FALSE; + recursion_loop_element = EL_UNDEFINED; } int get_num_special_action(int element, int action_first, int action_last) @@ -1743,6 +2582,7 @@ int get_num_special_action(int element, int action_first, int action_last) return num_special_action; } + /* ============================================================================= InitGame() @@ -1756,8 +2596,11 @@ void InitGame() boolean emulate_bd = TRUE; /* unless non-BOULDERDASH elements found */ boolean emulate_sb = TRUE; /* unless non-SOKOBAN elements found */ boolean emulate_sp = TRUE; /* unless non-SUPAPLEX elements found */ + boolean do_fading = (game_status == GAME_MODE_MAIN); int i, j, x, y; + game_status = GAME_MODE_PLAYING; + InitGameEngine(); /* don't play tapes over network */ @@ -1773,12 +2616,15 @@ void InitGame() player->present = FALSE; player->active = FALSE; + player->killed = FALSE; player->action = 0; player->effective_action = 0; player->programmed_action = 0; player->score = 0; + player->score_final = 0; + player->gems_still_needed = level.gems_needed; player->sokobanfields_still_needed = 0; player->lights_still_needed = 0; @@ -1787,6 +2633,8 @@ void InitGame() for (j = 0; j < MAX_NUM_KEYS; j++) player->key[j] = FALSE; + player->num_white_keys = 0; + player->dynabomb_count = 0; player->dynabomb_size = 1; player->dynabombs_left = 0; @@ -1808,6 +2656,8 @@ void InitGame() player->block_last_field = FALSE; /* initialized in InitPlayerField() */ player->block_delay_adjustment = 0; /* initialized in InitPlayerField() */ + player->gravity = level.initial_player_gravity[i]; + player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr); player->actual_frame_counter = 0; @@ -1816,6 +2666,8 @@ void InitGame() player->last_move_dir = MV_NONE; + player->is_active = FALSE; + player->is_waiting = FALSE; player->is_moving = FALSE; player->is_auto_moving = FALSE; @@ -1825,6 +2677,7 @@ void InitGame() player->is_pushing = FALSE; player->is_switching = FALSE; player->is_dropping = FALSE; + player->is_dropping_pressed = FALSE; player->is_bored = FALSE; player->is_sleeping = FALSE; @@ -1835,19 +2688,12 @@ void InitGame() player->anim_delay_counter = 0; player->post_delay_counter = 0; + player->dir_waiting = MV_NONE; player->action_waiting = ACTION_DEFAULT; player->last_action_waiting = ACTION_DEFAULT; player->special_action_bored = ACTION_DEFAULT; player->special_action_sleeping = ACTION_DEFAULT; - /* set number of special actions for bored and sleeping animation */ - player->num_special_action_bored = - get_num_special_action(player->artwork_element, - ACTION_BORING_1, ACTION_BORING_LAST); - player->num_special_action_sleeping = - get_num_special_action(player->artwork_element, - ACTION_SLEEPING_1, ACTION_SLEEPING_LAST); - player->switch_x = -1; player->switch_y = -1; @@ -1856,26 +2702,18 @@ void InitGame() player->show_envelope = 0; -#if 1 - SetPlayerMoveSpeed(player, level.initial_player_stepsize, TRUE); -#else - player->move_delay = game.initial_move_delay; - player->move_delay_value = game.initial_move_delay_value; - - player->move_delay_value_next = -1; - - player->move_delay_reset_counter = 0; - - player->cannot_move = FALSE; -#endif + SetPlayerMoveSpeed(player, level.initial_player_stepsize[i], TRUE); player->push_delay = -1; /* initialized when pushing starts */ player->push_delay_value = game.initial_push_delay_value; player->drop_delay = 0; + player->drop_pressed_delay = 0; - player->last_jx = player->last_jy = 0; - player->jx = player->jy = 0; + player->last_jx = -1; + player->last_jy = -1; + player->jx = -1; + player->jy = -1; player->shield_normal_time_left = 0; player->shield_deadly_time_left = 0; @@ -1888,6 +2726,12 @@ void InitGame() player->LevelSolved = FALSE; player->GameOver = FALSE; + + player->LevelSolved_GameWon = FALSE; + player->LevelSolved_GameEnd = FALSE; + player->LevelSolved_PanelOff = FALSE; + player->LevelSolved_SaveTape = FALSE; + player->LevelSolved_SaveScore = FALSE; } network_player_action_received = FALSE; @@ -1922,8 +2766,11 @@ void InitGame() game.timegate_time_left = 0; game.switchgate_pos = 0; game.wind_direction = level.wind_direction_initial; - game.gravity = level.initial_gravity; + +#if !USE_PLAYER_GRAVITY + game.gravity = FALSE; game.explosions_delayed = TRUE; +#endif game.lenses_time_left = 0; game.magnify_time_left = 0; @@ -1933,6 +2780,18 @@ void InitGame() game.envelope_active = FALSE; + /* set focus to local player for network games, else to all players */ + game.centered_player_nr = (network_playing ? local_player->index_nr : -1); + game.centered_player_nr_next = game.centered_player_nr; + game.set_centered_player = FALSE; + + if (network_playing && tape.recording) + { + /* store client dependent player focus when recording network games */ + tape.centered_player_nr_next = game.centered_player_nr_next; + tape.set_centered_player = TRUE; + } + for (i = 0; i < NUM_BELTS; i++) { game.belt_dir[i] = MV_NONE; @@ -1942,11 +2801,7 @@ void InitGame() for (i = 0; i < MAX_NUM_AMOEBA; i++) AmoebaCnt[i] = AmoebaCnt2[i] = 0; -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (x = 0; x < lev_fieldx; x++) for (y = 0; y < lev_fieldy; y++) -#endif { Feld[x][y] = level.field[x][y]; MovPos[x][y] = MovDir[x][y] = MovDelay[x][y] = 0; @@ -1960,6 +2815,7 @@ void InitGame() WasJustMoving[x][y] = 0; WasJustFalling[x][y] = 0; CheckCollision[x][y] = 0; + CheckImpact[x][y] = 0; Stop[x][y] = FALSE; Pushed[x][y] = FALSE; @@ -1980,11 +2836,7 @@ void InitGame() GfxDir[x][y] = MV_NONE; } -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { if (emulate_bd && !IS_BD_ELEMENT(Feld[x][y])) emulate_bd = FALSE; @@ -1998,6 +2850,19 @@ void InitGame() InitBeltMovement(); + for (i = 0; i < MAX_PLAYERS; i++) + { + struct PlayerInfo *player = &stored_player[i]; + + /* set number of special actions for bored and sleeping animation */ + player->num_special_action_bored = + get_num_special_action(player->artwork_element, + ACTION_BORING_1, ACTION_BORING_LAST); + player->num_special_action_sleeping = + get_num_special_action(player->artwork_element, + ACTION_SLEEPING_1, ACTION_SLEEPING_LAST); + } + game.emulation = (emulate_bd ? EMU_BOULDERDASH : emulate_sb ? EMU_SOKOBAN : emulate_sp ? EMU_SUPAPLEX : EMU_NONE); @@ -2076,10 +2941,6 @@ void InitGame() some_player->present = FALSE; some_player->active = FALSE; -#if 0 - player->element_nr = some_player->element_nr; -#endif - player->artwork_element = some_player->artwork_element; player->block_last_field = some_player->block_last_field; @@ -2097,7 +2958,7 @@ void InitGame() if (tape.playing) { - /* when playing a tape, eliminate all players which do not participate */ + /* when playing a tape, eliminate all players who do not participate */ for (i = 0; i < MAX_PLAYERS; i++) { @@ -2192,11 +3053,7 @@ void InitGame() int found_element = EL_UNDEFINED; int player_nr = local_player->index_nr; -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { int element = Feld[x][y]; int content; @@ -2293,13 +3150,33 @@ void InitGame() local_player->jy - MIDPOSY); } + StopAnimation(); + if (!game.restart_level) CloseDoor(DOOR_CLOSE_1); + if (level_editor_test_game) + fading = fading_none; + else + fading = menu.destination; + +#if 1 + if (fading.anim_mode == ANIM_CROSSFADE) + FadeCrossSaveBackbuffer(); + else + FadeOut(REDRAW_FIELD); +#else + if (do_fading) + FadeOut(REDRAW_FIELD); +#endif + /* !!! FIX THIS (START) !!! */ if (level.game_engine_type == GAME_ENGINE_TYPE_EM) { InitGameEngine_EM(); + + /* blit playfield from scroll buffer to normal back buffer for fading in */ + BlitScreenToBitmap_EM(backbuffer); } else { @@ -2310,14 +3187,26 @@ void InitGame() if (game.timegate_time_left == 0) CloseAllOpenTimegates(); + /* blit playfield from scroll buffer to normal back buffer for fading in */ if (setup.soft_scrolling) BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY); redraw_mask |= REDRAW_FROM_BACKBUFFER; - FadeToFront(); } /* !!! FIX THIS (END) !!! */ +#if 1 + if (fading.anim_mode == ANIM_CROSSFADE) + FadeCross(redraw_mask); + else + FadeIn(redraw_mask); +#else + if (do_fading) + FadeIn(REDRAW_FIELD); + + BackToFront(); +#endif + if (!game.restart_level) { /* copy default game door content to main double buffer */ @@ -2325,6 +3214,9 @@ void InitGame() DOOR_GFX_PAGEX5, DOOR_GFX_PAGEY1, DXSIZE, DYSIZE, DX, DY); } + SetPanelBackground(); + SetDrawBackgroundMask(REDRAW_DOOR_1); + DrawGameDoorValues(); if (!game.restart_level) @@ -2343,7 +3235,7 @@ void InitGame() OpenDoor(DOOR_OPEN_ALL); - PlaySoundStereo(SND_GAME_STARTING, SOUND_MIDDLE); + PlaySound(SND_GAME_STARTING); if (setup.sound_music) PlayLevelMusic(); @@ -2358,6 +3250,13 @@ void InitGame() } } +#if 1 + UnmapAllGadgets(); + + MapGameButtons(); + MapTapeButtons(); +#endif + game.restart_level = FALSE; } @@ -2387,7 +3286,7 @@ void InitMovDir(int x, int y) { MV_LEFT, MV_RIGHT, MV_UP, MV_DOWN } }; - switch(element) + switch (element) { case EL_BUG_RIGHT: case EL_BUG_UP: @@ -2429,6 +3328,14 @@ void InitMovDir(int x, int y) MovDir[x][y] = direction[0][element - EL_PACMAN_RIGHT]; break; + case EL_YAMYAM_LEFT: + case EL_YAMYAM_RIGHT: + case EL_YAMYAM_UP: + case EL_YAMYAM_DOWN: + Feld[x][y] = EL_YAMYAM; + MovDir[x][y] = direction[2][element - EL_YAMYAM_LEFT]; + break; + case EL_SP_SNIKSNAK: MovDir[x][y] = MV_UP; break; @@ -2560,123 +3467,220 @@ void InitAmoebaNr(int x, int y) AmoebaCnt2[group_nr]++; } -void GameWon() +static void PlayerWins(struct PlayerInfo *player) { - int hi_pos; - boolean raise_level = FALSE; + player->LevelSolved = TRUE; + player->GameOver = TRUE; - if (local_player->MovPos) - return; + player->score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ? + level.native_em_level->lev->score : player->score); +} - if (tape.auto_play) /* tape might already be stopped here */ - tape.auto_play_level_solved = TRUE; +void GameWon() +{ + static int time, time_final; + static int score, score_final; + static int game_over_delay_1 = 0; + static int game_over_delay_2 = 0; + int game_over_delay_value_1 = 50; + int game_over_delay_value_2 = 50; - local_player->LevelSolved = FALSE; + if (!local_player->LevelSolved_GameWon) + { + int i; - PlaySoundStereo(SND_GAME_WINNING, SOUND_MIDDLE); + /* do not start end game actions before the player stops moving (to exit) */ + if (local_player->MovPos) + return; - if (TimeLeft) - { - if (!tape.playing && setup.sound_loops) - PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE, - SND_CTRL_PLAY_LOOP); + local_player->LevelSolved_GameWon = TRUE; + local_player->LevelSolved_SaveTape = tape.recording; + local_player->LevelSolved_SaveScore = !tape.playing; - while (TimeLeft > 0) - { - if (!tape.playing && !setup.sound_loops) - PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE); + if (tape.auto_play) /* tape might already be stopped here */ + tape.auto_play_level_solved = TRUE; - if (TimeLeft > 100 && TimeLeft % 10 == 0) - { - TimeLeft -= 10; - RaiseScore(level.score[SC_TIME_BONUS] * 10); - } - else - { - TimeLeft--; - RaiseScore(level.score[SC_TIME_BONUS]); - } +#if 1 + TapeStop(); +#endif - DrawGameValue_Time(TimeLeft); + game_over_delay_1 = game_over_delay_value_1; + game_over_delay_2 = game_over_delay_value_2; - BackToFront(); + time = time_final = (level.time == 0 ? TimePlayed : TimeLeft); + score = score_final = local_player->score_final; - if (!tape.playing) - Delay(10); + if (TimeLeft > 0) + { + time_final = 0; + score_final += TimeLeft * level.score[SC_TIME_BONUS]; + } + else if (level.time == 0 && TimePlayed < 999) + { + time_final = 999; + score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS]; } - if (!tape.playing && setup.sound_loops) - StopSound(SND_GAME_LEVELTIME_BONUS); - } - else if (level.time == 0) /* level without time limit */ - { - if (!tape.playing && setup.sound_loops) - PlaySoundExt(SND_GAME_LEVELTIME_BONUS, SOUND_MAX_VOLUME, SOUND_MIDDLE, - SND_CTRL_PLAY_LOOP); + local_player->score_final = score_final; - while (TimePlayed < 999) + if (level_editor_test_game) { - if (!tape.playing && !setup.sound_loops) - PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE); + time = time_final; + score = score_final; - if (TimePlayed < 900 && TimePlayed % 10 == 0) - { - TimePlayed += 10; - RaiseScore(level.score[SC_TIME_BONUS] * 10); - } - else + DrawGameValue_Time(time); + DrawGameValue_Score(score); + } + + if (level.game_engine_type == GAME_ENGINE_TYPE_RND) + { + if (ExitX >= 0 && ExitY >= 0) /* local player has left the level */ { - TimePlayed++; - RaiseScore(level.score[SC_TIME_BONUS]); + /* close exit door after last player */ + if ((AllPlayersGone && + (Feld[ExitX][ExitY] == EL_EXIT_OPEN || + Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN || + Feld[ExitX][ExitY] == EL_STEEL_EXIT_OPEN)) || + Feld[ExitX][ExitY] == EL_EM_EXIT_OPEN || + Feld[ExitX][ExitY] == EL_EM_STEEL_EXIT_OPEN) + { + int element = Feld[ExitX][ExitY]; + +#if 0 + if (element == EL_EM_EXIT_OPEN || + element == EL_EM_STEEL_EXIT_OPEN) + { + Bang(ExitX, ExitY); + } + else +#endif + { + Feld[ExitX][ExitY] = + (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: + element == EL_STEEL_EXIT_OPEN ? EL_STEEL_EXIT_CLOSING: + EL_EM_STEEL_EXIT_CLOSING); + + PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING); + } + } + + /* player disappears */ + DrawLevelField(ExitX, ExitY); } - DrawGameValue_Time(TimePlayed); + for (i = 0; i < MAX_PLAYERS; i++) + { + struct PlayerInfo *player = &stored_player[i]; - BackToFront(); + if (player->present) + { + RemovePlayer(player); - if (!tape.playing) - Delay(10); + /* player disappears */ + DrawLevelField(player->jx, player->jy); + } + } } - if (!tape.playing && setup.sound_loops) - StopSound(SND_GAME_LEVELTIME_BONUS); + PlaySound(SND_GAME_WINNING); + } + + if (game_over_delay_1 > 0) + { + game_over_delay_1--; + + return; } - /* close exit door after last player */ - if (AllPlayersGone && ExitX >= 0 && ExitY >= 0 && - (Feld[ExitX][ExitY] == EL_EXIT_OPEN || - Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN)) + if (time != time_final) { - int element = Feld[ExitX][ExitY]; + int time_to_go = ABS(time_final - time); + int time_count_dir = (time < time_final ? +1 : -1); + int time_count_steps = (time_to_go > 100 && time_to_go % 10 == 0 ? 10 : 1); - Feld[ExitX][ExitY] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING : - EL_SP_EXIT_CLOSING); + time += time_count_steps * time_count_dir; + score += time_count_steps * level.score[SC_TIME_BONUS]; + + DrawGameValue_Time(time); + DrawGameValue_Score(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); - PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING); + return; } - /* player disappears */ - if (ExitX >= 0 && ExitY >= 0) - DrawLevelField(ExitX, ExitY); + local_player->LevelSolved_PanelOff = TRUE; - BackToFront(); + if (game_over_delay_2 > 0) + { + game_over_delay_2--; -#if 0 - if (tape.playing) - printf("::: TAPE PLAYING -> DO NOT SAVE SCORE\n"); - else - printf("::: NO TAPE PLAYING -> SAVING SCORE\n"); + return; + } + +#if 1 + GameEnd(); #endif +} - if (tape.playing) - return; +void GameEnd() +{ + int hi_pos; + boolean raise_level = FALSE; + + local_player->LevelSolved_GameEnd = TRUE; CloseDoor(DOOR_CLOSE_1); - if (tape.recording) + if (local_player->LevelSolved_SaveTape) { +#if 0 TapeStop(); - SaveTape(tape.level_nr); /* Ask to save tape */ +#endif + +#if 1 + SaveTapeChecked(tape.level_nr); /* ask to save tape */ +#else + SaveTape(tape.level_nr); /* ask to save tape */ +#endif + } + + if (level_editor_test_game) + { + game_status = GAME_MODE_MAIN; + +#if 1 + DrawAndFadeInMainMenu(REDRAW_FIELD); +#else + DrawMainMenu(); +#endif + + return; + } + + if (!local_player->LevelSolved_SaveScore) + { +#if 1 + if (fading.anim_mode == ANIM_CROSSFADE) + FadeCrossSaveBackbuffer(); + else + FadeOut(REDRAW_FIELD); +#else + FadeOut(REDRAW_FIELD); +#endif + + game_status = GAME_MODE_MAIN; + + DrawAndFadeInMainMenu(REDRAW_FIELD); + + return; } if (level_nr == leveldir_current->handicap_level) @@ -2685,15 +3689,15 @@ void GameWon() SaveLevelSetup_SeriesInfo(); } - if (level_editor_test_game) - local_player->score = -1; /* no highscore when playing from editor */ - else if (level_nr < leveldir_current->last_level) - raise_level = TRUE; /* advance to next level */ + if (level_nr < leveldir_current->last_level) + raise_level = TRUE; /* advance to next level */ if ((hi_pos = NewHiScore()) >= 0) { game_status = GAME_MODE_SCORES; + DrawHallOfFame(hi_pos); + if (raise_level) { level_nr++; @@ -2702,16 +3706,25 @@ void GameWon() } else { +#if 1 + if (fading.anim_mode == ANIM_CROSSFADE) + FadeCrossSaveBackbuffer(); + else + FadeOut(REDRAW_FIELD); +#else + FadeOut(REDRAW_FIELD); +#endif + game_status = GAME_MODE_MAIN; + if (raise_level) { level_nr++; TapeErase(); } - DrawMainMenu(); - } - BackToFront(); + DrawAndFadeInMainMenu(REDRAW_FIELD); + } } int NewHiScore() @@ -2721,13 +3734,13 @@ int NewHiScore() LoadScore(level_nr); - if (strcmp(setup.player_name, EMPTY_PLAYER_NAME) == 0 || - local_player->score < highscore[MAX_SCORE_ENTRIES - 1].Score) + if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) || + local_player->score_final < highscore[MAX_SCORE_ENTRIES - 1].Score) return -1; for (k = 0; k < MAX_SCORE_ENTRIES; k++) { - if (local_player->score > highscore[k].Score) + if (local_player->score_final > highscore[k].Score) { /* player has made it to the hall of fame */ @@ -2737,7 +3750,7 @@ int NewHiScore() #ifdef ONE_PER_NAME for (l = k; l < MAX_SCORE_ENTRIES; l++) - if (!strcmp(setup.player_name, highscore[l].Name)) + if (strEqual(setup.player_name, highscore[l].Name)) m = l; if (m == k) /* player's new highscore overwrites his old one */ goto put_into_list; @@ -2755,7 +3768,7 @@ int NewHiScore() #endif strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN); highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0'; - highscore[k].Score = local_player->score; + highscore[k].Score = local_player->score_final; position = k; break; } @@ -2774,10 +3787,9 @@ int NewHiScore() return position; } -inline static int getElementMoveStepsize(int x, int y) +inline static int getElementMoveStepsizeExt(int x, int y, int direction) { int element = Feld[x][y]; - int direction = MovDir[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); @@ -2787,24 +3799,21 @@ inline static int getElementMoveStepsize(int x, int y) /* special values for move stepsize for spring and things on conveyor belt */ if (horiz_move) { -#if 0 - if (element == EL_SPRING) - step = sign * MOVE_STEPSIZE_NORMAL * 2; - else if (CAN_FALL(element) && !CAN_MOVE(element) && - y < lev_fieldy - 1 && IS_BELT_ACTIVE(Feld[x][y + 1])) - step = sign * MOVE_STEPSIZE_NORMAL / 2; -#else 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; -#endif } return step; } +inline static int getElementMoveStepsize(int x, int y) +{ + return getElementMoveStepsizeExt(x, y, MovDir[x][y]); +} + void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir) { if (player->GfxAction != action || player->GfxDir != dir) @@ -2821,24 +3830,12 @@ void InitPlayerGfxAnimation(struct PlayerInfo *player, int action, int dir) } } -static void ResetRandomAnimationValue(int x, int y) -{ - GfxRandom[x][y] = INIT_GFX_RANDOM(); -} - -static void ResetGfxAnimation(int x, int y) +#if USE_GFX_RESET_GFX_ANIMATION +static void ResetGfxFrame(int x, int y, boolean redraw) { -#if 0 - int element, graphic; -#endif - - GfxFrame[x][y] = 0; - GfxAction[x][y] = ACTION_DEFAULT; - GfxDir[x][y] = MovDir[x][y]; - -#if 0 - element = Feld[x][y]; - graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + int element = Feld[x][y]; + int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + int last_gfx_frame = GfxFrame[x][y]; if (graphic_info[graphic].anim_global_sync) GfxFrame[x][y] = FrameCounter; @@ -2846,42 +3843,87 @@ static void ResetGfxAnimation(int x, int y) GfxFrame[x][y] = CustomValue[x][y]; else if (ANIM_MODE(graphic) == ANIM_CE_SCORE) GfxFrame[x][y] = element_info[element].collect_score; -#endif + else if (ANIM_MODE(graphic) == ANIM_CE_DELAY) + GfxFrame[x][y] = ChangeDelay[x][y]; + + if (redraw && GfxFrame[x][y] != last_gfx_frame) + DrawLevelGraphicAnimation(x, y, graphic); } +#endif -void InitMovingField(int x, int y, int direction) +static void ResetGfxAnimation(int x, int y) { - int element = Feld[x][y]; -#if 0 - int graphic; -#endif + GfxAction[x][y] = ACTION_DEFAULT; + GfxDir[x][y] = MovDir[x][y]; + GfxFrame[x][y] = 0; + +#if USE_GFX_RESET_GFX_ANIMATION + ResetGfxFrame(x, y, FALSE); +#endif +} + +static void ResetRandomAnimationValue(int x, int y) +{ + GfxRandom[x][y] = INIT_GFX_RANDOM(); +} + +void InitMovingField(int x, int y, int direction) +{ + 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 newx = x + dx; int newy = y + dy; + boolean is_moving_before, is_moving_after; +#if 0 + boolean continues_moving = (WasJustMoving[x][y] && direction == MovDir[x][y]); +#endif + + /* check if element was/is moving or being moved before/after mode change */ +#if 1 +#if 1 + is_moving_before = (WasJustMoving[x][y] != 0); +#else + /* (!!! this does not work -- WasJustMoving is NOT a boolean value !!!) */ + is_moving_before = WasJustMoving[x][y]; +#endif +#else + is_moving_before = (getElementMoveStepsizeExt(x, y, MovDir[x][y]) != 0); +#endif + is_moving_after = (getElementMoveStepsizeExt(x, y, direction) != 0); - if (!WasJustMoving[x][y] || direction != MovDir[x][y]) + /* reset animation only for moving elements which change direction of moving + or which just started or stopped moving + (else CEs with property "can move" / "not moving" are reset each frame) */ +#if USE_GFX_RESET_ONLY_WHEN_MOVING +#if 1 + if (is_moving_before != is_moving_after || + direction != MovDir[x][y]) + ResetGfxAnimation(x, y); +#else + if ((is_moving_before || is_moving_after) && !continues_moving) + ResetGfxAnimation(x, y); +#endif +#else + if (!continues_moving) ResetGfxAnimation(x, y); +#endif MovDir[x][y] = direction; GfxDir[x][y] = direction; + +#if USE_GFX_RESET_ONLY_WHEN_MOVING + GfxAction[x][y] = (!is_moving_after ? ACTION_WAITING : + direction == MV_DOWN && CAN_FALL(element) ? + ACTION_FALLING : ACTION_MOVING); +#else GfxAction[x][y] = (direction == MV_DOWN && CAN_FALL(element) ? ACTION_FALLING : ACTION_MOVING); - -#if 0 - graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); - - if (graphic_info[graphic].anim_global_sync) - GfxFrame[x][y] = FrameCounter; - else if (ANIM_MODE(graphic) == ANIM_CE_VALUE) - GfxFrame[x][y] = CustomValue[x][y]; - else if (ANIM_MODE(graphic) == ANIM_CE_SCORE) - GfxFrame[x][y] = element_info[element].collect_score; #endif /* this is needed for CEs with property "can move" / "not moving" */ - if (getElementMoveStepsize(x, y) != 0) /* moving or being moved */ + if (is_moving_after) { if (Feld[newx][newy] == EL_EMPTY) Feld[newx][newy] = EL_BLOCKED; @@ -2902,13 +3944,8 @@ void InitMovingField(int x, int y, int direction) void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y) { int direction = MovDir[x][y]; -#if 1 int newx = x + (direction & MV_LEFT ? -1 : direction & MV_RIGHT ? +1 : 0); int newy = y + (direction & MV_UP ? -1 : direction & MV_DOWN ? +1 : 0); -#else - int newx = x + (direction == MV_LEFT ? -1 : direction == MV_RIGHT ? +1 : 0); - int newy = y + (direction == MV_UP ? -1 : direction == MV_DOWN ? +1 : 0); -#endif *goes_to_x = newx; *goes_to_y = newy; @@ -3033,8 +4070,10 @@ 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]); @@ -3095,38 +4134,113 @@ void CheckDynamite(int x, int y) Bang(x, y); } -void DrawRelocatePlayer(struct PlayerInfo *player) +static void setMinimalPlayerBoundaries(int *sx1, int *sy1, int *sx2, int *sy2) +{ + boolean num_checked_players = 0; + int i; + + for (i = 0; i < MAX_PLAYERS; i++) + { + if (stored_player[i].active) + { + int sx = stored_player[i].jx; + int sy = stored_player[i].jy; + + if (num_checked_players == 0) + { + *sx1 = *sx2 = sx; + *sy1 = *sy2 = sy; + } + else + { + *sx1 = MIN(*sx1, sx); + *sy1 = MIN(*sy1, sy); + *sx2 = MAX(*sx2, sx); + *sy2 = MAX(*sy2, sy); + } + + num_checked_players++; + } + } +} + +static boolean checkIfAllPlayersFitToScreen_RND() +{ + int sx1 = 0, sy1 = 0, sx2 = 0, sy2 = 0; + + setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2); + + return (sx2 - sx1 < SCR_FIELDX && + sy2 - sy1 < SCR_FIELDY); +} + +static void setScreenCenteredToAllPlayers(int *sx, int *sy) +{ + int sx1 = scroll_x, sy1 = scroll_y, sx2 = scroll_x, sy2 = scroll_y; + + setMinimalPlayerBoundaries(&sx1, &sy1, &sx2, &sy2); + + *sx = (sx1 + sx2) / 2; + *sy = (sy1 + sy2) / 2; +} + +void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir, + boolean center_screen, boolean quick_relocation) { boolean ffwd_delay = (tape.playing && tape.fast_forward); boolean no_delay = (tape.warp_forward); int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay); int wait_delay_value = (no_delay ? 0 : frame_delay_value); - int jx = player->jx; - int jy = player->jy; - if (level.instant_relocation) + if (quick_relocation) { int offset = (setup.scroll_delay ? 3 : 0); - if (!IN_VIS_FIELD(SCREENX(jx), SCREENY(jy))) + if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen) { - scroll_x = (local_player->jx < SBX_Left + MIDPOSX ? SBX_Left : - local_player->jx > SBX_Right + MIDPOSX ? SBX_Right : - local_player->jx - MIDPOSX); + if (center_screen) + { + scroll_x = (x < SBX_Left + MIDPOSX ? SBX_Left : + x > SBX_Right + MIDPOSX ? SBX_Right : + x - MIDPOSX); + + scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper : + y > SBY_Lower + MIDPOSY ? SBY_Lower : + y - MIDPOSY); + } + else + { + /* quick relocation (without scrolling), but do not center screen */ - scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper : - local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower : - local_player->jy - MIDPOSY); + int center_scroll_x = (old_x < SBX_Left + MIDPOSX ? SBX_Left : + old_x > SBX_Right + MIDPOSX ? SBX_Right : + old_x - MIDPOSX); + + int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper : + old_y > SBY_Lower + MIDPOSY ? SBY_Lower : + old_y - MIDPOSY); + + int offset_x = x + (scroll_x - center_scroll_x); + int offset_y = y + (scroll_y - center_scroll_y); + + scroll_x = (offset_x < SBX_Left + MIDPOSX ? SBX_Left : + offset_x > SBX_Right + MIDPOSX ? SBX_Right : + offset_x - MIDPOSX); + + scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper : + offset_y > SBY_Lower + MIDPOSY ? SBY_Lower : + offset_y - MIDPOSY); + } } else { - if ((player->MovDir == MV_LEFT && scroll_x > jx - MIDPOSX + offset) || - (player->MovDir == MV_RIGHT && scroll_x < jx - MIDPOSX - offset)) - scroll_x = jx - MIDPOSX + (scroll_x < jx-MIDPOSX ? -offset : +offset); + if ((move_dir == MV_LEFT && scroll_x > x - MIDPOSX + offset) || + (move_dir == MV_RIGHT && scroll_x < x - MIDPOSX - offset)) + scroll_x = x - MIDPOSX + (scroll_x < x - MIDPOSX ? -offset : +offset); - if ((player->MovDir == MV_UP && scroll_y > jy - MIDPOSY + offset) || - (player->MovDir == MV_DOWN && scroll_y < jy - MIDPOSY - offset)) - scroll_y = jy - MIDPOSY + (scroll_y < jy-MIDPOSY ? -offset : +offset); + if ((move_dir == MV_UP && scroll_y > y - MIDPOSY + offset) || + (move_dir == MV_DOWN && scroll_y < y - MIDPOSY - offset)) + scroll_y = y - MIDPOSY + (scroll_y < y - MIDPOSY ? -offset : +offset); /* don't scroll over playfield boundaries */ if (scroll_x < SBX_Left || scroll_x > SBX_Right) @@ -3141,23 +4255,21 @@ void DrawRelocatePlayer(struct PlayerInfo *player) } else { - int scroll_xx = -999, scroll_yy = -999; + int scroll_xx = (x < SBX_Left + MIDPOSX ? SBX_Left : + x > SBX_Right + MIDPOSX ? SBX_Right : + x - MIDPOSX); + + int scroll_yy = (y < SBY_Upper + MIDPOSY ? SBY_Upper : + y > SBY_Lower + MIDPOSY ? SBY_Lower : + y - MIDPOSY); ScrollScreen(NULL, SCROLL_GO_ON); /* scroll last frame to full tile */ - while (scroll_xx != scroll_x || scroll_yy != scroll_y) + while (scroll_x != scroll_xx || scroll_y != scroll_yy) { int dx = 0, dy = 0; int fx = FX, fy = FY; - scroll_xx = (local_player->jx < SBX_Left + MIDPOSX ? SBX_Left : - local_player->jx > SBX_Right + MIDPOSX ? SBX_Right : - local_player->jx - MIDPOSX); - - scroll_yy = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper : - local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower : - local_player->jy - MIDPOSY); - dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0); dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0); @@ -3183,7 +4295,7 @@ void DrawRelocatePlayer(struct PlayerInfo *player) Delay(wait_delay_value); } - DrawPlayer(player); + DrawAllPlayers(); BackToFront(); Delay(wait_delay_value); } @@ -3264,8 +4376,9 @@ void RelocatePlayer(int jx, int jy, int el_player_raw) InitField(jx, jy, FALSE); } - if (player == local_player) /* only visually relocate local player */ - DrawRelocatePlayer(player); + /* only visually relocate centered player */ + DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir, + FALSE, level.instant_relocation); TestIfPlayerTouchesBadThing(jx, jy); TestIfPlayerTouchesCustomElement(jx, jy); @@ -3351,11 +4464,7 @@ void Explode(int ex, int ey, int phase, int mode) PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING); #endif -#if 1 last_phase = element_info[explosion_element].explosion_delay + 1; -#else - last_phase = element_info[center_element].explosion_delay + 1; -#endif for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++) { @@ -3428,33 +4537,14 @@ void Explode(int ex, int ey, int phase, int mode) if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey)) { -#if 1 int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1; Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr; -#else - switch(StorePlayer[ex][ey]) - { - case EL_PLAYER_2: - Store[x][y] = EL_PLAYER_IS_EXPLODING_2; - break; - case EL_PLAYER_3: - Store[x][y] = EL_PLAYER_IS_EXPLODING_3; - break; - case EL_PLAYER_4: - Store[x][y] = EL_PLAYER_IS_EXPLODING_4; - break; - case EL_PLAYER_1: - default: - Store[x][y] = EL_PLAYER_IS_EXPLODING_1; - break; - } -#endif if (PLAYERINFO(ex, ey)->use_murphy) Store[x][y] = EL_EMPTY; } -#if 1 + /* !!! 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)) @@ -3475,45 +4565,6 @@ void Explode(int ex, int ey, int phase, int mode) #endif else Store[x][y] = EL_EMPTY; -#else - else if (center_element == EL_MOLE) - Store[x][y] = EL_EMERALD_RED; - else if (center_element == EL_PENGUIN) - Store[x][y] = EL_EMERALD_PURPLE; - else if (center_element == EL_BUG) - Store[x][y] = ((x == ex && y == ey) ? EL_DIAMOND : EL_EMERALD); - else if (center_element == EL_BD_BUTTERFLY) - Store[x][y] = EL_BD_DIAMOND; - else if (center_element == EL_SP_ELECTRON) - Store[x][y] = EL_SP_INFOTRON; - else if (center_element == EL_AMOEBA_TO_DIAMOND) - Store[x][y] = level.amoeba_content; - else if (center_element == EL_YAMYAM) - Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy]; - else if (IS_CUSTOM_ELEMENT(center_element) && - element_info[center_element].content.e[xx][yy] != EL_EMPTY) - Store[x][y] = element_info[center_element].content.e[xx][yy]; - else if (element == EL_WALL_EMERALD) - Store[x][y] = EL_EMERALD; - else if (element == EL_WALL_DIAMOND) - Store[x][y] = EL_DIAMOND; - else if (element == EL_WALL_BD_DIAMOND) - Store[x][y] = EL_BD_DIAMOND; - else if (element == EL_WALL_EMERALD_YELLOW) - Store[x][y] = EL_EMERALD_YELLOW; - else if (element == EL_WALL_EMERALD_RED) - Store[x][y] = EL_EMERALD_RED; - else if (element == EL_WALL_EMERALD_PURPLE) - Store[x][y] = EL_EMERALD_PURPLE; - else if (element == EL_WALL_PEARL) - Store[x][y] = EL_PEARL; - else if (element == EL_WALL_CRYSTAL) - Store[x][y] = EL_CRYSTAL; - else if (IS_CUSTOM_ELEMENT(element) && !CAN_EXPLODE(element)) - Store[x][y] = element_info[element].content.e[1][1]; - else - Store[x][y] = EL_EMPTY; -#endif if (x != ex || y != ey || mode == EX_TYPE_BORDER || center_element == EL_AMOEBA_TO_DIAMOND) @@ -3556,6 +4607,11 @@ void Explode(int ex, int ey, int phase, int mode) #endif #if 1 +#if 1 + /* this can happen if the player leaves an explosion just in time */ + if (GfxElement[x][y] == EL_UNDEFINED) + GfxElement[x][y] = EL_EMPTY; +#else if (GfxElement[x][y] == EL_UNDEFINED) { printf("\n\n"); @@ -3565,6 +4621,8 @@ void Explode(int ex, int ey, int phase, int mode) GfxElement[x][y] = EL_EMPTY; } +#endif + #endif border_element = Store2[x][y]; @@ -3753,7 +4811,7 @@ void Bang(int x, int y) } } - switch(element) + switch (element) { case EL_BUG: case EL_SPACESHIP: @@ -3777,6 +4835,14 @@ void Bang(int x, int y) explosion_type = EX_TYPE_DYNA; break; + case EL_DC_LANDMINE: +#if 0 + case EL_EM_EXIT_OPEN: + case EL_EM_STEEL_EXIT_OPEN: +#endif + explosion_type = EX_TYPE_CENTER; + break; + case EL_PENGUIN: case EL_LAMP: case EL_LAMP_ACTIVE: @@ -3852,11 +4918,7 @@ static void InitBeltMovement() } } -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { int element = Feld[x][y]; @@ -3936,11 +4998,7 @@ static void ToggleBeltSwitch(int x, int y) graphic_info[graphic].anim_mode |= ANIM_REVERSE; } -#if 1 SCAN_PLAYFIELD(xx, yy) -#else - for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++) -#endif { int element = Feld[xx][yy]; @@ -3987,20 +5045,45 @@ static void ToggleSwitchgateSwitch(int x, int y) game.switchgate_pos = !game.switchgate_pos; -#if 1 SCAN_PLAYFIELD(xx, yy) -#else - for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++) -#endif { int element = Feld[xx][yy]; +#if !USE_BOTH_SWITCHGATE_SWITCHES if (element == EL_SWITCHGATE_SWITCH_UP || element == EL_SWITCHGATE_SWITCH_DOWN) { Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP + game.switchgate_pos; DrawLevelField(xx, yy); } + else if (element == EL_DC_SWITCHGATE_SWITCH_UP || + element == EL_DC_SWITCHGATE_SWITCH_DOWN) + { + Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP + game.switchgate_pos; + DrawLevelField(xx, yy); + } +#else + if (element == EL_SWITCHGATE_SWITCH_UP) + { + Feld[xx][yy] = EL_SWITCHGATE_SWITCH_DOWN; + DrawLevelField(xx, yy); + } + else if (element == EL_SWITCHGATE_SWITCH_DOWN) + { + Feld[xx][yy] = EL_SWITCHGATE_SWITCH_UP; + DrawLevelField(xx, yy); + } + else if (element == EL_DC_SWITCHGATE_SWITCH_UP) + { + Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_DOWN; + DrawLevelField(xx, yy); + } + else if (element == EL_DC_SWITCHGATE_SWITCH_DOWN) + { + Feld[xx][yy] = EL_DC_SWITCHGATE_SWITCH_UP; + DrawLevelField(xx, yy); + } +#endif else if (element == EL_SWITCHGATE_OPEN || element == EL_SWITCHGATE_OPENING) { @@ -4038,11 +5121,7 @@ static void RedrawAllLightSwitchesAndInvisibleElements() { int x, y; -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { int element = Feld[x][y]; @@ -4103,11 +5182,7 @@ static void RedrawAllInvisibleElementsForLenses() { int x, y; -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { int element = Feld[x][y]; @@ -4156,11 +5231,7 @@ static void RedrawAllInvisibleElementsForMagnifier() { int x, y; -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { int element = Feld[x][y]; @@ -4220,11 +5291,7 @@ static void ActivateTimegateSwitch(int x, int y) game.timegate_time_left = level.time_timegate * FRAMES_PER_SECOND; -#if 1 SCAN_PLAYFIELD(xx, yy) -#else - for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++) -#endif { int element = Feld[xx][yy]; @@ -4232,7 +5299,7 @@ static void ActivateTimegateSwitch(int x, int y) element == EL_TIMEGATE_CLOSING) { Feld[xx][yy] = EL_TIMEGATE_OPENING; - PlayLevelSound(xx, yy, SND_TIMEGATE_OPENING); + PlayLevelSound(xx, yy, SND_CLASS_TIMEGATE_OPENING); } /* @@ -4245,7 +5312,12 @@ static void ActivateTimegateSwitch(int x, int y) } +#if 1 + Feld[x][y] = (Feld[x][y] == EL_TIMEGATE_SWITCH ? EL_TIMEGATE_SWITCH_ACTIVE : + EL_DC_TIMEGATE_SWITCH_ACTIVE); +#else Feld[x][y] = EL_TIMEGATE_SWITCH_ACTIVE; +#endif } void Impact(int x, int y) @@ -4280,6 +5352,16 @@ void Impact(int x, int y) object_hit = TRUE; } + + if (Feld[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; + DrawLevelField(x, y + 2); + + object_hit = TRUE; + } #endif if (object_hit) @@ -4308,7 +5390,8 @@ void Impact(int x, int y) Bang(x, y); return; } - else if (impact && element == EL_PEARL) + else if (impact && element == EL_PEARL && + smashed != EL_DC_MAGIC_WALL && smashed != EL_DC_MAGIC_WALL_ACTIVE) { ResetGfxAnimation(x, y); @@ -4341,30 +5424,33 @@ void Impact(int x, int y) if (object_hit) /* check which object was hit */ { - if (CAN_PASS_MAGIC_WALL(element) && - (smashed == EL_MAGIC_WALL || - smashed == EL_BD_MAGIC_WALL)) + if ((CAN_PASS_MAGIC_WALL(element) && + (smashed == EL_MAGIC_WALL || + smashed == EL_BD_MAGIC_WALL)) || + (CAN_PASS_DC_MAGIC_WALL(element) && + smashed == EL_DC_MAGIC_WALL)) { int xx, yy; int activated_magic_wall = (smashed == EL_MAGIC_WALL ? EL_MAGIC_WALL_ACTIVE : - EL_BD_MAGIC_WALL_ACTIVE); + smashed == EL_BD_MAGIC_WALL ? EL_BD_MAGIC_WALL_ACTIVE : + EL_DC_MAGIC_WALL_ACTIVE); /* activate magic wall / mill */ -#if 1 SCAN_PLAYFIELD(xx, yy) -#else - for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++) -#endif + { if (Feld[xx][yy] == smashed) Feld[xx][yy] = activated_magic_wall; + } game.magic_wall_time_left = level.time_magic_wall * FRAMES_PER_SECOND; game.magic_wall_active = TRUE; PlayLevelSound(x, y, (smashed == EL_MAGIC_WALL ? SND_MAGIC_WALL_ACTIVATING : - SND_BD_MAGIC_WALL_ACTIVATING)); + smashed == EL_BD_MAGIC_WALL ? + SND_BD_MAGIC_WALL_ACTIVATING : + SND_DC_MAGIC_WALL_ACTIVATING)); } if (IS_PLAYER(x, y + 1)) @@ -4444,7 +5530,9 @@ void Impact(int x, int y) ToggleBeltSwitch(x, y + 1); } else if (smashed == EL_SWITCHGATE_SWITCH_UP || - smashed == EL_SWITCHGATE_SWITCH_DOWN) + smashed == EL_SWITCHGATE_SWITCH_DOWN || + smashed == EL_DC_SWITCHGATE_SWITCH_UP || + smashed == EL_DC_SWITCHGATE_SWITCH_DOWN) { ToggleSwitchgateSwitch(x, y + 1); } @@ -4477,12 +5565,15 @@ 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_BD_MAGIC_WALL_ACTIVE || + Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE)) { if (Feld[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) PlayLevelSound(x, y, SND_BD_MAGIC_WALL_FILLING); + else if (Feld[x][y + 1] == EL_DC_MAGIC_WALL_ACTIVE) + PlayLevelSound(x, y, SND_DC_MAGIC_WALL_FILLING); return; } @@ -4839,7 +5930,10 @@ inline static void TurnRoundExt(int x, int y) int ex = x + xy[i][0]; int ey = y + xy[i][1]; - if (IN_LEV_FIELD(ex, ey) && Feld[ex][ey] == EL_EXIT_OPEN) + 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)) { attr_x = ex; attr_y = ey; @@ -5306,9 +6400,6 @@ inline static void TurnRoundExt(int x, int y) static void TurnRound(int x, int y) { int direction = MovDir[x][y]; -#if 1 - int element, graphic; -#endif TurnRoundExt(x, y); @@ -5320,17 +6411,7 @@ static void TurnRound(int x, int y) if (MovDelay[x][y]) GfxAction[x][y] = ACTION_TURNING_FROM_LEFT + MV_DIR_TO_BIT(direction); -#if 1 - element = Feld[x][y]; - graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); - - if (graphic_info[graphic].anim_global_sync) - GfxFrame[x][y] = FrameCounter; - else if (ANIM_MODE(graphic) == ANIM_CE_VALUE) - GfxFrame[x][y] = CustomValue[x][y]; - else if (ANIM_MODE(graphic) == ANIM_CE_SCORE) - GfxFrame[x][y] = element_info[element].collect_score; -#endif + ResetGfxFrame(x, y, FALSE); } static boolean JustBeingPushed(int x, int y) @@ -5380,7 +6461,12 @@ void StartMoving(int x, int y) started_moving = TRUE; Feld[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; +#else Store[x][y] = EL_ROCK; +#endif PlayLevelSoundAction(x, y, ACTION_EMPTYING); } @@ -5404,6 +6490,43 @@ void StartMoving(int x, int y) PlayLevelSoundAction(x, y, ACTION_FILLING); } } + else if (element == EL_QUICKSAND_FAST_FULL) + { + if (IS_FREE(x, y + 1)) + { + InitMovingField(x, y, MV_DOWN); + started_moving = TRUE; + + Feld[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; +#else + Store[x][y] = EL_ROCK; +#endif + + PlayLevelSoundAction(x, y, ACTION_EMPTYING); + } + else if (Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY) + { + if (!MovDelay[x][y]) + MovDelay[x][y] = TILEY + 1; + + if (MovDelay[x][y]) + { + MovDelay[x][y]--; + if (MovDelay[x][y]) + return; + } + + Feld[x][y] = EL_QUICKSAND_FAST_EMPTY; + Feld[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 ((element == EL_ROCK || element == EL_BD_ROCK) && Feld[x][y + 1] == EL_QUICKSAND_EMPTY) { @@ -5415,6 +6538,17 @@ void StartMoving(int x, int y) PlayLevelSoundAction(x, y, ACTION_FILLING); } + else if ((element == EL_ROCK || element == EL_BD_ROCK) && + Feld[x][y + 1] == EL_QUICKSAND_FAST_EMPTY) + { + InitMovingField(x, y, MV_DOWN); + started_moving = TRUE; + + Feld[x][y] = EL_QUICKSAND_FAST_FILLING; + Store[x][y] = element; + + PlayLevelSoundAction(x, y, ACTION_FILLING); + } else if (element == EL_MAGIC_WALL_FULL) { if (IS_FREE(x, y + 1)) @@ -5451,7 +6585,7 @@ void StartMoving(int x, int y) started_moving = TRUE; Feld[x][y] = EL_BD_MAGIC_WALL_EMPTYING; - Store[x][y] = EL_CHANGED2(Store[x][y]); + Store[x][y] = EL_CHANGED_BD(Store[x][y]); } else if (Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE) { @@ -5467,20 +6601,52 @@ void StartMoving(int x, int y) Feld[x][y] = EL_BD_MAGIC_WALL_ACTIVE; Feld[x][y + 1] = EL_BD_MAGIC_WALL_FULL; - Store[x][y + 1] = EL_CHANGED2(Store[x][y]); + Store[x][y + 1] = EL_CHANGED_BD(Store[x][y]); + Store[x][y] = 0; + } + } + else if (element == EL_DC_MAGIC_WALL_FULL) + { + if (IS_FREE(x, y + 1)) + { + InitMovingField(x, y, MV_DOWN); + started_moving = TRUE; + + Feld[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) + { + if (!MovDelay[x][y]) + MovDelay[x][y] = TILEY/4 + 1; + + if (MovDelay[x][y]) + { + MovDelay[x][y]--; + if (MovDelay[x][y]) + return; + } + + Feld[x][y] = EL_DC_MAGIC_WALL_ACTIVE; + Feld[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)) + else if ((CAN_PASS_MAGIC_WALL(element) && + (Feld[x][y + 1] == EL_MAGIC_WALL_ACTIVE || + Feld[x][y + 1] == EL_BD_MAGIC_WALL_ACTIVE)) || + (CAN_PASS_DC_MAGIC_WALL(element) && + (Feld[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 : - EL_BD_MAGIC_WALL_FILLING); + Feld[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) @@ -5492,9 +6658,14 @@ void StartMoving(int x, int y) Store[x][y] = EL_ACID; } - else if ((game.engine_version >= VERSION_IDENT(3,1,0,0) && + else if ( +#if USE_FIX_IMPACT_COLLISION + (game.engine_version >= VERSION_IDENT(3,1,0,0) && + CheckImpact[x][y] && !IS_FREE(x, y + 1)) || +#else + (game.engine_version >= VERSION_IDENT(3,1,0,0) && CheckCollision[x][y] && !IS_FREE(x, y + 1)) || - +#endif (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))) || @@ -5514,6 +6685,7 @@ void StartMoving(int x, int y) simply not covered here... :-/ ) */ CheckCollision[x][y] = 0; + CheckImpact[x][y] = 0; Impact(x, y); } @@ -5838,7 +7010,10 @@ void StartMoving(int x, int y) } else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy)) { - if (Feld[newx][newy] == EL_EXIT_OPEN) + 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) { RemoveField(x, y); DrawLevelField(x, y); @@ -5850,7 +7025,7 @@ void StartMoving(int x, int y) local_player->friends_still_needed--; if (!local_player->friends_still_needed && !local_player->GameOver && AllPlayersGone) - local_player->LevelSolved = local_player->GameOver = TRUE; + PlayerWins(local_player); return; } @@ -6256,9 +7431,20 @@ void ContinueMoving(int x, int y) if (ABS(MovPos[x][y]) < TILEX) { - DrawLevelField(x, y); +#if 0 + int ee = Feld[x][y]; + int gg = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + int ff = getGraphicAnimationFrame(gg, GfxFrame[x][y]); - return; /* element is still moving */ + printf("::: %d.%d: moving %d ... [%d, %d, %d] [%d, %d, %d]\n", + x, y, ABS(MovPos[x][y]), + ee, gg, ff, + GfxAction[x][y], GfxDir[x][y], GfxFrame[x][y]); +#endif + + DrawLevelField(x, y); + + return; /* element is still moving */ } /* element reached destination field */ @@ -6287,6 +7473,16 @@ void ContinueMoving(int x, int y) Feld[x][y] = get_next_element(element); element = Feld[newx][newy] = Store[x][y]; } + else if (element == EL_QUICKSAND_FAST_FILLING) + { + element = Feld[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]; + } else if (element == EL_MAGIC_WALL_FILLING) { element = Feld[newx][newy] = get_next_element(element); @@ -6319,6 +7515,24 @@ void ContinueMoving(int x, int y) Feld[x][y] = EL_BD_MAGIC_WALL_DEAD; element = Feld[newx][newy] = Store[x][y]; +#if USE_NEW_CUSTOM_VALUE + InitField(newx, newy, FALSE); +#endif + } + else if (element == EL_DC_MAGIC_WALL_FILLING) + { + element = Feld[newx][newy] = get_next_element(element); + if (!game.magic_wall_active) + element = Feld[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); + if (!game.magic_wall_active) + Feld[x][y] = EL_DC_MAGIC_WALL_DEAD; + element = Feld[newx][newy] = Store[x][y]; + #if USE_NEW_CUSTOM_VALUE InitField(newx, newy, FALSE); #endif @@ -6346,29 +7560,17 @@ void ContinueMoving(int x, int y) MovDelay[newx][newy] = 0; -#if 1 if (CAN_CHANGE_OR_HAS_ACTION(element)) -#else - if (CAN_CHANGE(element)) -#endif { /* copy element change control values to new field */ ChangeDelay[newx][newy] = ChangeDelay[x][y]; ChangePage[newx][newy] = ChangePage[x][y]; ChangeCount[newx][newy] = ChangeCount[x][y]; ChangeEvent[newx][newy] = ChangeEvent[x][y]; - -#if 0 -#if USE_NEW_CUSTOM_VALUE - CustomValue[newx][newy] = CustomValue[x][y]; -#endif -#endif } -#if 1 #if USE_NEW_CUSTOM_VALUE CustomValue[newx][newy] = CustomValue[x][y]; -#endif #endif ChangeDelay[x][y] = 0; @@ -6463,13 +7665,18 @@ void ContinueMoving(int x, int y) int nextx = newx + dx, nexty = newy + dy; boolean check_collision_again = IN_LEV_FIELD_AND_IS_FREE(nextx, nexty); - WasJustMoving[newx][newy] = 3; + WasJustMoving[newx][newy] = CHECK_DELAY_MOVING; if (CAN_FALL(element) && direction == MV_DOWN) - WasJustFalling[newx][newy] = 3; + WasJustFalling[newx][newy] = CHECK_DELAY_FALLING; if ((!CAN_FALL(element) || direction == MV_DOWN) && check_collision_again) - CheckCollision[newx][newy] = 2; + CheckCollision[newx][newy] = CHECK_DELAY_COLLISION; + +#if USE_FIX_IMPACT_COLLISION + if (CAN_FALL(element) && direction == MV_DOWN && check_collision_again) + CheckImpact[newx][newy] = CHECK_DELAY_IMPACT; +#endif } if (DONT_TOUCH(element)) /* object may be nasty to player or others */ @@ -6534,12 +7741,10 @@ void ContinueMoving(int x, int y) TestIfPlayerTouchesCustomElement(newx, newy); TestIfElementTouchesCustomElement(newx, newy); -#if 1 if (IS_CUSTOM_ELEMENT(element) && ei->move_enter_element != EL_EMPTY && IS_EQUAL_OR_IN_GROUP(stored_new, ei->move_enter_element)) CheckElementChangeBySide(newx, newy, element, stored_new, CE_DIGGING_X, - MV_DIR_OPPOSITE(direction)); -#endif + MV_DIR_OPPOSITE(direction)); } int AmoebeNachbarNr(int ax, int ay) @@ -6608,11 +7813,7 @@ void AmoebenVereinigen(int ax, int ay) AmoebaCnt2[new_group_nr] += AmoebaCnt2[old_group_nr]; AmoebaCnt2[old_group_nr] = 0; -#if 1 SCAN_PLAYFIELD(xx, yy) -#else - for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++) -#endif { if (AmoebaNr[xx][yy] == old_group_nr) AmoebaNr[xx][yy] = new_group_nr; @@ -6638,11 +7839,7 @@ void AmoebeUmwandeln(int ax, int ay) } #endif -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { if (Feld[x][y] == EL_AMOEBA_DEAD && AmoebaNr[x][y] == group_nr) { @@ -6700,11 +7897,7 @@ void AmoebeUmwandelnBD(int ax, int ay, int new_element) } #endif -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { if (AmoebaNr[x][y] == group_nr && (Feld[x][y] == EL_AMOEBA_DEAD || @@ -6843,7 +8036,8 @@ void AmoebeAbleger(int ax, int ay) if (IS_FREE(x, y) || CAN_GROW_INTO(Feld[x][y]) || - Feld[x][y] == EL_QUICKSAND_EMPTY) + Feld[x][y] == EL_QUICKSAND_EMPTY || + Feld[x][y] == EL_QUICKSAND_FAST_EMPTY) { newax = x; neway = y; @@ -6868,7 +8062,8 @@ void AmoebeAbleger(int ax, int ay) if (IS_FREE(x, y) || CAN_GROW_INTO(Feld[x][y]) || - Feld[x][y] == EL_QUICKSAND_EMPTY) + Feld[x][y] == EL_QUICKSAND_EMPTY || + Feld[x][y] == EL_QUICKSAND_FAST_EMPTY) { newax = x; neway = y; @@ -6953,9 +8148,6 @@ void AmoebeAbleger(int ax, int ay) void Life(int ax, int ay) { int x1, y1, x2, y2; -#if 0 - static int life[4] = { 2, 3, 3, 3 }; /* parameters for "game of life" */ -#endif int life_time = 40; int element = Feld[ax][ay]; int graphic = el2img(element); @@ -7056,7 +8248,7 @@ static void InitTimegateWheel(int x, int y) static void RunTimegateWheel(int x, int y) { - PlayLevelSound(x, y, SND_TIMEGATE_SWITCH_ACTIVE); + PlayLevelSound(x, y, SND_CLASS_TIMEGATE_SWITCH_ACTIVE); } static void InitMagicBallDelay(int x, int y) @@ -7100,13 +8292,6 @@ static void ActivateMagicBall(int bx, int by) game.ball_content_nr = (game.ball_content_nr + 1) % level.num_ball_contents; } -static void InitDiagonalMovingElement(int x, int y) -{ -#if 0 - MovDelay[x][y] = level.android_move_time; -#endif -} - void CheckExit(int x, int y) { if (local_player->gems_still_needed > 0 || @@ -7130,6 +8315,75 @@ void CheckExit(int x, int y) PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING); } +void CheckExitEM(int x, int y) +{ + if (local_player->gems_still_needed > 0 || + local_player->sokobanfields_still_needed > 0 || + local_player->lights_still_needed > 0) + { + int element = Feld[x][y]; + int graphic = el2img(element); + + if (IS_ANIMATED(graphic)) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + + return; + } + + if (AllPlayersGone) /* do not re-open exit door closed after last player */ + return; + + Feld[x][y] = EL_EM_EXIT_OPENING; + + PlayLevelSoundNearest(x, y, SND_CLASS_EM_EXIT_OPENING); +} + +void CheckExitSteel(int x, int y) +{ + if (local_player->gems_still_needed > 0 || + local_player->sokobanfields_still_needed > 0 || + local_player->lights_still_needed > 0) + { + int element = Feld[x][y]; + int graphic = el2img(element); + + if (IS_ANIMATED(graphic)) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + + return; + } + + if (AllPlayersGone) /* do not re-open exit door closed after last player */ + return; + + Feld[x][y] = EL_STEEL_EXIT_OPENING; + + PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING); +} + +void CheckExitSteelEM(int x, int y) +{ + if (local_player->gems_still_needed > 0 || + local_player->sokobanfields_still_needed > 0 || + local_player->lights_still_needed > 0) + { + int element = Feld[x][y]; + int graphic = el2img(element); + + if (IS_ANIMATED(graphic)) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + + return; + } + + if (AllPlayersGone) /* do not re-open exit door closed after last player */ + return; + + Feld[x][y] = EL_EM_STEEL_EXIT_OPENING; + + PlayLevelSoundNearest(x, y, SND_CLASS_EM_STEEL_EXIT_OPENING); +} + void CheckExitSP(int x, int y) { if (local_player->gems_still_needed > 0) @@ -7155,11 +8409,7 @@ static void CloseAllOpenTimegates() { int x, y; -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { int element = Feld[x][y]; @@ -7172,7 +8422,7 @@ static void CloseAllOpenTimegates() } } -void EdelsteinFunkeln(int x, int y) +void DrawTwinkleOnField(int x, int y) { if (!IN_SCR_FIELD(SCREENX(x), SCREENY(y)) || IS_MOVING(x, y)) return; @@ -7181,7 +8431,7 @@ void EdelsteinFunkeln(int x, int y) return; if (MovDelay[x][y] == 0) /* next animation frame */ - MovDelay[x][y] = 11 * !SimpleRND(500); + MovDelay[x][y] = 11 * !GetSimpleRandom(500); if (MovDelay[x][y] != 0) /* wait some time before next frame */ { @@ -7323,7 +8573,8 @@ void MauerAbleger(int ax, int ay) if (element == EL_EXPANDABLE_WALL_HORIZONTAL || element == EL_EXPANDABLE_WALL_ANY || - element == EL_EXPANDABLE_WALL) + element == EL_EXPANDABLE_WALL || + element == EL_BD_EXPANDABLE_WALL) { if (links_frei) { @@ -7371,6 +8622,108 @@ void MauerAbleger(int ax, int ay) PlayLevelSoundAction(ax, ay, ACTION_GROWING); } +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 (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1)) + oben_frei = TRUE; + if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1)) + unten_frei = TRUE; + if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay)) + links_frei = TRUE; + if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay)) + rechts_frei = TRUE; + + if (element == EL_EXPANDABLE_STEELWALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_ANY) + { + if (oben_frei) + { + 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 (links_frei) + { + 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); + new_wall = TRUE; + } + + if (rechts_frei) + { + 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); + 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 (((oben_massiv && unten_massiv) || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) && + ((links_massiv && rechts_massiv) || + element == EL_EXPANDABLE_STEELWALL_VERTICAL)) + Feld[ax][ay] = EL_WALL; + + if (new_wall) + PlayLevelSoundAction(ax, ay, ACTION_GROWING); +} + void CheckForDragon(int x, int y) { int i, j; @@ -7505,6 +8858,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) { struct ElementInfo *ei = &element_info[element]; struct ElementChangeInfo *change = &ei->change_page[page]; + int target_element = change->target_element; int action_type = change->action_type; int action_mode = change->action_mode; int action_arg = change->action_arg; @@ -7543,21 +8897,21 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) action_type == CA_SET_LEVEL_GEMS ? 999 : action_type == CA_SET_LEVEL_TIME ? 9999 : action_type == CA_SET_LEVEL_SCORE ? 99999 : - action_type == CA_SET_CE_SCORE ? 9999 : action_type == CA_SET_CE_VALUE ? 9999 : + action_type == CA_SET_CE_SCORE ? 9999 : CA_ARG_MAX); int action_arg_number_reset = - (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize : + (action_type == CA_SET_PLAYER_SPEED ? level.initial_player_stepsize[0] : action_type == CA_SET_LEVEL_GEMS ? level.gems_needed : action_type == CA_SET_LEVEL_TIME ? level.time : action_type == CA_SET_LEVEL_SCORE ? 0 : - action_type == CA_SET_CE_SCORE ? 0 : -#if 1 - action_type == CA_SET_CE_VALUE ? GET_NEW_CUSTOM_VALUE(element) : +#if USE_NEW_CUSTOM_VALUE + action_type == CA_SET_CE_VALUE ? GET_NEW_CE_VALUE(element) : #else action_type == CA_SET_CE_VALUE ? ei->custom_value_initial : #endif + action_type == CA_SET_CE_SCORE ? 0 : 0); int action_arg_number = @@ -7568,18 +8922,20 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min : action_arg == CA_ARG_NUMBER_MAX ? action_arg_number_max : action_arg == CA_ARG_NUMBER_RESET ? action_arg_number_reset : - action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score : #if USE_NEW_CUSTOM_VALUE action_arg == CA_ARG_NUMBER_CE_VALUE ? CustomValue[x][y] : #else action_arg == CA_ARG_NUMBER_CE_VALUE ? ei->custom_value_initial : #endif + action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score : action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CE_DELAY_VALUE(change) : action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value : action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed : action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score : - action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CUSTOM_VALUE(change->target_element) : + action_arg == CA_ARG_ELEMENT_CV_TARGET ? GET_NEW_CE_VALUE(target_element): action_arg == CA_ARG_ELEMENT_CV_TRIGGER ? change->actual_trigger_ce_value: + action_arg == CA_ARG_ELEMENT_CS_TARGET ? GET_CE_SCORE(target_element) : + action_arg == CA_ARG_ELEMENT_CS_TRIGGER ? change->actual_trigger_ce_score: action_arg == CA_ARG_ELEMENT_NR_TARGET ? change->target_element : action_arg == CA_ARG_ELEMENT_NR_TRIGGER ? change->actual_trigger_element : -1); @@ -7588,8 +8944,8 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) (action_type == CA_SET_LEVEL_GEMS ? local_player->gems_still_needed : action_type == CA_SET_LEVEL_TIME ? TimeLeft : action_type == CA_SET_LEVEL_SCORE ? local_player->score : - action_type == CA_SET_CE_SCORE ? ei->collect_score : action_type == CA_SET_CE_VALUE ? CustomValue[x][y] : + action_type == CA_SET_CE_SCORE ? ei->collect_score : 0); int action_arg_number_new = @@ -7611,7 +8967,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) /* ---------- execute action -------------------------------------------- */ - switch(action_type) + switch (action_type) { case CA_NO_ACTION: { @@ -7672,6 +9028,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) break; } +#if !USE_PLAYER_GRAVITY case CA_SET_LEVEL_GRAVITY: { game.gravity = (action_arg == CA_ARG_GRAVITY_OFF ? FALSE : @@ -7680,6 +9037,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) game.gravity); break; } +#endif case CA_SET_LEVEL_WIND: { @@ -7704,7 +9062,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) { for (i = 0; i < MAX_PLAYERS; i++) if (action_arg_player_bits & (1 << i)) - stored_player[i].LevelSolved = stored_player[i].GameOver = TRUE; + PlayerWins(&stored_player[i]); break; } @@ -7732,9 +9090,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) { stored_player[i].key[KEY_NR(element)] = key_state; - DrawGameValue_Keys(stored_player[i].key); - - redraw_mask |= REDRAW_DOOR_1; + DrawGameDoorValues(); } } } @@ -7762,6 +9118,10 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) action_mode = (action_arg == CA_ARG_SPEED_SLOWER ? CA_MODE_DIVIDE : CA_MODE_MULTIPLY); } + else if (action_arg == CA_ARG_NUMBER_RESET) + { + action_arg_number = level.initial_player_stepsize[i]; + } move_stepsize = getModifiedActionNumber(move_stepsize, @@ -7770,18 +9130,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) action_arg_number_min, action_arg_number_max); -#if 1 SetPlayerMoveSpeed(&stored_player[i], move_stepsize, FALSE); -#else - /* make sure that value is power of 2 */ - move_stepsize = (1 << log_2(move_stepsize)); - - /* do no immediately change -- the player might just be moving */ - stored_player[i].move_delay_value_next = TILEX / move_stepsize; - - stored_player[i].cannot_move = - (action_arg == CA_ARG_SPEED_NOT_MOVING ? TRUE : FALSE); -#endif } } @@ -7814,6 +9163,25 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) break; } +#if USE_PLAYER_GRAVITY + case CA_SET_PLAYER_GRAVITY: + { + for (i = 0; i < MAX_PLAYERS; i++) + { + if (trigger_player_bits & (1 << i)) + { + stored_player[i].gravity = + (action_arg == CA_ARG_GRAVITY_OFF ? FALSE : + action_arg == CA_ARG_GRAVITY_ON ? TRUE : + action_arg == CA_ARG_GRAVITY_TOGGLE ? !stored_player[i].gravity : + stored_player[i].gravity); + } + } + + break; + } +#endif + case CA_SET_PLAYER_ARTWORK: { for (i = 0; i < MAX_PLAYERS; i++) @@ -7827,6 +9195,11 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) (level.use_artwork_element[i] ? level.artwork_element[i] : stored_player[i].element_nr); +#if USE_GFX_RESET_PLAYER_ARTWORK + if (stored_player[i].artwork_element != artwork_element) + stored_player[i].Frame = 0; +#endif + stored_player[i].artwork_element = artwork_element; SetPlayerWaiting(&stored_player[i], FALSE); @@ -7846,36 +9219,66 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) /* ---------- CE actions ---------------------------------------------- */ - case CA_SET_CE_SCORE: - { - ei->collect_score = action_arg_number_new; - - break; - } - case CA_SET_CE_VALUE: { #if USE_NEW_CUSTOM_VALUE - int last_custom_value = CustomValue[x][y]; + int last_ce_value = CustomValue[x][y]; CustomValue[x][y] = action_arg_number_new; -#if 0 - printf("::: Count == %d\n", CustomValue[x][y]); -#endif - - if (CustomValue[x][y] == 0 && last_custom_value > 0) + if (CustomValue[x][y] != last_ce_value) { -#if 0 - printf("::: CE_VALUE_GETS_ZERO\n"); + CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_CHANGES); + CheckTriggeredElementChange(x, y, element, CE_VALUE_CHANGES_OF_X); + + if (CustomValue[x][y] == 0) + { + CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO); + CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X); + } + } #endif - CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO); - CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X); + break; + } -#if 0 - printf("::: RESULT: %d, %d\n", Feld[x][y], ChangePage[x][y]); -#endif + case CA_SET_CE_SCORE: + { +#if USE_NEW_CUSTOM_VALUE + int last_ce_score = ei->collect_score; + + ei->collect_score = action_arg_number_new; + + if (ei->collect_score != last_ce_score) + { + CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_CHANGES); + CheckTriggeredElementChange(x, y, element, CE_SCORE_CHANGES_OF_X); + + if (ei->collect_score == 0) + { + int xx, yy; + + CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO); + CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X); + + /* + This is a very special case that seems to be a mixture between + CheckElementChange() and CheckTriggeredElementChange(): while + the first one only affects single elements that are triggered + directly, the second one affects multiple elements in the playfield + that are triggered indirectly by another element. This is a third + case: Changing the CE score always affects multiple identical CEs, + so every affected CE must be checked, not only the single CE for + which the CE score was changed in the first place (as every instance + of that CE shares the same CE score, and therefore also can change)! + */ + SCAN_PLAYFIELD(xx, yy) + { + if (Feld[xx][yy] == element) + CheckElementChange(xx, yy, element, EL_UNDEFINED, + CE_SCORE_GETS_ZERO); + } + } } #endif @@ -7898,54 +9301,98 @@ 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 new_element = GetElementFromGroupElement(element); int previous_move_direction = MovDir[x][y]; #if USE_NEW_CUSTOM_VALUE int last_ce_value = CustomValue[x][y]; #endif - boolean add_player = (ELEM_IS_PLAYER(element) && - IS_WALKABLE(Feld[x][y])); + boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y); + boolean new_element_is_player = ELEM_IS_PLAYER(new_element); + boolean add_player_onto_element = (new_element_is_player && +#if USE_CODE_THAT_BREAKS_SNAKE_BITE + /* this breaks SnakeBite when a snake is + halfway through a door that closes */ + /* NOW FIXED AT LEVEL INIT IN files.c */ + new_element != EL_SOKOBAN_FIELD_PLAYER && +#endif + IS_WALKABLE(old_element)); - /* check if element under player changes from accessible to unaccessible +#if 0 + /* check if element under the player changes from accessible to unaccessible (needed for special case of dropping element which then changes) */ if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) && - IS_ACCESSIBLE(Feld[x][y]) && !IS_ACCESSIBLE(element)) + IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) { Bang(x, y); return; } +#endif - if (!add_player) + if (!add_player_onto_element) { if (IS_MOVING(x, y) || IS_BLOCKED(x, y)) RemoveMovingField(x, y); else RemoveField(x, y); - Feld[x][y] = element; + Feld[x][y] = new_element; +#if !USE_GFX_RESET_GFX_ANIMATION ResetGfxAnimation(x, y); ResetRandomAnimationValue(x, y); +#endif - if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS) + if (element_info[new_element].move_direction_initial == MV_START_PREVIOUS) MovDir[x][y] = previous_move_direction; #if USE_NEW_CUSTOM_VALUE - if (element_info[Feld[x][y]].use_last_ce_value) + if (element_info[new_element].use_last_ce_value) CustomValue[x][y] = last_ce_value; #endif InitField_WithBug1(x, y, FALSE); + new_element = Feld[x][y]; /* element may have changed */ + +#if USE_GFX_RESET_GFX_ANIMATION + ResetGfxAnimation(x, y); + ResetRandomAnimationValue(x, y); +#endif + DrawLevelField(x, y); - if (GFX_CRUMBLED(Feld[x][y])) + if (GFX_CRUMBLED(new_element)) DrawLevelFieldCrumbledSandNeighbours(x, y); } +#if 1 + /* 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 USE_FIX_KILLED_BY_NON_WALKABLE + if (IS_PLAYER(x, y) && !player_explosion_protected && + IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) + { + Bang(x, y); + + return; + } +#else + if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y) && + IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) + { + Bang(x, y); + + return; + } +#endif +#endif + /* "ChangeCount" not set yet to allow "entered by player" change one time */ - if (ELEM_IS_PLAYER(element)) - RelocatePlayer(x, y, element); + if (new_element_is_player) + RelocatePlayer(x, y, new_element); if (is_change) ChangeCount[x][y]++; /* count number of changes in the same frame */ @@ -7982,7 +9429,10 @@ static void CreateElementFromChange(int x, int y, int element) static boolean ChangeElement(int x, int y, int element, int page) { - struct ElementChangeInfo *change = &element_info[element].change_page[page]; + struct ElementInfo *ei = &element_info[element]; + struct ElementChangeInfo *change = &ei->change_page[page]; + int ce_value = CustomValue[x][y]; + int ce_score = ei->collect_score; int target_element; int old_element = Feld[x][y]; @@ -7997,6 +9447,7 @@ static boolean ChangeElement(int x, int y, int element, int page) change->actual_trigger_player = EL_PLAYER_1; change->actual_trigger_side = CH_SIDE_NONE; change->actual_trigger_ce_value = 0; + change->actual_trigger_ce_score = 0; } /* do not change elements more than a specified maximum number of changes */ @@ -8101,7 +9552,8 @@ static boolean ChangeElement(int x, int y, int element, int page) ChangeEvent[ex][ey] = ChangeEvent[x][y]; content_element = change->target_content.e[xx][yy]; - target_element = GET_TARGET_ELEMENT(content_element, change); + target_element = GET_TARGET_ELEMENT(element, content_element, change, + ce_value, ce_score); CreateElementFromChange(ex, ey, target_element); @@ -8122,7 +9574,8 @@ static boolean ChangeElement(int x, int y, int element, int page) } else { - target_element = GET_TARGET_ELEMENT(change->target_element, change); + target_element = GET_TARGET_ELEMENT(element, change->target_element, change, + ce_value, ce_score); if (element == EL_DIAGONAL_GROWING || element == EL_DIAGONAL_SHRINKING) @@ -8181,8 +9634,19 @@ static void HandleElementChange(int x, int y, int page) if (change->can_change) { - ResetGfxAnimation(x, y); - ResetRandomAnimationValue(x, y); +#if 1 + /* !!! not clear why graphic animation should be reset at all here !!! */ + /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */ +#if USE_GFX_RESET_WHEN_NOT_MOVING + /* when a custom element is about to change (for example by change delay), + do not reset graphic animation when the custom element is moving */ + if (!IS_MOVING(x, y)) +#endif + { + ResetGfxAnimation(x, y); + ResetRandomAnimationValue(x, y); + } +#endif if (change->pre_change_function) change->pre_change_function(x, y); @@ -8331,6 +9795,14 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, if (!(trigger_events[trigger_element][trigger_event])) return FALSE; +#if 0 + printf("::: CheckTriggeredElementChangeExt %d ... [%d, %d, %d, '%s']\n", + trigger_event, recursion_loop_depth, recursion_loop_detected, + recursion_loop_element, EL_NAME(recursion_loop_element)); +#endif + + RECURSION_LOOP_DETECTION_START(trigger_element, FALSE); + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) { int element = EL_CUSTOM_START + i; @@ -8356,16 +9828,13 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player); 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); if ((change->can_change && !change_done) || change->has_action) { int x, y; -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { if (Feld[x][y] == element) { @@ -8402,6 +9871,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, } } + RECURSION_LOOP_DETECTION_END(); + return change_done_any; } @@ -8440,14 +9911,30 @@ static boolean CheckElementChangeExt(int x, int y, return FALSE; #endif +#if 0 + printf("::: CheckElementChangeExt %d ... [%d, %d, %d, '%s']\n", + trigger_event, recursion_loop_depth, recursion_loop_detected, + recursion_loop_element, EL_NAME(recursion_loop_element)); +#endif + + RECURSION_LOOP_DETECTION_START(trigger_element, FALSE); + for (p = 0; p < element_info[element].num_change_pages; p++) { struct ElementChangeInfo *change = &element_info[element].change_page[p]; + /* check trigger element for all events where the element that is checked + for changing interacts with a directly adjacent element -- this is + 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_HITTING_X || - trigger_event == CE_HIT_BY_X); + trigger_event == CE_HIT_BY_X || +#if 1 + /* this one was forgotten until 3.2.3 */ + trigger_event == CE_DIGGING_X); +#endif if (change->can_change_or_has_action && change->has_event[trigger_event] && @@ -8460,6 +9947,7 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player); change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[x][y]; + change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); /* special case: trigger element not at (x,y) position for some events */ if (check_trigger_element) @@ -8482,6 +9970,7 @@ static boolean CheckElementChangeExt(int x, int y, int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy; change->actual_trigger_ce_value = CustomValue[xx][yy]; + change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); } if (change->can_change && !change_done) @@ -8509,6 +9998,8 @@ static boolean CheckElementChangeExt(int x, int y, } } + RECURSION_LOOP_DETECTION_END(); + return change_done; } @@ -8550,6 +10041,7 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) boolean last_waiting = player->is_waiting; int move_dir = player->MovDir; + player->dir_waiting = move_dir; player->last_action_waiting = player->action_waiting; if (is_waiting) @@ -8561,13 +10053,13 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) player->frame_counter_bored = FrameCounter + game.player_boring_delay_fixed + - SimpleRND(game.player_boring_delay_random); + GetSimpleRandom(game.player_boring_delay_random); player->frame_counter_sleeping = FrameCounter + game.player_sleeping_delay_fixed + - SimpleRND(game.player_sleeping_delay_random); + GetSimpleRandom(game.player_sleeping_delay_random); - InitPlayerGfxAnimation(player, ACTION_WAITING, player->MovDir); + InitPlayerGfxAnimation(player, ACTION_WAITING, move_dir); } if (game.player_sleeping_delay_fixed + @@ -8585,6 +10077,24 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) player->is_bored ? ACTION_BORING : ACTION_WAITING); + if (player->is_sleeping && player->use_murphy) + { + /* 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 && + !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 && + !IS_MOVING(player->jx + 1, player->jy))) + move_dir = MV_RIGHT; + else + player->is_sleeping = FALSE; + + player->dir_waiting = move_dir; + } + if (player->is_sleeping) { if (player->num_special_action_sleeping > 0) @@ -8603,10 +10113,10 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) player->anim_delay_counter = graphic_info[special_graphic].anim_delay_fixed + - SimpleRND(graphic_info[special_graphic].anim_delay_random); + GetSimpleRandom(graphic_info[special_graphic].anim_delay_random); player->post_delay_counter = graphic_info[special_graphic].post_delay_fixed + - SimpleRND(graphic_info[special_graphic].post_delay_random); + GetSimpleRandom(graphic_info[special_graphic].post_delay_random); player->special_action_sleeping = special_action; } @@ -8629,16 +10139,16 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) if (player->anim_delay_counter == 0 && player->post_delay_counter == 0) { int special_action = - ACTION_BORING_1 + SimpleRND(player->num_special_action_bored); + ACTION_BORING_1 + GetSimpleRandom(player->num_special_action_bored); int special_graphic = el_act_dir2img(player->artwork_element, special_action, move_dir); player->anim_delay_counter = graphic_info[special_graphic].anim_delay_fixed + - SimpleRND(graphic_info[special_graphic].anim_delay_random); + GetSimpleRandom(graphic_info[special_graphic].anim_delay_random); player->post_delay_counter = graphic_info[special_graphic].post_delay_fixed + - SimpleRND(graphic_info[special_graphic].post_delay_random); + GetSimpleRandom(graphic_info[special_graphic].post_delay_random); player->special_action_bored = special_action; } @@ -8667,6 +10177,7 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) player->anim_delay_counter = 0; player->post_delay_counter = 0; + player->dir_waiting = player->MovDir; player->action_waiting = ACTION_DEFAULT; player->special_action_bored = ACTION_DEFAULT; @@ -8683,8 +10194,8 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action) int down = player_action & JOY_DOWN; int button1 = player_action & JOY_BUTTON_1; int button2 = player_action & JOY_BUTTON_2; - int dx = (left ? -1 : right ? 1 : 0); - int dy = (up ? -1 : down ? 1 : 0); + int dx = (left ? -1 : right ? 1 : 0); + int dy = (up ? -1 : down ? 1 : 0); if (!player->active || tape.pausing) return 0; @@ -8729,11 +10240,87 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action) player->is_moving = FALSE; player->is_dropping = FALSE; + player->is_dropping_pressed = FALSE; + player->drop_pressed_delay = 0; return 0; } } +static void CheckLevelTime() +{ + int i; + + if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + { + if (level.native_em_level->lev->home == 0) /* all players at home */ + { + PlayerWins(local_player); + + AllPlayersGone = TRUE; + + level.native_em_level->lev->home = -1; + } + + if (level.native_em_level->ply[0]->alive == 0 && + level.native_em_level->ply[1]->alive == 0 && + level.native_em_level->ply[2]->alive == 0 && + level.native_em_level->ply[3]->alive == 0) /* all dead */ + AllPlayersGone = TRUE; + } + + if (TimeFrames >= FRAMES_PER_SECOND) + { + TimeFrames = 0; + TapeTime++; + + for (i = 0; i < MAX_PLAYERS; i++) + { + struct PlayerInfo *player = &stored_player[i]; + + if (SHIELD_ON(player)) + { + player->shield_normal_time_left--; + + if (player->shield_deadly_time_left > 0) + player->shield_deadly_time_left--; + } + } + + if (!local_player->LevelSolved && !level.use_step_counter) + { + TimePlayed++; + + if (TimeLeft > 0) + { + TimeLeft--; + + if (TimeLeft <= 10 && setup.time_limit) + PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); + + DrawGameValue_Time(TimeLeft); + + if (!TimeLeft && setup.time_limit) + { + if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + level.native_em_level->lev->killed_out_of_time = TRUE; + else + for (i = 0; i < MAX_PLAYERS; i++) + KillPlayer(&stored_player[i]); + } + } + else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */ + DrawGameValue_Time(TimePlayed); + + level.native_em_level->lev->time = + (level.time == 0 ? TimePlayed : TimeLeft); + } + + if (tape.recording || tape.playing) + DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime); + } +} + void AdvanceFrameAndPlayerCounters(int player_nr) { int i; @@ -8779,20 +10366,88 @@ void AdvanceFrameAndPlayerCounters(int player_nr) if (stored_player[i].drop_delay > 0) stored_player[i].drop_delay--; + + if (stored_player[i].is_dropping_pressed) + stored_player[i].drop_pressed_delay++; } } +void StartGameActions(boolean init_network_game, boolean record_tape, + long random_seed) +{ + unsigned long new_random_seed = InitRND(random_seed); + + if (record_tape) + TapeStartRecording(new_random_seed); + +#if defined(NETWORK_AVALIABLE) + if (init_network_game) + { + SendToServer_StartPlaying(); + + return; + } +#endif + + InitGame(); +} + void GameActions() { static unsigned long game_frame_delay = 0; unsigned long game_frame_delay_value; - int magic_wall_x = 0, magic_wall_y = 0; - int i, x, y, element, graphic; byte *recorded_player_action; byte summarized_player_action = 0; byte tape_action[MAX_PLAYERS]; + int i; + + /* detect endless loops, caused by custom element programming */ + if (recursion_loop_detected && recursion_loop_depth == 0) + { + char *message = getStringCat3("Internal Error ! Element ", + 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)); + + RequestQuitGameExt(FALSE, level_editor_test_game, message); + + recursion_loop_detected = FALSE; /* if game should be continued */ + + free(message); - if (game_status != GAME_MODE_PLAYING) + return; + } + + if (game.restart_level) + StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE); + + if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + { + if (level.native_em_level->lev->home == 0) /* all players at home */ + { + PlayerWins(local_player); + + AllPlayersGone = TRUE; + + level.native_em_level->lev->home = -1; + } + + if (level.native_em_level->ply[0]->alive == 0 && + level.native_em_level->ply[1]->alive == 0 && + level.native_em_level->ply[2]->alive == 0 && + level.native_em_level->ply[3]->alive == 0) /* all dead */ + AllPlayersGone = TRUE; + } + + if (local_player->LevelSolved && !local_player->LevelSolved_GameEnd) + GameWon(); + + if (AllPlayersGone && !TAPE_IS_STOPPED(tape)) + TapeStop(); + + if (game_status != GAME_MODE_PLAYING) /* status might have changed */ return; game_frame_delay_value = @@ -8805,19 +10460,6 @@ void GameActions() WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value); - InitPlayfieldScanModeVars(); - -#if USE_ONE_MORE_CHANGE_PER_FRAME - if (game.engine_version >= VERSION_IDENT(3,2,0,7)) - { - SCAN_PLAYFIELD(x, y) - { - ChangeCount[x][y] = 0; - ChangeEvent[x][y] = -1; - } - } -#endif - if (network_playing && !network_player_action_received) { /* try to get network player actions in time */ @@ -8833,19 +10475,32 @@ void GameActions() if (!network_player_action_received) return; /* failed to get network player actions in time */ + + /* do not yet reset "network_player_action_received" (for tape.pausing) */ } if (tape.pausing) return; + /* at this point we know that we really continue executing the game */ + + network_player_action_received = FALSE; + + /* when playing tape, read previously recorded player input from tape data */ recorded_player_action = (tape.playing ? TapePlayAction() : NULL); #if 1 - /* !!! CHECK THIS (tape.pausing is always FALSE here!) !!! */ - if (recorded_player_action == NULL && tape.pausing) + /* TapePlayAction() may return NULL when toggling to "pause before death" */ + if (tape.pausing) return; #endif + if (tape.set_centered_player) + { + game.centered_player_nr_next = tape.centered_player_nr_next; + game.set_centered_player = TRUE; + } + for (i = 0; i < MAX_PLAYERS; i++) { summarized_player_action |= stored_player[i].action; @@ -8862,6 +10517,13 @@ void GameActions() if (!options.network && !setup.team_mode) local_player->effective_action = summarized_player_action; + if (setup.team_mode && setup.input_on_focus && game.centered_player_nr != -1) + { + for (i = 0; i < MAX_PLAYERS; i++) + stored_player[i].effective_action = + (i == game.centered_player_nr ? summarized_player_action : 0); + } + if (recorded_player_action != NULL) for (i = 0; i < MAX_PLAYERS; i++) stored_player[i].effective_action = recorded_player_action[i]; @@ -8870,14 +10532,101 @@ void GameActions() { tape_action[i] = stored_player[i].effective_action; + /* (this can only happen in the R'n'D game engine) */ if (tape.recording && tape_action[i] && !tape.player_participates[i]) tape.player_participates[i] = TRUE; /* player just appeared from CE */ } - /* only save actions from input devices, but not programmed actions */ + /* only record actions from input devices, but not programmed actions */ if (tape.recording) TapeRecordAction(tape_action); + if (level.game_engine_type == GAME_ENGINE_TYPE_EM) + { + GameActions_EM_Main(); + } + else + { + GameActions_RND(); + } +} + +void GameActions_EM_Main() +{ + 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); + + CheckLevelTime(); + + AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */ +} + +void GameActions_RND() +{ + int magic_wall_x = 0, magic_wall_y = 0; + int i, x, y, element, graphic; + + InitPlayfieldScanModeVars(); + +#if USE_ONE_MORE_CHANGE_PER_FRAME + if (game.engine_version >= VERSION_IDENT(3,2,0,7)) + { + SCAN_PLAYFIELD(x, y) + { + ChangeCount[x][y] = 0; + ChangeEvent[x][y] = -1; + } + } +#endif + + if (game.set_centered_player) + { + boolean all_players_fit_to_screen = checkIfAllPlayersFitToScreen_RND(); + + /* switching to "all players" only possible if all players fit to screen */ + if (game.centered_player_nr_next == -1 && !all_players_fit_to_screen) + { + game.centered_player_nr_next = game.centered_player_nr; + game.set_centered_player = FALSE; + } + + /* do not switch focus to non-existing (or non-active) player */ + if (game.centered_player_nr_next >= 0 && + !stored_player[game.centered_player_nr_next].active) + { + game.centered_player_nr_next = game.centered_player_nr; + game.set_centered_player = FALSE; + } + } + + if (game.set_centered_player && + ScreenMovPos == 0) /* screen currently aligned at tile position */ + { + int sx, sy; + + if (game.centered_player_nr_next == -1) + { + setScreenCenteredToAllPlayers(&sx, &sy); + } + else + { + sx = stored_player[game.centered_player_nr_next].jx; + sy = stored_player[game.centered_player_nr_next].jy; + } + + 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); + DrawGameDoorValues(); + } + for (i = 0; i < MAX_PLAYERS; i++) { int actual_player_action = stored_player[i].effective_action; @@ -8897,20 +10646,11 @@ void GameActions() if (stored_player[i].programmed_action) actual_player_action = stored_player[i].programmed_action; -#if 1 PlayerActions(&stored_player[i], actual_player_action); -#else - tape_action[i] = PlayerActions(&stored_player[i], actual_player_action); - - if (tape.recording && tape_action[i] && !tape.player_participates[i]) - tape.player_participates[i] = TRUE; /* player just appeared from CE */ -#endif ScrollPlayer(&stored_player[i], SCROLL_GO_ON); } - network_player_action_received = FALSE; - ScrollScreen(NULL, SCROLL_GO_ON); /* for backwards compatibility, the following code emulates a fixed bug that @@ -8938,18 +10678,16 @@ void GameActions() /* continue moving after pushing (this is actually a bug) */ if (!IS_MOVING(x, y)) - { Stop[x][y] = FALSE; - } } } } -#if 1 - SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) +#if 0 + debug_print_timestamp(0, "start main loop profiling"); #endif + + SCAN_PLAYFIELD(x, y) { ChangeCount[x][y] = 0; ChangeEvent[x][y] = -1; @@ -8993,6 +10731,8 @@ void GameActions() WasJustFalling[x][y]--; if (CheckCollision[x][y] > 0) CheckCollision[x][y]--; + if (CheckImpact[x][y] > 0) + CheckImpact[x][y]--; GfxFrame[x][y]++; @@ -9021,46 +10761,69 @@ void GameActions() #endif } -#if 1 - SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) +#if 0 + debug_print_timestamp(0, "- time for pre-main loop:"); #endif + +#if 0 // -------------------- !!! TEST ONLY !!! -------------------- + SCAN_PLAYFIELD(x, y) { element = Feld[x][y]; graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); -#if 0 - printf("::: %d,%d\n", x, y); - - if (element == EL_ROCK) - printf("::: Yo man! Rocks can fall!\n"); +#if 1 + { +#if 1 + int element2 = element; + int graphic2 = graphic; +#else + int element2 = Feld[x][y]; + int graphic2 = el_act_dir2img(element2, GfxAction[x][y], GfxDir[x][y]); #endif + int last_gfx_frame = GfxFrame[x][y]; - if (graphic_info[graphic].anim_global_sync) - GfxFrame[x][y] = FrameCounter; - else if (ANIM_MODE(graphic) == ANIM_CE_VALUE) - { - int old_gfx_frame = GfxFrame[x][y]; + if (graphic_info[graphic2].anim_global_sync) + GfxFrame[x][y] = FrameCounter; + else if (ANIM_MODE(graphic2) == ANIM_CE_VALUE) + GfxFrame[x][y] = CustomValue[x][y]; + else if (ANIM_MODE(graphic2) == ANIM_CE_SCORE) + GfxFrame[x][y] = element_info[element2].collect_score; + else if (ANIM_MODE(graphic2) == ANIM_CE_DELAY) + GfxFrame[x][y] = ChangeDelay[x][y]; - GfxFrame[x][y] = CustomValue[x][y]; + if (redraw && GfxFrame[x][y] != last_gfx_frame) + DrawLevelGraphicAnimation(x, y, graphic2); + } +#else + ResetGfxFrame(x, y, TRUE); +#endif #if 1 - if (GfxFrame[x][y] != old_gfx_frame) + if (ANIM_MODE(graphic) == ANIM_RANDOM && + IS_NEXT_FRAME(GfxFrame[x][y], graphic)) + ResetRandomAnimationValue(x, y); #endif - DrawLevelGraphicAnimation(x, y, graphic); - } - else if (ANIM_MODE(graphic) == ANIM_CE_SCORE) - { - int old_gfx_frame = GfxFrame[x][y]; - GfxFrame[x][y] = element_info[element].collect_score; +#if 1 + SetRandomAnimationValue(x, y); +#endif #if 1 - if (GfxFrame[x][y] != old_gfx_frame) + PlayLevelSoundActionIfLoop(x, y, GfxAction[x][y]); #endif - DrawLevelGraphicAnimation(x, y, graphic); - } + } +#endif // -------------------- !!! TEST ONLY !!! -------------------- + +#if 0 + debug_print_timestamp(0, "- time for TEST loop: -->"); +#endif + + SCAN_PLAYFIELD(x, y) + { + element = Feld[x][y]; + graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + + ResetGfxFrame(x, y, TRUE); if (ANIM_MODE(graphic) == ANIM_RANDOM && IS_NEXT_FRAME(GfxFrame[x][y], graphic)) @@ -9083,35 +10846,158 @@ void GameActions() (game.engine_version < VERSION_IDENT(3,0,7,1) || !Stop[x][y])) { int page = element_info[element].event_page_nr[CE_DELAY]; -#if 0 - HandleElementChange(x, y, ChangePage[x][y] != -1 ? ChangePage[x][y] : page); + +#if 1 + HandleElementChange(x, y, page); #else + if (CAN_CHANGE(element)) + HandleElementChange(x, y, page); + + if (HAS_ACTION(element)) + ExecuteCustomElementAction(x, y, element, page); +#endif + + element = Feld[x][y]; + graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + } + +#if 0 // --------------------------------------------------------------------- + + if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element))) + { + StartMoving(x, y); + + element = Feld[x][y]; + graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + + if (IS_ANIMATED(graphic) && + !IS_MOVING(x, y) && + !Stop[x][y]) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + + if (IS_GEM(element) || element == EL_SP_INFOTRON) + DrawTwinkleOnField(x, y); + } + else if (IS_MOVING(x, y)) + ContinueMoving(x, y); + else + { + switch (element) + { + case EL_ACID: + case EL_EXIT_OPEN: + case EL_EM_EXIT_OPEN: + case EL_SP_EXIT_OPEN: + case EL_STEEL_EXIT_OPEN: + case EL_EM_STEEL_EXIT_OPEN: + case EL_SP_TERMINAL: + case EL_SP_TERMINAL_ACTIVE: + case EL_EXTRA_TIME: + case EL_SHIELD_NORMAL: + case EL_SHIELD_DEADLY: + if (IS_ANIMATED(graphic)) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + break; + + case EL_DYNAMITE_ACTIVE: + case EL_EM_DYNAMITE_ACTIVE: + case EL_DYNABOMB_PLAYER_1_ACTIVE: + case EL_DYNABOMB_PLAYER_2_ACTIVE: + case EL_DYNABOMB_PLAYER_3_ACTIVE: + case EL_DYNABOMB_PLAYER_4_ACTIVE: + case EL_SP_DISK_RED_ACTIVE: + CheckDynamite(x, y); + break; + + case EL_AMOEBA_GROWING: + AmoebeWaechst(x, y); + break; + + case EL_AMOEBA_SHRINKING: + AmoebaDisappearing(x, y); + break; + +#if !USE_NEW_AMOEBA_CODE + case EL_AMOEBA_WET: + case EL_AMOEBA_DRY: + case EL_AMOEBA_FULL: + case EL_BD_AMOEBA: + case EL_EMC_DRIPPER: + AmoebeAbleger(x, y); + break; +#endif + + case EL_GAME_OF_LIFE: + case EL_BIOMAZE: + Life(x, y); + break; + + case EL_EXIT_CLOSED: + CheckExit(x, y); + break; + + case EL_EM_EXIT_CLOSED: + CheckExitEM(x, y); + break; + + case EL_STEEL_EXIT_CLOSED: + CheckExitSteel(x, y); + break; + + case EL_EM_STEEL_EXIT_CLOSED: + CheckExitSteelEM(x, y); + break; + + case EL_SP_EXIT_CLOSED: + CheckExitSP(x, y); + break; + + case EL_EXPANDABLE_WALL_GROWING: + case EL_EXPANDABLE_STEELWALL_GROWING: + MauerWaechst(x, y); + break; + + case EL_EXPANDABLE_WALL: + case EL_EXPANDABLE_WALL_HORIZONTAL: + case EL_EXPANDABLE_WALL_VERTICAL: + case EL_EXPANDABLE_WALL_ANY: + case EL_BD_EXPANDABLE_WALL: + MauerAbleger(x, y); + break; -#if 0 - printf("::: ChangeDelay == %d\n", ChangeDelay[x][y]); -#endif + case EL_EXPANDABLE_STEELWALL_HORIZONTAL: + case EL_EXPANDABLE_STEELWALL_VERTICAL: + case EL_EXPANDABLE_STEELWALL_ANY: + MauerAblegerStahl(x, y); + break; -#if 0 - if (element == EL_CUSTOM_255) - printf("::: ChangeDelay == %d\n", ChangeDelay[x][y]); -#endif + case EL_FLAMES: + CheckForDragon(x, y); + break; -#if 1 - HandleElementChange(x, y, page); -#else - if (CAN_CHANGE(element)) - HandleElementChange(x, y, page); + case EL_EXPLOSION: + break; - if (HAS_ACTION(element)) - ExecuteCustomElementAction(x, y, element, page); -#endif + case EL_ELEMENT_SNAPPING: + case EL_DIAGONAL_SHRINKING: + case EL_DIAGONAL_GROWING: + { + graphic = + el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]); -#endif + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + break; + } - element = Feld[x][y]; - graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + default: + if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y)) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + break; + } } +#else // --------------------------------------------------------------------- + if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element))) { StartMoving(x, y); @@ -9125,11 +11011,14 @@ void GameActions() DrawLevelGraphicAnimationIfNeeded(x, y, graphic); if (IS_GEM(element) || element == EL_SP_INFOTRON) - EdelsteinFunkeln(x, y); + DrawTwinkleOnField(x, y); } else if ((element == EL_ACID || element == EL_EXIT_OPEN || + element == EL_EM_EXIT_OPEN || element == EL_SP_EXIT_OPEN || + element == EL_STEEL_EXIT_OPEN || + element == EL_EM_STEEL_EXIT_OPEN || element == EL_SP_TERMINAL || element == EL_SP_TERMINAL_ACTIVE || element == EL_EXTRA_TIME || @@ -9155,15 +11044,27 @@ void GameActions() Life(x, y); else if (element == EL_EXIT_CLOSED) CheckExit(x, y); + else if (element == EL_EM_EXIT_CLOSED) + CheckExitEM(x, y); + else if (element == EL_STEEL_EXIT_CLOSED) + CheckExitSteel(x, y); + else if (element == EL_EM_STEEL_EXIT_CLOSED) + CheckExitSteelEM(x, y); else if (element == EL_SP_EXIT_CLOSED) CheckExitSP(x, y); - else if (element == EL_EXPANDABLE_WALL_GROWING) + else if (element == EL_EXPANDABLE_WALL_GROWING || + element == EL_EXPANDABLE_STEELWALL_GROWING) MauerWaechst(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_EXPANDABLE_WALL_ANY || + element == EL_BD_EXPANDABLE_WALL) MauerAbleger(x, y); + else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_EXPANDABLE_STEELWALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_ANY) + MauerAblegerStahl(x, y); else if (element == EL_FLAMES) CheckForDragon(x, y); else if (element == EL_EXPLOSION) @@ -9172,20 +11073,14 @@ void GameActions() element == EL_DIAGONAL_SHRINKING || element == EL_DIAGONAL_GROWING) { -#if 1 graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]); DrawLevelGraphicAnimationIfNeeded(x, y, graphic); -#endif } else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y)) DrawLevelGraphicAnimationIfNeeded(x, y, graphic); -#if 0 - if (element == EL_CUSTOM_255 || - element == EL_CUSTOM_256) - DrawLevelGraphicAnimation(x, y, graphic); -#endif +#endif // --------------------------------------------------------------------- if (IS_BELT_ACTIVE(element)) PlayLevelSoundAction(x, y, ACTION_ACTIVE); @@ -9200,7 +11095,10 @@ void GameActions() element == EL_MAGIC_WALL_EMPTYING || element == EL_BD_MAGIC_WALL_FULL || element == EL_BD_MAGIC_WALL_ACTIVE || - element == EL_BD_MAGIC_WALL_EMPTYING) && + element == EL_BD_MAGIC_WALL_EMPTYING || + element == EL_DC_MAGIC_WALL_FULL || + element == EL_DC_MAGIC_WALL_ACTIVE || + element == EL_DC_MAGIC_WALL_EMPTYING) && ABS(x-jx) + ABS(y-jy) < ABS(magic_wall_x-jx) + ABS(magic_wall_y-jy)) { magic_wall_x = x; @@ -9209,6 +11107,10 @@ void GameActions() } } +#if 0 + debug_print_timestamp(0, "- time for MAIN loop: -->"); +#endif + #if USE_NEW_AMOEBA_CODE /* new experimental amoeba growth stuff */ if (!(FrameCounter % 8)) @@ -9225,6 +11127,7 @@ void GameActions() (element == EL_EMPTY || CAN_GROW_INTO(element) || element == EL_QUICKSAND_EMPTY || + element == EL_QUICKSAND_FAST_EMPTY || element == EL_ACID_SPLASH_LEFT || element == EL_ACID_SPLASH_RIGHT)) { @@ -9246,11 +11149,7 @@ void GameActions() { game.explosions_delayed = FALSE; -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { element = Feld[x][y]; @@ -9275,6 +11174,10 @@ void GameActions() element == EL_BD_MAGIC_WALL_ACTIVE || element == EL_BD_MAGIC_WALL_EMPTYING) PlayLevelSound(magic_wall_x, magic_wall_y, SND_BD_MAGIC_WALL_ACTIVE); + else if (element == EL_DC_MAGIC_WALL_FULL || + element == EL_DC_MAGIC_WALL_ACTIVE || + element == EL_DC_MAGIC_WALL_EMPTYING) + PlayLevelSound(magic_wall_x, magic_wall_y, SND_DC_MAGIC_WALL_ACTIVE); else PlayLevelSound(magic_wall_x, magic_wall_y, SND_MAGIC_WALL_ACTIVE); } @@ -9284,11 +11187,7 @@ void GameActions() game.magic_wall_time_left--; if (!game.magic_wall_time_left) { -#if 1 SCAN_PLAYFIELD(x, y) -#else - for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) -#endif { element = Feld[x][y]; @@ -9304,6 +11203,12 @@ void GameActions() Feld[x][y] = EL_BD_MAGIC_WALL_DEAD; 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; + DrawLevelField(x, y); + } } game.magic_wall_active = FALSE; @@ -9356,48 +11261,7 @@ void GameActions() } } - if (TimeFrames >= FRAMES_PER_SECOND) - { - TimeFrames = 0; - TapeTime++; - - for (i = 0; i < MAX_PLAYERS; i++) - { - struct PlayerInfo *player = &stored_player[i]; - - if (SHIELD_ON(player)) - { - player->shield_normal_time_left--; - - if (player->shield_deadly_time_left > 0) - player->shield_deadly_time_left--; - } - } - - if (!level.use_step_counter) - { - TimePlayed++; - - if (TimeLeft > 0) - { - TimeLeft--; - - if (TimeLeft <= 10 && setup.time_limit) - PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE); - - DrawGameValue_Time(TimeLeft); - - if (!TimeLeft && setup.time_limit) - for (i = 0; i < MAX_PLAYERS; i++) - KillPlayer(&stored_player[i]); - } - else if (level.time == 0 && !AllPlayersGone) /* level w/o time limit */ - DrawGameValue_Time(TimePlayed); - } - - if (tape.recording || tape.playing) - DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime); - } + CheckLevelTime(); DrawAllPlayers(); PlayAllPlayersSound(); @@ -9430,6 +11294,11 @@ void GameActions() local_player->show_envelope = 0; } +#if 0 + debug_print_timestamp(0, "stop main loop profiling "); + printf("----------------------------------------------------------\n"); +#endif + /* use random number generator in every frame to make it less predictable */ if (game.engine_version >= VERSION_IDENT(3,1,1,0)) RND(1); @@ -9476,25 +11345,85 @@ static boolean AllPlayersInVisibleScreen() void ScrollLevel(int dx, int dy) { +#if 1 + static Bitmap *bitmap_db_field2 = NULL; int softscroll_offset = (setup.soft_scrolling ? TILEX : 0); int x, y; +#else + int i, x, y; +#endif + +#if 0 + /* !!! THIS IS APPARENTLY WRONG FOR PLAYER RELOCATION !!! */ + /* only horizontal XOR vertical scroll direction allowed */ + if ((dx == 0 && dy == 0) || (dx != 0 && dy != 0)) + return; +#endif + +#if 1 + if (bitmap_db_field2 == NULL) + bitmap_db_field2 = CreateBitmap(FXSIZE, FYSIZE, DEFAULT_DEPTH); + + /* needed when blitting directly to same bitmap -- should not be needed with + recent SDL libraries, but apparently does not work in 1.2.11 directly */ + BlitBitmap(drawto_field, bitmap_db_field2, + FX + TILEX * (dx == -1) - softscroll_offset, + FY + TILEY * (dy == -1) - softscroll_offset, + SXSIZE - TILEX * (dx != 0) + 2 * softscroll_offset, + SYSIZE - TILEY * (dy != 0) + 2 * softscroll_offset, + FX + TILEX * (dx == 1) - softscroll_offset, + FY + TILEY * (dy == 1) - softscroll_offset); + BlitBitmap(bitmap_db_field2, drawto_field, + FX + TILEX * (dx == 1) - softscroll_offset, + FY + TILEY * (dy == 1) - softscroll_offset, + SXSIZE - TILEX * (dx != 0) + 2 * softscroll_offset, + SYSIZE - TILEY * (dy != 0) + 2 * softscroll_offset, + FX + TILEX * (dx == 1) - softscroll_offset, + FY + TILEY * (dy == 1) - softscroll_offset); + +#else + +#if 1 + /* !!! DOES NOT WORK FOR DIAGONAL PLAYER RELOCATION !!! */ + int xsize = (BX2 - BX1 + 1); + int ysize = (BY2 - BY1 + 1); + int start = (dx != 0 ? (dx == -1 ? BX1 : BX2) : (dy == -1 ? BY1 : BY2)); + int end = (dx != 0 ? (dx == -1 ? BX2 : BX1) : (dy == -1 ? BY2 : BY1)); + int step = (start < end ? +1 : -1); + + for (i = start; i != end; i += step) + { + BlitBitmap(drawto_field, drawto_field, + FX + TILEX * (dx != 0 ? i + step : 0), + FY + TILEY * (dy != 0 ? i + step : 0), + TILEX * (dx != 0 ? 1 : xsize), + TILEY * (dy != 0 ? 1 : ysize), + FX + TILEX * (dx != 0 ? i : 0), + FY + TILEY * (dy != 0 ? i : 0)); + } + +#else + + int softscroll_offset = (setup.soft_scrolling ? TILEX : 0); BlitBitmap(drawto_field, drawto_field, FX + TILEX * (dx == -1) - softscroll_offset, FY + TILEY * (dy == -1) - softscroll_offset, - SXSIZE - TILEX * (dx!=0) + 2 * softscroll_offset, - SYSIZE - TILEY * (dy!=0) + 2 * softscroll_offset, + SXSIZE - TILEX * (dx != 0) + 2 * softscroll_offset, + SYSIZE - TILEY * (dy != 0) + 2 * softscroll_offset, FX + TILEX * (dx == 1) - softscroll_offset, FY + TILEY * (dy == 1) - softscroll_offset); +#endif +#endif - if (dx) + if (dx != 0) { x = (dx == 1 ? BX1 : BX2); for (y = BY1; y <= BY2; y++) DrawScreenField(x, y); } - if (dy) + if (dy != 0) { y = (dy == 1 ? BY1 : BY2); for (x = BX1; x <= BX2; x++) @@ -9548,11 +11477,15 @@ static boolean canMoveToValidFieldWithGravity(int x, int y, int move_dir) static void CheckGravityMovement(struct PlayerInfo *player) { +#if USE_PLAYER_GRAVITY + if (player->gravity && !player->programmed_action) +#else if (game.gravity && !player->programmed_action) +#endif { int move_dir_horizontal = player->effective_action & MV_HORIZONTAL; int move_dir_vertical = player->effective_action & MV_VERTICAL; - boolean player_is_snapping = player->effective_action & JOY_BUTTON_1; + boolean player_is_snapping = (player->effective_action & JOY_BUTTON_1); int jx = player->jx, jy = player->jy; boolean player_is_moving_to_valid_field = (!player_is_snapping && @@ -9570,7 +11503,11 @@ static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *player) { return CheckGravityMovement(player); +#if USE_PLAYER_GRAVITY + if (player->gravity && !player->programmed_action) +#else if (game.gravity && !player->programmed_action) +#endif { int jx = player->jx, jy = player->jy; boolean field_under_player_is_free = @@ -9616,7 +11553,6 @@ boolean MovePlayerOneStep(struct PlayerInfo *player, if (!player_can_move) { -#if 1 if (player->MovPos == 0) { player->is_moving = FALSE; @@ -9625,18 +11561,16 @@ boolean MovePlayerOneStep(struct PlayerInfo *player, player->is_snapping = FALSE; player->is_pushing = FALSE; } -#else - DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH); - SnapField(player, 0, 0); -#endif - -#if 0 - return MP_NO_ACTION; -#endif } +#if 1 + if (!options.network && game.centered_player_nr == -1 && + !AllPlayersInSight(player, new_jx, new_jy)) + return MP_NO_ACTION; +#else if (!options.network && !AllPlayersInSight(player, new_jx, new_jy)) return MP_NO_ACTION; +#endif #if !USE_FIXED_DONT_RUN_INTO element = MovingOrBlocked2ElementIfNotLeaving(new_jx, new_jy); @@ -9661,18 +11595,9 @@ boolean MovePlayerOneStep(struct PlayerInfo *player, #endif can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG); -#if 0 -#if USE_FIXED_DONT_RUN_INTO - if (can_move == MP_DONT_RUN_INTO) - return MP_MOVING; -#endif -#endif if (can_move != MP_MOVING) return can_move; -#if USE_FIXED_DONT_RUN_INTO -#endif - /* check if DigField() has caused relocation of the player */ if (player->jx != jx || player->jy != jy) return MP_NO_ACTION; /* <-- !!! CHECK THIS [-> MP_ACTION ?] !!! */ @@ -9697,7 +11622,14 @@ boolean MovePlayerOneStep(struct PlayerInfo *player, PlayerVisit[jx][jy] = FrameCounter; +#if USE_UFAST_PLAYER_EXIT_BUGFIX + player->is_moving = TRUE; +#endif + +#if 1 + /* should better be called in MovePlayer(), but this breaks some tapes */ ScrollPlayer(player, SCROLL_INIT); +#endif return MP_MOVING; } @@ -9765,6 +11697,8 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy) player->move_delay_value = original_move_delay_value; } + player->is_active = FALSE; + if (player->last_move_dir & MV_HORIZONTAL) { if (!(moved |= MovePlayerOneStep(player, 0, dy, dx, dy))) @@ -9776,11 +11710,28 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy) moved |= MovePlayerOneStep(player, 0, dy, dx, dy); } +#if USE_FIXED_BORDER_RUNNING_GFX + if (!moved && !player->is_active) + { + player->is_moving = FALSE; + player->is_digging = FALSE; + player->is_collecting = FALSE; + player->is_snapping = FALSE; + player->is_pushing = FALSE; + } +#endif + jx = player->jx; jy = player->jy; +#if 1 + if (moved & MP_MOVING && !ScreenMovPos && + (player->index_nr == game.centered_player_nr || + game.centered_player_nr == -1)) +#else if (moved & MP_MOVING && !ScreenMovPos && (player == local_player || !options.network)) +#endif { int old_scroll_x = scroll_x, old_scroll_y = scroll_y; int offset = (setup.scroll_delay ? 3 : 0); @@ -9835,12 +11786,22 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy) if (scroll_x != old_scroll_x || scroll_y != old_scroll_y) { +#if 1 + if (!options.network && game.centered_player_nr == -1 && + !AllPlayersInVisibleScreen()) + { + scroll_x = old_scroll_x; + scroll_y = old_scroll_y; + } + else +#else if (!options.network && !AllPlayersInVisibleScreen()) { scroll_x = old_scroll_x; scroll_y = old_scroll_y; } else +#endif { ScrollScreen(player, SCROLL_INIT); ScrollLevel(old_scroll_x - scroll_x, old_scroll_y - scroll_y); @@ -9864,6 +11825,13 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy) player->is_snapping = FALSE; player->is_switching = FALSE; player->is_dropping = FALSE; + player->is_dropping_pressed = FALSE; + player->drop_pressed_delay = 0; + +#if 0 + /* should better be called here than above, but this breaks some tapes */ + ScrollPlayer(player, SCROLL_INIT); +#endif } else { @@ -9929,8 +11897,13 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) last_field_block_delay += player->move_delay_value; /* when blocking enabled, prevent moving up despite gravity */ +#if USE_PLAYER_GRAVITY + if (player->gravity && player->MovDir == MV_UP) + block_delay_adjustment = -1; +#else if (game.gravity && player->MovDir == MV_UP) block_delay_adjustment = -1; +#endif } /* add block delay adjustment (also possible when not blocking) */ @@ -9950,12 +11923,6 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) else if (!FrameReached(&player->actual_frame_counter, 1)) return; -#if 0 - printf("::: player->MovPos: %d -> %d\n", - player->MovPos, - player->MovPos + (player->MovPos > 0 ? -1 : 1) * move_stepsize); -#endif - #if USE_NEW_PLAYER_SPEED if (player->MovPos != 0) { @@ -9977,10 +11944,6 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) if (player->MovPos == 0) /* player reached destination field */ { -#if 0 - printf("::: player reached destination field\n"); -#endif - if (player->move_delay_reset_counter > 0) { player->move_delay_reset_counter--; @@ -9999,6 +11962,9 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) player->last_jy = jy; if (Feld[jx][jy] == EL_EXIT_OPEN || + Feld[jx][jy] == EL_EM_EXIT_OPEN || + Feld[jx][jy] == EL_STEEL_EXIT_OPEN || + Feld[jx][jy] == EL_EM_STEEL_EXIT_OPEN || Feld[jx][jy] == EL_SP_EXIT_OPEN || Feld[jx][jy] == EL_SP_EXIT_OPENING) /* <-- special case */ { @@ -10007,7 +11973,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) if (local_player->friends_still_needed == 0 || IS_SP_ELEMENT(Feld[jx][jy])) - player->LevelSolved = player->GameOver = TRUE; + PlayerWins(player); } /* this breaks one level: "machine", level 000 */ @@ -10055,7 +12021,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) RemovePlayer(player); } - if (level.use_step_counter) + if (!local_player->LevelSolved && level.use_step_counter) { int i; @@ -10066,7 +12032,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) TimeLeft--; if (TimeLeft <= 10 && setup.time_limit) - PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE); + PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); DrawGameValue_Time(TimeLeft); @@ -10674,6 +12640,23 @@ void KillPlayer(struct PlayerInfo *player) if (!player->active) return; + /* the following code was introduced to prevent an infinite loop when calling + -> Bang() + -> CheckTriggeredElementChangeExt() + -> ExecuteCustomElementAction() + -> KillPlayer() + -> (infinitely repeating the above sequence of function calls) + which occurs when killing the player while having a CE with the setting + "kill player X when explosion of "; the solution using a new + field "player->killed" was chosen for backwards compatibility, although + clever use of the fields "player->active" etc. would probably also work */ +#if 1 + if (player->killed) + return; +#endif + + player->killed = TRUE; + /* remove accessible field at the player's position */ Feld[jx][jy] = EL_EMPTY; @@ -10852,50 +12835,18 @@ int DigField(struct PlayerInfo *player, game.engine_version >= VERSION_IDENT(2,2,0,0)) old_element = Back[jx][jy]; -#if 0 -#if USE_FIXED_DONT_RUN_INTO - if (player_can_move && DONT_RUN_INTO(element)) - { - if (element == EL_ACID && dx == 0 && dy == 1) - { - SplashAcid(x, y); - Feld[jx][jy] = EL_PLAYER_1; - InitMovingField(jx, jy, MV_DOWN); - Store[jx][jy] = EL_ACID; - ContinueMoving(jx, jy); - BuryPlayer(player); - } - else - TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir); - - return MP_DONT_RUN_INTO; - } -#endif -#endif - -#if 1 -#if USE_FIXED_DONT_RUN_INTO - if (player_can_move && DONT_RUN_INTO(element)) - { - TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir); - - return MP_DONT_RUN_INTO; - } -#endif -#endif - 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)) return MP_NO_ACTION; /* field has no opening in this direction */ -#if 1 #if USE_FIXED_DONT_RUN_INTO if (player_can_move && element == EL_ACID && move_direction == MV_DOWN) { SplashAcid(x, y); - Feld[jx][jy] = EL_PLAYER_1; + + Feld[jx][jy] = player->artwork_element; InitMovingField(jx, jy, MV_DOWN); Store[jx][jy] = EL_ACID; ContinueMoving(jx, jy); @@ -10904,28 +12855,15 @@ int DigField(struct PlayerInfo *player, return MP_DONT_RUN_INTO; } #endif -#endif -#if 0 #if USE_FIXED_DONT_RUN_INTO if (player_can_move && DONT_RUN_INTO(element)) { - if (element == EL_ACID && dx == 0 && dy == 1) - { - SplashAcid(x, y); - Feld[jx][jy] = EL_PLAYER_1; - InitMovingField(jx, jy, MV_DOWN); - Store[jx][jy] = EL_ACID; - ContinueMoving(jx, jy); - BuryPlayer(player); - } - else - TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir); + TestIfPlayerRunsIntoBadThing(jx, jy, player->MovDir); return MP_DONT_RUN_INTO; } #endif -#endif #if USE_FIXED_DONT_RUN_INTO if (IS_MOVING(x, y) || IS_PLAYER(x, y)) @@ -10952,16 +12890,26 @@ int DigField(struct PlayerInfo *player, CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X, player->index_bit, dig_side); + if (element == EL_DC_LANDMINE) + Bang(x, y); + if (Feld[x][y] != element) /* field changed by snapping */ return MP_ACTION; return MP_NO_ACTION; } +#if USE_PLAYER_GRAVITY + if (player->gravity && is_player && !player->is_auto_moving && + canFallDown(player) && move_direction != MV_DOWN && + !canMoveToValidFieldWithGravity(jx, jy, move_direction)) + return MP_NO_ACTION; /* player cannot walk here due to gravity */ +#else if (game.gravity && is_player && !player->is_auto_moving && canFallDown(player) && move_direction != MV_DOWN && !canMoveToValidFieldWithGravity(jx, jy, move_direction)) return MP_NO_ACTION; /* player cannot walk here due to gravity */ +#endif if (player_can_move && IS_WALKABLE(element) && ACCESS_FROM(element, opposite_direction)) @@ -10985,6 +12933,9 @@ int DigField(struct PlayerInfo *player, return MP_NO_ACTION; } else if (element == EL_EXIT_OPEN || + element == EL_EM_EXIT_OPEN || + element == EL_STEEL_EXIT_OPEN || + element == EL_EM_STEEL_EXIT_OPEN || element == EL_SP_EXIT_OPEN || element == EL_SP_EXIT_OPENING) { @@ -11025,23 +12976,59 @@ int DigField(struct PlayerInfo *player, if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)]) return MP_NO_ACTION; } + else if (IS_EMC_GATE(element)) + { + if (!player->key[EMC_GATE_NR(element)]) + return MP_NO_ACTION; + } + else if (IS_EMC_GATE_GRAY(element)) + { + if (!player->key[EMC_GATE_GRAY_NR(element)]) + return MP_NO_ACTION; + } + else if (IS_EMC_GATE_GRAY_ACTIVE(element)) + { + if (!player->key[EMC_GATE_GRAY_ACTIVE_NR(element)]) + return MP_NO_ACTION; + } + else if (element == EL_DC_GATE_WHITE || + element == EL_DC_GATE_WHITE_GRAY || + element == EL_DC_GATE_WHITE_GRAY_ACTIVE) + { + if (player->num_white_keys == 0) + return MP_NO_ACTION; + + player->num_white_keys--; + } else if (IS_SP_PORT(element)) { if (element == EL_SP_GRAVITY_PORT_LEFT || element == EL_SP_GRAVITY_PORT_RIGHT || element == EL_SP_GRAVITY_PORT_UP || element == EL_SP_GRAVITY_PORT_DOWN) +#if USE_PLAYER_GRAVITY + player->gravity = !player->gravity; +#else game.gravity = !game.gravity; +#endif else if (element == EL_SP_GRAVITY_ON_PORT_LEFT || element == EL_SP_GRAVITY_ON_PORT_RIGHT || element == EL_SP_GRAVITY_ON_PORT_UP || element == EL_SP_GRAVITY_ON_PORT_DOWN) +#if USE_PLAYER_GRAVITY + player->gravity = TRUE; +#else game.gravity = TRUE; +#endif else if (element == EL_SP_GRAVITY_OFF_PORT_LEFT || element == EL_SP_GRAVITY_OFF_PORT_RIGHT || element == EL_SP_GRAVITY_OFF_PORT_UP || element == EL_SP_GRAVITY_OFF_PORT_DOWN) +#if USE_PLAYER_GRAVITY + player->gravity = FALSE; +#else game.gravity = FALSE; +#endif } /* automatically move to the next field with double speed */ @@ -11118,7 +13105,7 @@ int DigField(struct PlayerInfo *player, if (player->inventory_size < MAX_INVENTORY_SIZE) player->inventory_element[player->inventory_size++] = element; - DrawGameValue_Dynamite(local_player->inventory_size); + DrawGameDoorValues(); } else if (element == EL_DYNABOMB_INCREASE_NUMBER) { @@ -11137,9 +13124,14 @@ int DigField(struct PlayerInfo *player, { player->key[KEY_NR(element)] = TRUE; - DrawGameValue_Keys(player->key); + DrawGameDoorValues(); + } + else if (element == EL_DC_KEY_WHITE) + { + player->num_white_keys++; - redraw_mask |= REDRAW_DOOR_1; + /* display white keys? */ + /* DrawGameDoorValues(); */ } else if (IS_ENVELOPE(element)) { @@ -11169,7 +13161,7 @@ int DigField(struct PlayerInfo *player, if (player->inventory_size < MAX_INVENTORY_SIZE) player->inventory_element[player->inventory_size++] = element; - DrawGameValue_Dynamite(local_player->inventory_size); + DrawGameDoorValues(); } else if (collect_count > 0) { @@ -11249,6 +13241,7 @@ int DigField(struct PlayerInfo *player, } player->is_pushing = TRUE; + player->is_active = TRUE; if (!(IN_LEV_FIELD(nextx, nexty) && (IS_FREE(nextx, nexty) || @@ -11301,7 +13294,8 @@ int DigField(struct PlayerInfo *player, if (local_player->sokobanfields_still_needed == 0 && game.emulation == EMU_SOKOBAN) { - player->LevelSolved = player->GameOver = TRUE; + PlayerWins(player); + PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING); } } @@ -11361,11 +13355,7 @@ int DigField(struct PlayerInfo *player, { int xx, yy; -#if 1 SCAN_PLAYFIELD(xx, yy) -#else - for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++) -#endif { if (Feld[xx][yy] == EL_SP_DISK_YELLOW) Bang(xx, yy); @@ -11378,7 +13368,9 @@ int DigField(struct PlayerInfo *player, ToggleBeltSwitch(x, y); } else if (element == EL_SWITCHGATE_SWITCH_UP || - element == EL_SWITCHGATE_SWITCH_DOWN) + element == EL_SWITCHGATE_SWITCH_DOWN || + element == EL_DC_SWITCHGATE_SWITCH_UP || + element == EL_DC_SWITCHGATE_SWITCH_DOWN) { ToggleSwitchgateSwitch(x, y); } @@ -11387,7 +13379,8 @@ int DigField(struct PlayerInfo *player, { ToggleLightSwitch(x, y); } - else if (element == EL_TIMEGATE_SWITCH) + else if (element == EL_TIMEGATE_SWITCH || + element == EL_DC_TIMEGATE_SWITCH) { ActivateTimegateSwitch(x, y); } @@ -11433,11 +13426,7 @@ int DigField(struct PlayerInfo *player, game.ball_state = !game.ball_state; -#if 1 SCAN_PLAYFIELD(xx, yy) -#else - for (yy = 0; yy < lev_fieldy; yy++) for (xx = 0; xx < lev_fieldx; xx++) -#endif { int e = Feld[xx][yy]; @@ -11501,7 +13490,10 @@ 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 */ + { player->is_collecting = !player->is_digging; + player->is_active = TRUE; + } } return MP_MOVING; @@ -11515,6 +13507,8 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy) dx == +1 ? MV_RIGHT : dy == -1 ? MV_UP : dy == +1 ? MV_DOWN : MV_NONE); + boolean can_continue_snapping = (level.continuous_snapping && + WasJustFalling[x][y] < CHECK_DELAY_FALLING); if (player->MovPos != 0 && game.engine_version >= VERSION_IDENT(2,2,0,0)) return FALSE; @@ -11542,8 +13536,14 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy) return FALSE; } +#if USE_NEW_CONTINUOUS_SNAPPING + /* prevent snapping with already pressed snap key when not allowed */ + if (player->is_snapping && !can_continue_snapping) + return FALSE; +#else if (player->is_snapping) return FALSE; +#endif player->MovDir = snap_direction; @@ -11555,11 +13555,14 @@ boolean SnapField(struct PlayerInfo *player, int dx, int dy) } player->is_dropping = FALSE; + player->is_dropping_pressed = FALSE; + player->drop_pressed_delay = 0; if (DigField(player, jx, jy, x, y, 0, 0, DF_SNAP) == MP_NO_ACTION) return FALSE; player->is_snapping = TRUE; + player->is_active = TRUE; if (player->MovPos == 0) { @@ -11590,6 +13593,8 @@ boolean DropElement(struct PlayerInfo *player) EL_DYNABOMB_PLAYER_1_ACTIVE + player->index_nr : EL_UNDEFINED); + player->is_dropping_pressed = TRUE; + /* do not drop an element on top of another element; when holding drop key pressed without moving, dropped element must move away before the next element can be dropped (this is especially important if the next element @@ -11617,6 +13622,10 @@ boolean DropElement(struct PlayerInfo *player) if (new_element == EL_UNDEFINED) return FALSE; + /* check if drop key was pressed long enough for EM style dynamite */ + if (new_element == EL_EM_DYNAMITE && player->drop_pressed_delay < 40) + return FALSE; + /* check if anything can be dropped at the current position */ if (IS_ACTIVE_BOMB(old_element) || old_element == EL_EXPLOSION) return FALSE; @@ -11638,7 +13647,7 @@ boolean DropElement(struct PlayerInfo *player) { player->inventory_size--; - DrawGameValue_Dynamite(local_player->inventory_size); + DrawGameDoorValues(); if (new_element == EL_DYNAMITE) new_element = EL_DYNAMITE_ACTIVE; @@ -11698,12 +13707,21 @@ boolean DropElement(struct PlayerInfo *player) nexty = dropy + GET_DY_FROM_DIR(move_direction); ChangeCount[dropx][dropy] = 0; /* allow at least one more change */ - CheckCollision[dropx][dropy] = 2; + +#if USE_FIX_IMPACT_COLLISION + /* do not cause impact style collision by dropping elements that can fall */ + CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION; +#else + CheckCollision[dropx][dropy] = CHECK_DELAY_COLLISION; +#endif } player->drop_delay = GET_NEW_DROP_DELAY(drop_element); player->is_dropping = TRUE; + player->drop_pressed_delay = 0; + player->is_dropping_pressed = FALSE; + player->drop_x = dropx; player->drop_y = dropy; @@ -11828,9 +13846,12 @@ static void PlayLevelMusic() PlayMusic(MAP_NOCONF_MUSIC(level_nr)); /* from music dir */ } -void PlayLevelSound_EM(int x, int y, int element_em, int sample) +void PlayLevelSound_EM(int xx, int yy, int element_em, int sample) { int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0); + int offset = (BorderElement == EL_STEELWALL ? 1 : 0); + int x = xx - 1 - offset; + int y = yy - 1 - offset; switch (sample) { @@ -11976,7 +13997,7 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample) break; case SAMPLE_time: - PlaySoundStereo(SND_GAME_RUNNING_OUT_OF_TIME, SOUND_MIDDLE); + PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); break; default: @@ -11985,6 +14006,31 @@ void PlayLevelSound_EM(int x, int y, int element_em, int sample) } } +#if 0 +void ChangeTime(int value) +{ + int *time = (level.time == 0 ? &TimePlayed : &TimeLeft); + + *time += value; + + /* EMC game engine uses value from time counter of RND game engine */ + level.native_em_level->lev->time = *time; + + DrawGameValue_Time(*time); +} + +void RaiseScore(int value) +{ + /* EMC game engine and RND game engine have separate score counters */ + int *score = (level.game_engine_type == GAME_ENGINE_TYPE_EM ? + &level.native_em_level->lev->score : &local_player->score); + + *score += value; + + DrawGameValue_Score(*score); +} +#endif + void RaiseScore(int value) { local_player->score += value; @@ -11994,7 +14040,7 @@ void RaiseScore(int value) void RaiseScoreElement(int element) { - switch(element) + switch (element) { case EL_EMERALD: case EL_BD_DIAMOND: @@ -12063,6 +14109,7 @@ void RaiseScoreElement(int element) case EL_EMC_KEY_6: case EL_EMC_KEY_7: case EL_EMC_KEY_8: + case EL_DC_KEY_WHITE: RaiseScore(level.score[SC_KEY]); break; default: @@ -12071,25 +14118,52 @@ void RaiseScoreElement(int element) } } -void RequestQuitGame(boolean ask_if_really_quit) +void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message) { - if (AllPlayersGone || - !ask_if_really_quit || - level_editor_test_game || - Request("Do you really want to quit the game ?", - REQ_ASK | REQ_STAY_CLOSED)) + if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED)) { #if defined(NETWORK_AVALIABLE) if (options.network) - SendToServer_StopPlaying(); + SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER); else #endif { - game_status = GAME_MODE_MAIN; - DrawMainMenu(); + if (quick_quit) + { +#if 1 + fading = fading_none; +#else + OpenDoor(DOOR_CLOSE_1); +#endif + + game_status = GAME_MODE_MAIN; + +#if 1 + DrawAndFadeInMainMenu(REDRAW_FIELD); +#else + DrawMainMenu(); +#endif + } + else + { +#if 0 +#if 1 + if (fading.anim_mode == ANIM_CROSSFADE) + FadeCrossSaveBackbuffer(); + else + FadeOut(REDRAW_FIELD); +#else + FadeOut(REDRAW_FIELD); +#endif +#endif + + game_status = GAME_MODE_MAIN; + + DrawAndFadeInMainMenu(REDRAW_FIELD); + } } } - else + else /* continue playing the game */ { if (tape.playing && tape.deactivate_display) TapeDeactivateDisplayOff(TRUE); @@ -12101,6 +14175,325 @@ void RequestQuitGame(boolean ask_if_really_quit) } } +void RequestQuitGame(boolean ask_if_really_quit) +{ + boolean quick_quit = (!ask_if_really_quit || level_editor_test_game); + boolean skip_request = AllPlayersGone || quick_quit; + + RequestQuitGameExt(skip_request, quick_quit, + "Do you really want to quit the game ?"); +} + + +/* ------------------------------------------------------------------------- */ +/* random generator functions */ +/* ------------------------------------------------------------------------- */ + +unsigned int InitEngineRandom_RND(long seed) +{ + game.num_random_calls = 0; + +#if 0 + unsigned int rnd_seed = InitEngineRandom(seed); + + printf("::: START RND: %d\n", rnd_seed); + + return rnd_seed; +#else + + return InitEngineRandom(seed); + +#endif + +} + +unsigned int RND(int max) +{ + if (max > 0) + { + game.num_random_calls++; + + return GetEngineRandom(max); + } + + return 0; +} + + +/* ------------------------------------------------------------------------- */ +/* game engine snapshot handling functions */ +/* ------------------------------------------------------------------------- */ + +#define ARGS_ADDRESS_AND_SIZEOF(x) (&(x)), (sizeof(x)) + +struct EngineSnapshotInfo +{ + /* runtime values for custom element collect score */ + int collect_score[NUM_CUSTOM_ELEMENTS]; + + /* runtime values for group element choice position */ + int choice_pos[NUM_GROUP_ELEMENTS]; + + /* runtime values for belt position animations */ + int belt_graphic[4 * NUM_BELT_PARTS]; + int belt_anim_mode[4 * NUM_BELT_PARTS]; +}; + +struct EngineSnapshotNodeInfo +{ + void *buffer_orig; + void *buffer_copy; + int size; +}; + +static struct EngineSnapshotInfo engine_snapshot_rnd; +static ListNode *engine_snapshot_list = NULL; +static char *snapshot_level_identifier = NULL; +static int snapshot_level_nr = -1; + +void FreeEngineSnapshot() +{ + while (engine_snapshot_list != NULL) + deleteNodeFromList(&engine_snapshot_list, engine_snapshot_list->key, + checked_free); + + setString(&snapshot_level_identifier, NULL); + snapshot_level_nr = -1; +} + +static void SaveEngineSnapshotValues_RND() +{ + static int belt_base_active_element[4] = + { + EL_CONVEYOR_BELT_1_LEFT_ACTIVE, + EL_CONVEYOR_BELT_2_LEFT_ACTIVE, + EL_CONVEYOR_BELT_3_LEFT_ACTIVE, + EL_CONVEYOR_BELT_4_LEFT_ACTIVE + }; + int i, j; + + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + engine_snapshot_rnd.collect_score[i] = element_info[element].collect_score; + } + + for (i = 0; i < NUM_GROUP_ELEMENTS; i++) + { + int element = EL_GROUP_START + i; + + engine_snapshot_rnd.choice_pos[i] = element_info[element].group->choice_pos; + } + + for (i = 0; i < 4; i++) + { + for (j = 0; j < NUM_BELT_PARTS; j++) + { + int element = belt_base_active_element[i] + j; + int graphic = el2img(element); + int anim_mode = graphic_info[graphic].anim_mode; + + engine_snapshot_rnd.belt_graphic[i * 4 + j] = graphic; + engine_snapshot_rnd.belt_anim_mode[i * 4 + j] = anim_mode; + } + } +} + +static void LoadEngineSnapshotValues_RND() +{ + unsigned long num_random_calls = game.num_random_calls; + int i, j; + + for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) + { + int element = EL_CUSTOM_START + i; + + element_info[element].collect_score = engine_snapshot_rnd.collect_score[i]; + } + + for (i = 0; i < NUM_GROUP_ELEMENTS; i++) + { + int element = EL_GROUP_START + i; + + element_info[element].group->choice_pos = engine_snapshot_rnd.choice_pos[i]; + } + + for (i = 0; i < 4; i++) + { + for (j = 0; j < NUM_BELT_PARTS; j++) + { + int graphic = engine_snapshot_rnd.belt_graphic[i * 4 + j]; + int anim_mode = engine_snapshot_rnd.belt_anim_mode[i * 4 + j]; + + graphic_info[graphic].anim_mode = anim_mode; + } + } + + if (level.game_engine_type == GAME_ENGINE_TYPE_RND) + { + InitRND(tape.random_seed); + for (i = 0; i < num_random_calls; i++) + RND(1); + } + + 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"); + } +} + +static void SaveEngineSnapshotBuffer(void *buffer, int size) +{ + struct EngineSnapshotNodeInfo *bi = + checked_calloc(sizeof(struct EngineSnapshotNodeInfo)); + + bi->buffer_orig = buffer; + bi->buffer_copy = checked_malloc(size); + bi->size = size; + + memcpy(bi->buffer_copy, buffer, size); + + addNodeToList(&engine_snapshot_list, NULL, bi); +} + +void SaveEngineSnapshot() +{ + FreeEngineSnapshot(); /* free previous snapshot, if needed */ + + if (level_editor_test_game) /* do not save snapshots from editor */ + return; + + /* copy some special values to a structure better suited for the snapshot */ + + SaveEngineSnapshotValues_RND(); + SaveEngineSnapshotValues_EM(); + + /* save values stored in special snapshot structure */ + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em)); + + /* save further RND engine values */ + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(stored_player)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(game)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(tape)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZX)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZY)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitX)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitY)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(FrameCounter)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeFrames)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimePlayed)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeLeft)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TapeTime)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Feld)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovPos)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDir)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDelay)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeDelay)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangePage)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CustomValue)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store2)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(StorePlayer)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Back)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaNr)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustMoving)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustFalling)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckCollision)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckImpact)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Stop)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Pushed)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeCount)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeEvent)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodePhase)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeField)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(RunnerVisit)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(PlayerVisit)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxFrame)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxRandom)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxElement)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxAction)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxDir)); + + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_x)); + SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_y)); + + /* save level identification information */ + + setString(&snapshot_level_identifier, leveldir_current->identifier); + snapshot_level_nr = level_nr; + +#if 0 + ListNode *node = engine_snapshot_list; + int num_bytes = 0; + + while (node != NULL) + { + num_bytes += ((struct EngineSnapshotNodeInfo *)node->content)->size; + + node = node->next; + } + + printf("::: size of engine snapshot: %d bytes\n", num_bytes); +#endif +} + +static void LoadEngineSnapshotBuffer(struct EngineSnapshotNodeInfo *bi) +{ + memcpy(bi->buffer_orig, bi->buffer_copy, bi->size); +} + +void LoadEngineSnapshot() +{ + ListNode *node = engine_snapshot_list; + + if (engine_snapshot_list == NULL) + return; + + while (node != NULL) + { + LoadEngineSnapshotBuffer((struct EngineSnapshotNodeInfo *)node->content); + + node = node->next; + } + + /* restore special values from snapshot structure */ + + LoadEngineSnapshotValues_RND(); + LoadEngineSnapshotValues_EM(); +} + +boolean CheckEngineSnapshot() +{ + return (strEqual(snapshot_level_identifier, leveldir_current->identifier) && + snapshot_level_nr == level_nr); +} + /* ---------- new game button stuff ---------------------------------------- */ @@ -12121,11 +14514,50 @@ void RequestQuitGame(boolean ask_if_really_quit) static struct { - int x, y; + int *x, *y; + int gd_x, gd_y; int gadget_id; char *infotext; } gamebutton_info[NUM_GAME_BUTTONS] = { +#if 1 + { + &game.button.stop.x, &game.button.stop.y, + GAME_BUTTON_STOP_XPOS, GAME_BUTTON_YPOS, + GAME_CTRL_ID_STOP, + "stop game" + }, + { + &game.button.pause.x, &game.button.pause.y, + GAME_BUTTON_PAUSE_XPOS, GAME_BUTTON_YPOS, + GAME_CTRL_ID_PAUSE, + "pause game" + }, + { + &game.button.play.x, &game.button.play.y, + GAME_BUTTON_PLAY_XPOS, GAME_BUTTON_YPOS, + GAME_CTRL_ID_PLAY, + "play game" + }, + { + &game.button.sound_music.x, &game.button.sound_music.y, + SOUND_BUTTON_MUSIC_XPOS, SOUND_BUTTON_YPOS, + SOUND_CTRL_ID_MUSIC, + "background music on/off" + }, + { + &game.button.sound_loops.x, &game.button.sound_loops.y, + SOUND_BUTTON_LOOPS_XPOS, SOUND_BUTTON_YPOS, + SOUND_CTRL_ID_LOOPS, + "sound loops on/off" + }, + { + &game.button.sound_simple.x,&game.button.sound_simple.y, + SOUND_BUTTON_SIMPLE_XPOS, SOUND_BUTTON_YPOS, + SOUND_CTRL_ID_SIMPLE, + "normal sounds on/off" + } +#else { GAME_BUTTON_STOP_XPOS, GAME_BUTTON_YPOS, GAME_CTRL_ID_STOP, @@ -12156,6 +14588,7 @@ static struct SOUND_CTRL_ID_SIMPLE, "normal sounds on/off" } +#endif }; void CreateGameButtons() @@ -12169,12 +14602,15 @@ void CreateGameButtons() int button_type; boolean checked; unsigned long event_mask; + int x, y; int gd_xoffset, gd_yoffset; int gd_x1, gd_x2, gd_y1, gd_y2; int id = i; - gd_xoffset = gamebutton_info[i].x; - gd_yoffset = gamebutton_info[i].y; + x = DX + *gamebutton_info[i].x; + y = DY + *gamebutton_info[i].y; + gd_xoffset = gamebutton_info[i].gd_x; + gd_yoffset = gamebutton_info[i].gd_y; gd_x1 = DOOR_GFX_PAGEX4 + gd_xoffset; gd_x2 = DOOR_GFX_PAGEX3 + gd_xoffset; @@ -12202,8 +14638,13 @@ void CreateGameButtons() gi = CreateGadget(GDI_CUSTOM_ID, id, GDI_INFO_TEXT, gamebutton_info[i].infotext, +#if 1 + GDI_X, x, + GDI_Y, y, +#else GDI_X, DX + gd_xoffset, GDI_Y, DY + gd_yoffset, +#endif GDI_WIDTH, GAME_BUTTON_XSIZE, GDI_HEIGHT, GAME_BUTTON_YSIZE, GDI_TYPE, button_type, @@ -12258,7 +14699,10 @@ static void HandleGameButtons(struct GadgetInfo *gi) switch (id) { case GAME_CTRL_ID_STOP: - RequestQuitGame(TRUE); + if (tape.playing) + TapeStop(); + else + RequestQuitGame(TRUE); break; case GAME_CTRL_ID_PAUSE: @@ -12285,7 +14729,7 @@ static void HandleGameButtons(struct GadgetInfo *gi) #endif { tape.pausing = FALSE; - DrawVideoDisplay(VIDEO_STATE_PAUSE_OFF,0); + DrawVideoDisplay(VIDEO_STATE_PAUSE_OFF, 0); } } break;