+static int getModifiedActionNumber(int value_old, int value_min, int value_max,
+ int operator, int operand)
+{
+ int value_new = (operator == CA_MODE_ADD ? value_old + operand :
+ operator == CA_MODE_SUBTRACT ? value_old - operand :
+ operator == CA_MODE_MULTIPLY ? value_old * operand :
+ operator == CA_MODE_DIVIDE ? value_old / MAX(1, operand) :
+ operator == CA_MODE_SET ? operand :
+ value_old);
+
+ return (value_new < value_min ? value_min :
+ value_new > value_max ? value_max :
+ value_new);
+}
+
+static void ExecuteCustomElementAction(int element, int page)
+{
+ struct ElementInfo *ei = &element_info[element];
+ struct ElementChangeInfo *change = &ei->change_page[page];
+ int action_type = change->action_type;
+ int action_mode = change->action_mode;
+ int action_arg = change->action_arg;
+ int i;
+
+ if (!change->has_action)
+ return;
+
+ /* ---------- determine action paramater values ---------- */
+
+ int action_arg_element =
+ (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player :
+ action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_element :
+ action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element :
+ EL_EMPTY);
+
+ int action_arg_number =
+ (action_arg <= CA_ARG_MAX ? action_arg :
+ action_arg == CA_ARG_NUMBER_MIN ? CA_ARG_MIN :
+ action_arg == CA_ARG_NUMBER_MAX ? CA_ARG_MAX :
+ action_arg == CA_ARG_NUMBER_CE_SCORE ? ei->collect_score :
+ action_arg == CA_ARG_NUMBER_CE_COUNT ? ei->collect_count :
+ action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CHANGE_DELAY(change) :
+ -1);
+
+ /* (for explicit player choice, set invalid value to "no player") */
+ int action_arg_player_bits =
+ (action_arg == CA_ARG_PLAYER_ANY ? action_arg - CA_ARG_PLAYER :
+ action_arg >= CA_ARG_PLAYER_1 &&
+ action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER :
+ action_arg >= CA_ARG_1 &&
+ action_arg <= CA_ARG_PLAYER_4 ? (1 << (action_arg - 1)) :
+ action_arg_element >= EL_PLAYER_1 &&
+ action_arg_element <= EL_PLAYER_4 ?
+ (1 << (action_arg_element - EL_PLAYER_1)) :
+ 0);
+
+ /* (for implicit player choice, set invalid value to "all players") */
+ int trigger_player_bits =
+ (change->actual_trigger_player >= EL_PLAYER_1 &&
+ change->actual_trigger_player <= EL_PLAYER_4 ?
+ (1 << (change->actual_trigger_player - EL_PLAYER_1)) :
+ PLAYER_BITS_ANY);
+
+ /* ---------- execute action ---------- */
+
+ switch(action_type)
+ {
+ case CA_NO_ACTION:
+ {
+ return;
+ }
+
+ case CA_EXIT_PLAYER:
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (action_arg_player_bits & (1 << i))
+ stored_player[i].LevelSolved = stored_player[i].GameOver = TRUE;
+
+ break;
+ }
+
+ case CA_KILL_PLAYER:
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (action_arg_player_bits & (1 << i))
+ KillHero(&stored_player[i]);
+
+ break;
+ }
+
+ case CA_RESTART_LEVEL:
+ {
+ game.restart_level = TRUE;
+
+ break;
+ }
+
+ case CA_SHOW_ENVELOPE:
+ {
+ int element = getSpecialActionElement(action_arg_element,
+ action_arg_number, EL_ENVELOPE_1);
+
+ if (IS_ENVELOPE(element))
+ local_player->show_envelope = element;
+
+ break;
+ }
+
+ case CA_ADD_KEY:
+ {
+ int element = getSpecialActionElement(action_arg_element,
+ action_arg_number, EL_KEY_1);
+
+ if (IS_KEY(element))
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (trigger_player_bits & (1 << i))
+ {
+ stored_player[i].key[KEY_NR(element)] = TRUE;
+
+ DrawGameValue_Keys(stored_player[i].key);
+
+ redraw_mask |= REDRAW_DOOR_1;
+ }
+ }
+ }
+
+ break;
+ }
+
+ case CA_DEL_KEY:
+ {
+ int element = getSpecialActionElement(action_arg_element,
+ action_arg_number, EL_KEY_1);
+
+ if (IS_KEY(element))
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (trigger_player_bits & (1 << i))
+ {
+ stored_player[i].key[KEY_NR(element)] = FALSE;
+
+ DrawGameValue_Keys(stored_player[i].key);
+
+ redraw_mask |= REDRAW_DOOR_1;
+ }
+ }
+ }
+
+ break;
+ }
+
+ case CA_SET_PLAYER_SPEED:
+ {
+ for (i = 0; i < MAX_PLAYERS; i++)
+ {
+ if (trigger_player_bits & (1 << i))
+ {
+ if (action_arg == CA_ARG_NUMBER_RESET)
+ stored_player[i].move_delay_value = game.initial_move_delay_value;
+ else if (action_arg == CA_ARG_NUMBER_NORMAL)
+ stored_player[i].move_delay_value = MOVE_DELAY_NORMAL_SPEED;
+ else if (action_arg == CA_ARG_NUMBER_MIN)
+ stored_player[i].move_delay_value = 16;
+ else if (action_arg == CA_ARG_NUMBER_MAX)
+ stored_player[i].move_delay_value = MOVE_DELAY_HIGH_SPEED;
+ else
+ {
+#if 0
+ if (action_mode == CA_MODE_ADD)
+ {
+ action_mode = CA_MODE_DIVIDE;
+ action_arg_number = (1 << action_arg_number);
+ }
+ else if (action_mode == CA_MODE_SUBTRACT)
+ {
+ action_mode = CA_MODE_MULTIPLY;
+ action_arg_number = (1 << action_arg_number);
+ }
+
+ int mode = (action_mode == CA_MODE_MULTIPLY ? CA_MODE_DIVIDE :
+ action_mode == CA_MODE_DIVIDE ? CA_MODE_MULTIPLY :
+ action_mode);
+
+ stored_player[i].move_delay_value =
+ getModifiedActionNumber(stored_player[i].move_delay_value,
+ 1, 16,
+ action_mode, action_arg_number);
+#endif
+ }
+ }
+ }
+
+ break;
+ }
+
+ case CA_SET_GEMS:
+ {
+ local_player->gems_still_needed =
+ getModifiedActionNumber(local_player->gems_still_needed, 0, 999,
+ action_mode, action_arg_number);
+
+ DrawGameValue_Emeralds(local_player->gems_still_needed);
+
+ break;
+ }
+
+ case CA_SET_TIME:
+ {
+ if (level.time > 0) /* only modify limited time value */
+ {
+ TimeLeft = getModifiedActionNumber(TimeLeft, 0, 9999,
+ action_mode, action_arg_number);
+
+ DrawGameValue_Time(TimeLeft);
+ }
+
+ break;
+ }
+
+ case CA_SET_SCORE:
+ {
+ local_player->score =
+ getModifiedActionNumber(local_player->score, 0, 99999,
+ action_mode, action_arg_number);
+
+ DrawGameValue_Score(local_player->score);
+
+ break;
+ }
+
+ case CA_SET_CE_SCORE:
+ {
+ printf("::: CA_SET_CE_SCORE -- not yet implemented\n");
+
+ break;
+ }
+
+ case CA_SET_CE_COUNT:
+ {
+ printf("::: CA_SET_CE_COUNT -- not yet implemented\n");
+
+ break;
+ }
+
+ case CA_SET_DYNABOMB_NUMBER:
+ {
+ printf("::: CA_SET_DYNABOMB_NUMBER -- not yet implemented\n");
+
+ break;
+ }
+
+ case CA_SET_DYNABOMB_SIZE:
+ {
+ printf("::: CA_SET_DYNABOMB_SIZE -- not yet implemented\n");
+
+ break;
+ }
+
+ case CA_SET_DYNABOMB_POWER:
+ {
+ printf("::: CA_SET_DYNABOMB_POWER -- not yet implemented\n");
+
+ break;
+ }
+
+ case CA_TOGGLE_PLAYER_GRAVITY:
+ {
+ game.gravity = !game.gravity;
+
+ break;
+ }
+
+ case CA_ENABLE_PLAYER_GRAVITY:
+ {
+ game.gravity = TRUE;
+
+ break;
+ }
+
+ case CA_DISABLE_PLAYER_GRAVITY:
+ {
+ game.gravity = FALSE;
+
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void ChangeElementNowExt(struct ElementChangeInfo *change,
+ int x, int y, int target_element)
+{
+ int previous_move_direction = MovDir[x][y];
+ boolean add_player = (ELEM_IS_PLAYER(target_element) &&
+ IS_WALKABLE(Feld[x][y]));
+
+ /* check if element under 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(target_element))
+ {
+ Bang(x, y);
+ return;
+ }
+
+ if (!add_player)
+ {
+ if (IS_MOVING(x, y) || IS_BLOCKED(x, y))
+ RemoveMovingField(x, y);
+ else
+ RemoveField(x, y);
+
+ Feld[x][y] = target_element;
+
+ ResetGfxAnimation(x, y);
+ ResetRandomAnimationValue(x, y);
+
+ if (element_info[Feld[x][y]].move_direction_initial == MV_START_PREVIOUS)
+ MovDir[x][y] = previous_move_direction;
+
+ InitField_WithBug1(x, y, FALSE);
+
+ DrawLevelField(x, y);
+
+ if (GFX_CRUMBLED(Feld[x][y]))
+ DrawLevelFieldCrumbledSandNeighbours(x, y);
+ }
+
+ /* "Changed[][]" not set yet to allow "entered by player" change one time */
+ if (ELEM_IS_PLAYER(target_element))
+ RelocatePlayer(x, y, target_element);
+
+#if 1
+ Changed[x][y] = TRUE; /* ignore all further changes in this frame */
+#else
+ Changed[x][y] |= ChangeEvent[x][y]; /* ignore same changes in this frame */
+#endif
+
+ TestIfBadThingTouchesHero(x, y);
+ TestIfPlayerTouchesCustomElement(x, y);
+ TestIfElementTouchesCustomElement(x, y);
+}
+
+static boolean ChangeElementNow(int x, int y, int element, int page)
+{
+ struct ElementChangeInfo *change = &element_info[element].change_page[page];
+ int target_element;
+ int old_element = Feld[x][y];
+
+ /* always use default change event to prevent running into a loop */
+ if (ChangeEvent[x][y] == -1)
+ ChangeEvent[x][y] = CE_DELAY;
+
+ if (ChangeEvent[x][y] == CE_DELAY)
+ {
+ /* reset actual trigger element, trigger player and action element */
+ change->actual_trigger_element = EL_EMPTY;
+ change->actual_trigger_player = EL_PLAYER_1;
+ }
+
+#if 1
+ /* do not change any elements that have already changed in this frame */
+ if (Changed[x][y])
+ return FALSE;
+#else
+ /* do not change already changed elements with same change event */
+ if (Changed[x][y] & ChangeEvent[x][y])
+ return FALSE;
+#endif
+
+#if 1
+ Changed[x][y] = TRUE; /* ignore all further changes in this frame */
+#else
+ Changed[x][y] |= ChangeEvent[x][y]; /* ignore same changes in this frame */
+#endif
+
+ if (change->explode)
+ {
+ Bang(x, y);
+
+ return TRUE;
+ }
+
+ if (change->use_target_content)
+ {
+ boolean complete_replace = TRUE;
+ boolean can_replace[3][3];
+ int xx, yy;
+
+ for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+ {
+ boolean is_empty;
+ boolean is_walkable;
+ boolean is_diggable;
+ boolean is_collectible;
+ boolean is_removable;
+ boolean is_destructible;
+ int ex = x + xx - 1;
+ int ey = y + yy - 1;
+ int content_element = change->target_content[xx][yy];
+ int e;
+
+ can_replace[xx][yy] = TRUE;
+
+ if (ex == x && ey == y) /* do not check changing element itself */
+ continue;
+
+ if (content_element == EL_EMPTY_SPACE)
+ {
+ can_replace[xx][yy] = FALSE; /* do not replace border with space */
+
+ continue;
+ }
+
+ if (!IN_LEV_FIELD(ex, ey))
+ {
+ can_replace[xx][yy] = FALSE;
+ complete_replace = FALSE;
+
+ continue;
+ }
+
+ e = Feld[ex][ey];
+
+ if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+ e = MovingOrBlocked2Element(ex, ey);
+
+ is_empty = (IS_FREE(ex, ey) ||
+ (IS_FREE_OR_PLAYER(ex, ey) && IS_WALKABLE(content_element)));
+
+ is_walkable = (is_empty || IS_WALKABLE(e));
+ is_diggable = (is_empty || IS_DIGGABLE(e));
+ is_collectible = (is_empty || IS_COLLECTIBLE(e));
+ is_destructible = (is_empty || !IS_INDESTRUCTIBLE(e));
+ is_removable = (is_diggable || is_collectible);
+
+ can_replace[xx][yy] =
+ (((change->replace_when == CP_WHEN_EMPTY && is_empty) ||
+ (change->replace_when == CP_WHEN_WALKABLE && is_walkable) ||
+ (change->replace_when == CP_WHEN_DIGGABLE && is_diggable) ||
+ (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) ||
+ (change->replace_when == CP_WHEN_REMOVABLE && is_removable) ||
+ (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
+ !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
+
+ if (!can_replace[xx][yy])
+ complete_replace = FALSE;
+ }
+
+ if (!change->only_if_complete || complete_replace)
+ {
+ boolean something_has_changed = FALSE;
+
+ if (change->only_if_complete && change->use_random_replace &&
+ RND(100) < change->random_percentage)
+ return FALSE;
+
+ for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3 ; xx++)
+ {
+ int ex = x + xx - 1;
+ int ey = y + yy - 1;
+ int content_element;
+
+ if (can_replace[xx][yy] && (!change->use_random_replace ||
+ RND(100) < change->random_percentage))
+ {
+ if (IS_MOVING(ex, ey) || IS_BLOCKED(ex, ey))
+ RemoveMovingField(ex, ey);
+
+ ChangeEvent[ex][ey] = ChangeEvent[x][y];
+
+ content_element = change->target_content[xx][yy];
+ target_element = GET_TARGET_ELEMENT(content_element, change);
+
+ ChangeElementNowExt(change, ex, ey, target_element);
+
+ something_has_changed = TRUE;
+
+ /* for symmetry reasons, freeze newly created border elements */
+ if (ex != x || ey != y)
+ Stop[ex][ey] = TRUE; /* no more moving in this frame */
+ }
+ }
+
+ if (something_has_changed)
+ PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+ }
+ }
+ else
+ {
+ target_element = GET_TARGET_ELEMENT(change->target_element, change);
+
+ ChangeElementNowExt(change, x, y, target_element);
+
+ PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
+ }
+
+ /* this uses direct change before indirect change */
+ CheckTriggeredElementChangeByPage(x, y, old_element, CE_CHANGE_OF_X, page);
+
+ return TRUE;
+}
+
+static void ChangeElement(int x, int y, int page)
+{
+ int element = MovingOrBlocked2Element(x, y);
+ struct ElementInfo *ei = &element_info[element];
+ struct ElementChangeInfo *change = &ei->change_page[page];
+
+#ifdef DEBUG
+ if (!CAN_CHANGE(element) && !CAN_CHANGE(Back[x][y]))
+ {
+ printf("\n\n");
+ printf("ChangeElement(): %d,%d: element = %d ('%s')\n",
+ x, y, element, element_info[element].token_name);
+ printf("ChangeElement(): This should never happen!\n");
+ printf("\n\n");
+ }
+#endif
+
+ /* this can happen with classic bombs on walkable, changing elements */
+ if (!CAN_CHANGE(element))
+ {
+#if 0
+ if (!CAN_CHANGE(Back[x][y])) /* prevent permanent repetition */
+ ChangeDelay[x][y] = 0;
+#endif
+
+ return;
+ }
+
+ if (ChangeDelay[x][y] == 0) /* initialize element change */
+ {
+ ChangeDelay[x][y] = GET_CHANGE_DELAY(change) + 1;
+
+ ResetGfxAnimation(x, y);
+ ResetRandomAnimationValue(x, y);
+
+ if (change->pre_change_function)
+ change->pre_change_function(x, y);
+ }
+
+ ChangeDelay[x][y]--;
+
+ if (ChangeDelay[x][y] != 0) /* continue element change */
+ {
+ int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]);
+
+ if (IS_ANIMATED(graphic))
+ DrawLevelGraphicAnimationIfNeeded(x, y, graphic);
+
+ if (change->change_function)
+ change->change_function(x, y);
+ }
+ else /* finish element change */
+ {
+ if (ChangePage[x][y] != -1) /* remember page from delayed change */
+ {
+ page = ChangePage[x][y];
+ ChangePage[x][y] = -1;
+
+ change = &ei->change_page[page];
+ }
+
+ if (IS_MOVING(x, y)) /* never change a running system ;-) */
+ {
+ ChangeDelay[x][y] = 1; /* try change after next move step */
+ ChangePage[x][y] = page; /* remember page to use for change */
+
+ return;
+ }
+
+ if (ChangeElementNow(x, y, element, page))
+ {
+ if (change->post_change_function)
+ change->post_change_function(x, y);
+ }
+ }
+}
+
+static boolean CheckTriggeredElementChangeExt(int lx, int ly,
+ int trigger_element,
+ int trigger_event,
+ int trigger_player,
+ int trigger_side,
+ int trigger_page)
+{
+ int i, j, x, y;
+ int trigger_page_bits = (trigger_page < 0 ? CH_PAGE_ANY : 1 << trigger_page);
+
+ if (!(trigger_events[trigger_element][trigger_event]))
+ return FALSE;
+
+ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+ {
+ int element = EL_CUSTOM_START + i;
+ boolean change_found = FALSE;
+
+ if (!CAN_CHANGE_OR_HAS_ACTION(element) ||
+ !HAS_ANY_CHANGE_EVENT(element, trigger_event))
+ continue;
+
+ for (j = 0; j < element_info[element].num_change_pages; j++)
+ {
+ struct ElementChangeInfo *change = &element_info[element].change_page[j];
+
+ if (change->can_change_or_has_action &&
+ change->has_event[trigger_event] &&
+ change->trigger_side & trigger_side &&
+ change->trigger_player & trigger_player &&
+ change->trigger_page & trigger_page_bits &&
+ IS_EQUAL_OR_IN_GROUP(trigger_element, change->trigger_element))
+ {
+ change->actual_trigger_element = trigger_element;
+ change->actual_trigger_player = EL_PLAYER_1 + log_2(trigger_player);
+
+ if (change->can_change && !change_found)
+ {
+ change_found = TRUE;
+
+ for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++)
+ {
+ if (Feld[x][y] == element)
+ {
+ ChangeDelay[x][y] = 1;
+ ChangeEvent[x][y] = trigger_event;
+ ChangeElement(x, y, j);
+ }
+ }
+ }
+
+ if (change->has_action)
+ ExecuteCustomElementAction(element, j);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+static boolean CheckElementChangeExt(int x, int y,
+ int element,
+ int trigger_element,
+ int trigger_event,
+ int trigger_player,
+ int trigger_side,
+ int trigger_page)
+{
+ if (!CAN_CHANGE(element) || !HAS_ANY_CHANGE_EVENT(element, trigger_event))
+ return FALSE;
+
+ if (Feld[x][y] == EL_BLOCKED)
+ {
+ Blocked2Moving(x, y, &x, &y);
+ element = Feld[x][y];
+ }
+
+ if (Feld[x][y] != element) /* check if element has already changed */
+ return FALSE;