Before this change, when CEs are triggered by the player digging or
collecting some tile or element, the trigger action was executed
immediately when the player started digging or collecting.
While this generally works fine, it looks (and behaves) strange when
the related CE action causes the player to relocate to a different
position in the playfield (CEs that are "teleporting" the player). In
such cases, the player's dig or collect action is usually executed at
both the old and the new player position, which is not how it really
should work.
This change fixes this behaviour by delaying the CE trigger action
until the dig or collect action is finished, so that only the tile at
the old player position gets digged or collected, but not the tile at
the new player position (if the triggered CE causes the player to
relocate).
This commit improves the workaround in commit
fd0ec980.
GADGET_ID_SB_OBJECTS_NEEDED,
GADGET_ID_AUTO_EXIT_SOKOBAN,
GADGET_ID_SOLVED_BY_ONE_PLAYER,
GADGET_ID_SB_OBJECTS_NEEDED,
GADGET_ID_AUTO_EXIT_SOKOBAN,
GADGET_ID_SOLVED_BY_ONE_PLAYER,
+ GADGET_ID_FINISH_DIG_COLLECT,
GADGET_ID_CONTINUOUS_SNAPPING,
GADGET_ID_BLOCK_SNAP_FIELD,
GADGET_ID_BLOCK_LAST_FIELD,
GADGET_ID_CONTINUOUS_SNAPPING,
GADGET_ID_BLOCK_SNAP_FIELD,
GADGET_ID_BLOCK_LAST_FIELD,
ED_CHECKBUTTON_ID_SB_OBJECTS_NEEDED,
ED_CHECKBUTTON_ID_AUTO_EXIT_SOKOBAN,
ED_CHECKBUTTON_ID_SOLVED_BY_ONE_PLAYER,
ED_CHECKBUTTON_ID_SB_OBJECTS_NEEDED,
ED_CHECKBUTTON_ID_AUTO_EXIT_SOKOBAN,
ED_CHECKBUTTON_ID_SOLVED_BY_ONE_PLAYER,
+ ED_CHECKBUTTON_ID_FINISH_DIG_COLLECT,
ED_CHECKBUTTON_ID_CONTINUOUS_SNAPPING,
ED_CHECKBUTTON_ID_BLOCK_SNAP_FIELD,
ED_CHECKBUTTON_ID_BLOCK_LAST_FIELD,
ED_CHECKBUTTON_ID_CONTINUOUS_SNAPPING,
ED_CHECKBUTTON_ID_BLOCK_SNAP_FIELD,
ED_CHECKBUTTON_ID_BLOCK_LAST_FIELD,
NULL, NULL,
"only one player must enter exit", "level solved by first player in exit"
},
NULL, NULL,
"only one player must enter exit", "level solved by first player in exit"
},
+ {
+ ED_ELEMENT_SETTINGS_XPOS(0), ED_ELEMENT_SETTINGS_YPOS(3),
+ GADGET_ID_FINISH_DIG_COLLECT, GADGET_ID_NONE,
+ &level.finish_dig_collect,
+ NULL, NULL,
+ "CE action on finished dig/collect", "only finished dig/collect triggers CE"
+ },
{
ED_ELEMENT_SETTINGS_XPOS(0), ED_ELEMENT_SETTINGS_YPOS(9),
GADGET_ID_CONTINUOUS_SNAPPING, GADGET_ID_NONE,
{
ED_ELEMENT_SETTINGS_XPOS(0), ED_ELEMENT_SETTINGS_YPOS(9),
GADGET_ID_CONTINUOUS_SNAPPING, GADGET_ID_NONE,
// draw checkbutton gadgets
MapCheckbuttonGadget(ED_CHECKBUTTON_ID_USE_INITIAL_INVENTORY);
// draw checkbutton gadgets
MapCheckbuttonGadget(ED_CHECKBUTTON_ID_USE_INITIAL_INVENTORY);
+ MapCheckbuttonGadget(ED_CHECKBUTTON_ID_FINISH_DIG_COLLECT);
// draw counter gadgets
MapCounterButtons(ED_COUNTER_ID_INVENTORY_SIZE);
// draw counter gadgets
MapCounterButtons(ED_COUNTER_ID_INVENTORY_SIZE);
TYPE_BOOLEAN, CONF_VALUE_8_BIT(15),
&li.lazy_relocation, FALSE
},
TYPE_BOOLEAN, CONF_VALUE_8_BIT(15),
&li.lazy_relocation, FALSE
},
+ {
+ EL_PLAYER_1, -1,
+ TYPE_BOOLEAN, CONF_VALUE_8_BIT(16),
+ &li.finish_dig_collect, TRUE
+ },
// (these values are different for each player)
{
// (these values are different for each player)
{
// only Sokoban fields (but not objects) had to be solved before 4.1.1.1
if (level->game_version < VERSION_IDENT(4,1,1,1))
level->sb_objects_needed = FALSE;
// only Sokoban fields (but not objects) had to be solved before 4.1.1.1
if (level->game_version < VERSION_IDENT(4,1,1,1))
level->sb_objects_needed = FALSE;
+
+ // CE actions were triggered by unfinished digging/collecting up to 4.2.2.0
+ if (level->game_version <= VERSION_IDENT(4,2,2,0))
+ level->finish_dig_collect = FALSE;
}
static void LoadLevel_InitStandardElements(struct LevelInfo *level)
}
static void LoadLevel_InitStandardElements(struct LevelInfo *level)
static int getInvisibleActiveFromInvisibleElement(int);
static int getInvisibleFromInvisibleActiveElement(int);
static int getInvisibleActiveFromInvisibleElement(int);
static int getInvisibleFromInvisibleActiveElement(int);
+static void TestFieldAfterSnapping(int, int, int, int, int);
+
static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
// for detection of endless loops, caused by custom element programming
static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
// for detection of endless loops, caused by custom element programming
player->shield_normal_time_left = 0;
player->shield_deadly_time_left = 0;
player->shield_normal_time_left = 0;
player->shield_deadly_time_left = 0;
+ player->last_removed_element = EL_UNDEFINED;
+
player->inventory_infinite_element = EL_UNDEFINED;
player->inventory_size = 0;
player->inventory_infinite_element = EL_UNDEFINED;
player->inventory_size = 0;
MovDelay[x][y]--;
if (MovDelay[x][y] <= 0)
{
MovDelay[x][y]--;
if (MovDelay[x][y] <= 0)
{
+ int element = Store[x][y];
+ int move_direction = MovDir[x][y];
+ int player_index_bit = Store2[x][y];
+
+ Store[x][y] = 0;
+ Store2[x][y] = 0;
+
RemoveField(x, y);
TEST_DrawLevelField(x, y);
RemoveField(x, y);
TEST_DrawLevelField(x, y);
- TestIfElementTouchesCustomElement(x, y); // for empty space
+ TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
if (!player->is_pushing)
TestIfElementTouchesCustomElement(jx, jy); // for empty space
if (!player->is_pushing)
TestIfElementTouchesCustomElement(jx, jy); // for empty space
+ if (level.finish_dig_collect &&
+ (player->is_digging || player->is_collecting))
+ {
+ int last_element = player->last_removed_element;
+ int move_direction = player->MovDir;
+ int enter_side = MV_DIR_OPPOSITE(move_direction);
+ int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
+ CE_PLAYER_COLLECTS_X);
+
+ CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
+ player->index_bit, enter_side);
+
+ player->last_removed_element = EL_UNDEFINED;
+ }
+
if (!player->active)
RemovePlayer(player);
}
if (!player->active)
RemovePlayer(player);
}
game.players_still_needed--;
}
game.players_still_needed--;
}
-static void setFieldForSnapping(int x, int y, int element, int direction)
+static void SetFieldForSnapping(int x, int y, int element, int direction,
+ int player_index_bit)
{
struct ElementInfo *ei = &element_info[element];
int direction_bit = MV_DIR_TO_BIT(direction);
{
struct ElementInfo *ei = &element_info[element];
int direction_bit = MV_DIR_TO_BIT(direction);
Tile[x][y] = EL_ELEMENT_SNAPPING;
MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
Tile[x][y] = EL_ELEMENT_SNAPPING;
MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
+ MovDir[x][y] = direction;
+ Store[x][y] = element;
+ Store2[x][y] = player_index_bit;
+static void TestFieldAfterSnapping(int x, int y, int element, int direction,
+ int player_index_bit)
+{
+ TestIfElementTouchesCustomElement(x, y); // for empty space
+
+ if (level.finish_dig_collect)
+ {
+ int dig_side = MV_DIR_OPPOSITE(direction);
+
+ CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+ player_index_bit, dig_side);
+ }
+}
+
/*
=============================================================================
checkDiagonalPushing()
/*
=============================================================================
checkDiagonalPushing()
PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
- CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
- player->index_bit, dig_side);
+ // use old behaviour for old levels (digging)
+ if (!level.finish_dig_collect)
+ {
+ CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
+ player->index_bit, dig_side);
- // if digging triggered player relocation, finish digging tile
- if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
- setFieldForSnapping(x, y, element, move_direction);
+ // if digging triggered player relocation, finish digging tile
+ if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
+ SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
+ }
if (mode == DF_SNAP)
{
if (level.block_snap_field)
if (mode == DF_SNAP)
{
if (level.block_snap_field)
- setFieldForSnapping(x, y, element, move_direction);
+ SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
- TestIfElementTouchesCustomElement(x, y); // for empty space
+ TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
- CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
- player->index_bit, dig_side);
+ // use old behaviour for old levels (snapping)
+ if (!level.finish_dig_collect)
+ CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+ player->index_bit, dig_side);
}
}
else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
}
}
else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
RaiseScoreElement(element);
PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
RaiseScoreElement(element);
PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
+ // use old behaviour for old levels (collecting)
+ if (!level.finish_dig_collect && is_player)
{
CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
player->index_bit, dig_side);
// if collecting triggered player relocation, finish collecting tile
if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
{
CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
player->index_bit, dig_side);
// if collecting triggered player relocation, finish collecting tile
if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
- setFieldForSnapping(x, y, element, move_direction);
+ SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
}
if (mode == DF_SNAP)
{
if (level.block_snap_field)
}
if (mode == DF_SNAP)
{
if (level.block_snap_field)
- setFieldForSnapping(x, y, element, move_direction);
+ SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
- TestIfElementTouchesCustomElement(x, y); // for empty space
+ TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
- CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
- player->index_bit, dig_side);
+ // use old behaviour for old levels (snapping)
+ if (!level.finish_dig_collect)
+ CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+ player->index_bit, dig_side);
}
}
else if (player_can_move_or_snap && IS_PUSHABLE(element))
}
}
else if (player_can_move_or_snap && IS_PUSHABLE(element))
{
player->is_collecting = !player->is_digging;
player->is_active = TRUE;
{
player->is_collecting = !player->is_digging;
player->is_active = TRUE;
+
+ player->last_removed_element = element;
int shield_normal_time_left;
int shield_deadly_time_left;
int shield_normal_time_left;
int shield_deadly_time_left;
+ int last_removed_element;
+
int inventory_element[MAX_INVENTORY_SIZE];
int inventory_infinite_element;
int inventory_size;
int inventory_element[MAX_INVENTORY_SIZE];
int inventory_infinite_element;
int inventory_size;
boolean sb_objects_needed; // all Sokoban objects must be solved
boolean auto_exit_sokoban; // automatically finish solved Sokoban levels
boolean solved_by_one_player; // level is solved if one player enters exit
boolean sb_objects_needed; // all Sokoban objects must be solved
boolean auto_exit_sokoban; // automatically finish solved Sokoban levels
boolean solved_by_one_player; // level is solved if one player enters exit
+ boolean finish_dig_collect; // only finished dig/collect triggers ce action
boolean continuous_snapping; // repeated snapping without releasing key
boolean block_snap_field; // snapping blocks field to show animation
boolean continuous_snapping; // repeated snapping without releasing key
boolean block_snap_field; // snapping blocks field to show animation