L5-SHADOW: тень уровня 5 крадёт зелье (спецсобытие комнаты 24)
Порт seg002: check_shadow (0064), do_init_shad (0000), do_auto_moves (1089),
autocontrol_shadow_level5 (1157) + таблица shad_drink_move (data.h:866).
Механика. При входе в комнату 24 уровня 5, если зелье (кол 3, ряд 0) ещё
на месте, слот соперника занимает ТЕНЬ — и занимает его ВМЕСТО стража:
enter_guard в этой комнате оригинал не зовёт НИКОГДА (в данных страж есть,
guards_tile[23] = 8, но check_shadow уходит в return до него — в том числе
когда зелье уже выпито). Поэтому pop_check_shadow вернула 1 = «событие
обработано, стража не поднимать», и вызов стоит ПЕРЕД pop_guard_enter.
Тень стоит, пока не откроются ворота комнаты (openness >= 80), затем
проигрывает ЗАПИСЬ ходов: подойти, взять, выпить, развернуться, уйти за
левый край. do_auto_moves — тот же движок, что у демо-режима заставки;
тонкость выбора записи (при достигнутом времени индекс сдвигается, а ход
берётся из ещё не сдвинутой записи) портирована дословно.
Три вещи, найденные по дороге:
1. ГЕЙТ ВЫКЛЮЧЕННОГО ПЕРСОНАЖА. play_guard_frame (seg000:1248) начинается
с `if (Guard.direction != dir_56_none)`, и clear_char выключает
персонажа именно так — он не трогает charid. У нас гейт был только по
charid, поэтому ушедшая за край тень продолжала тикать: do_auto_moves за
концом таблицы отдаёт последнюю запись {0x31,1} = «вперёд», тень
разворачивалась, вбегала обратно, пробегала верхний ряд и падала в проём
(наблюдение пользователя в MAME). Гейт добавлен и в pop_guard_tick, и в
pop_guard_phys_tick.
2. ЭФФЕКТ ЗЕЛЬЯ — ТОЛЬКО КИДУ. proc_get_object (seg006:16CB) начинается с
`if (Char.charid != charid_0_kid || ...) return`: предмет забирает любой
персонаж, а эффект достаётся только Киду. На этом стоит всё спецсобытие
— тень пьёт, зелье исчезает и не достаётся никому. Без проверки
hitp_delta уходил бы Киду, то есть тень его ЛЕЧИЛА бы.
3. ВЕТКА ТЕНИ в check_guard_fallout (seg002:0241): исчезает, только если
реально падает (action == 4). Была помечена в коде как «появится с
L3-SKEL» — на самом деле она про тень.
Чит ROOMNAV: телепорт ставил Кида в первый попавшийся пол, то есть почти
всегда (0,0). Теперь ищет пол С МАКСИМАЛЬНЫМ Y (ряд 2 -> 1 -> 0, по
просьбе пользователя): в комнате 24 старая посадка была вплотную к тени, и
вместо сцены кражи начиналась схватка, которой там быть не должно.
Отладочный тумблер POP_DBG_SHADOW_NOWAIT (pop_tune.h, по умолчанию 0) —
посмотреть сцену, не проходя уровень до кнопки.
Проверено в MAME (уровень 5, комната 24): при закрытых воротах тень стоит
за левым краем и не видна; с тумблером — подошла, выпила (зелье исчезло),
развернулась, ушла за край, выключилась и НЕ вернулась. tests-host 5/5,
make size-check OK. Цена: резидент +102 Б (24637 -> 24739), банк 1
+368 Б, банк 3 +11 Б.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
- **[L4-MIRROR](TASKS_CLOSED.md#l4-mirror) закрыта**: зеркало, отражение,
|
||||
прыжок сквозь него и рождение тени проверены в MAME. Хвосты — вид тени
|
||||
(отложен, [`../docs/shadow_render.md`](../docs/shadow_render.md)) и
|
||||
[MIRROR-FG-STALE](BUGS_OPEN.md#mirror-fg-stale);
|
||||
[MIRROR-FG-STALE](BUGS_CLOSED.md#mirror-fg-stale) (закрыт как не баг);
|
||||
- **[L3-CHOMP](TASKS_CLOSED.md#l3-chomp) и [L3-SKEL](TASKS_CLOSED.md#l3-skel)
|
||||
закрыты** (2026-08-08 / 2026-08-07);
|
||||
- **тайлсет palace сделан** — `pop_bg_load(type)`, `pal_*.atl`, дворцовая
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "pop_level.h" /* pop_current_level — спецсобытие уровня 3 */
|
||||
#include "pop_state.h" /* pop_immortal, pop_droppedout */
|
||||
#include "pop_tile.h" /* POP_TILE_DIV — таблица колонок (tile_div_tbl) */
|
||||
#include "pop_tune.h" /* POP_DBG_SHADOW_NOWAIT — отладка сцены тени */
|
||||
|
||||
#define TILE_MIDX 7 /* середина тайла в координатах персонажа */
|
||||
#define SCREENSPACE_X 58 /* = pop_x_bump[FIRST_ONSCREEN_COLUMN] */
|
||||
@@ -188,6 +189,71 @@ void pop_check_skel(void) __banked
|
||||
is_guard_notice = guard_refrac = 0;
|
||||
}
|
||||
|
||||
/* ---- Тень: спецсобытие входа в комнату (check_shadow, seg002:0064) -- *
|
||||
*
|
||||
* На «шадоу»-уровнях слот соперника занимает ТЕНЬ, и занимает его ВМЕСТО
|
||||
* стража: оригинал вызывает check_shadow при смене комнаты и уходит в
|
||||
* return, не доходя до enter_guard. Важно, что на уровне 5 в комнате 24
|
||||
* страж в данных ЕСТЬ (`guards_tile[23] = 8`) — если позвать enter_guard,
|
||||
* он поднимется там, где его быть не должно. Поэтому вызывающий обязан
|
||||
* звать pop_check_shadow ВМЕСТО pop_guard_enter и уважать её ответ.
|
||||
*
|
||||
* do_init_shad (seg002:0000): первые 7 полей персонажа берутся из таблицы
|
||||
* init_shad_N, остальные наследуются от того, что уже лежит в окне Char. У
|
||||
* оригинала там КИД (check_shadow идёт сразу после loadkid(), seg000:1312),
|
||||
* поэтому наследование сделано явным — alive/sword берём у Кида, а не у
|
||||
* прошлого обитателя слота. Поднимаем в окне Char и saveshad'им, как
|
||||
* pop_check_skel и как сам do_init_shad. */
|
||||
/* Состояние проигрывателя автодвижений (seg002: demo_time/demo_index) —
|
||||
* объявлено здесь, потому что do_init_shad его сбрасывает, а движок
|
||||
* do_auto_moves живёт ниже, рядом с остальным ИИ. */
|
||||
static uint8_t demo_time; /* кадров с начала записи (0xFE = стоп) */
|
||||
static uint8_t demo_index; /* текущая запись */
|
||||
|
||||
#define SHADOW_SKILL 3
|
||||
#define SHADOW_HP 4
|
||||
#define SEQ_2_STAND 2
|
||||
#define TILE_POTION 10
|
||||
|
||||
/* init_shad_5 (data:0EF2): frame 15, x 55, y 55, direction 0 (вправо),
|
||||
* curr_col −1 (ещё за левым краем), curr_row 0, action 0 (стоит). */
|
||||
static void do_init_shad_5(uint8_t room)
|
||||
{
|
||||
pop_loadshad();
|
||||
Char.frame = 0x0F;
|
||||
Char.x = 0x37;
|
||||
Char.y = 0x37;
|
||||
Char.direction = 0;
|
||||
Char.curr_col = -1;
|
||||
Char.curr_row = 0;
|
||||
Char.action = 0;
|
||||
Char.room = room;
|
||||
Char.charid = CHARID_1_SHADOW;
|
||||
Char.alive = Kid.alive; /* наследование от Кида, см. шапку */
|
||||
Char.sword = Kid.sword;
|
||||
Char.fall_x = Char.fall_y = 0;
|
||||
pop_char_set_seq(SEQ_2_STAND);
|
||||
pop_saveshad();
|
||||
|
||||
demo_time = 0;
|
||||
demo_index = 0;
|
||||
guard_skill = SHADOW_SKILL;
|
||||
guardhp_delta = 0;
|
||||
guardhp_curr = guardhp_max = SHADOW_HP;
|
||||
is_guard_notice = guard_refrac = 0;
|
||||
}
|
||||
|
||||
uint8_t pop_check_shadow(uint8_t room) __banked
|
||||
{
|
||||
if (pop_current_level != SHADOW_STEAL_LEVEL) return 0;
|
||||
if (room != SHADOW_STEAL_ROOM) return 0;
|
||||
/* Зелья уже нет — тень своё дело сделала. Возвращаем 1 и здесь: стража
|
||||
* комнаты 24 оригинал не поднимает НИКОГДА (return до enter_guard). */
|
||||
if (pop_level_tile(room, SHADOW_POTION_TILEPOS) != TILE_POTION) return 1;
|
||||
do_init_shad_5(room);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- Синтетический ввод (seg002:0706..) --------------------------- */
|
||||
static void move_0_nothing(void)
|
||||
{
|
||||
@@ -219,6 +285,12 @@ static void move_6_shift(void)
|
||||
control_shift = POP_CONTROL_HELD;
|
||||
control_shift2 = POP_CONTROL_HELD;
|
||||
}
|
||||
/* move_7 (seg002:0770): ОТПУСТИТЬ shift — отдельный ход, потому что
|
||||
* move_0_nothing сбрасывает всё, а здесь нужно снять только его. */
|
||||
static void move_7(void)
|
||||
{
|
||||
control_shift = POP_CONTROL_RELEASED;
|
||||
}
|
||||
static void move_down_back(void)
|
||||
{
|
||||
control_down = POP_CONTROL_HELD;
|
||||
@@ -457,12 +529,78 @@ static void autocontrol_shadow_level4(void)
|
||||
else move_1_forward();
|
||||
}
|
||||
|
||||
/* autocontrol_shadow (seg002:081D) — по уровням. У нас пока только ур. 4;
|
||||
* ветки 5 (крадёт зелье), 6 (шагает по плите) и 12 появятся со своими
|
||||
* уровнями. */
|
||||
/* ---- Автодвижения: «демо-запись» вместо ИИ (seg002:1089) ----------- *
|
||||
*
|
||||
* Тень уровня 5 не принимает решений — она проигрывает ЗАПИСЬ нажатий:
|
||||
* пара {time, move}, где time — момент по счётчику кадров demo_time, а
|
||||
* move — номер синтетического хода (те же move_*, что заполняет
|
||||
* autocontrol_*). Оригинал использует этот же движок для демо-режима
|
||||
* заставки, поэтому имена сохранены.
|
||||
*
|
||||
* Тонкость выбора записи, которую легко «упростить» и сломать: когда время
|
||||
* текущей записи ДОСТИГНУТО, индекс сдвигается вперёд, но ход берётся всё
|
||||
* равно из ТЕКУЩЕЙ (ещё не сдвинутой) записи; пока не достигнуто — из
|
||||
* ПРЕДЫДУЩЕЙ. То есть запись задаёт «с какого кадра действует её ход». */
|
||||
typedef struct { uint8_t time; int8_t move; } auto_move_t;
|
||||
|
||||
/* shad_drink_move (data.h:866): подойти, взять зелье, выпить, уйти. */
|
||||
static const auto_move_t shad_drink_move[8] = {
|
||||
{0x00, 0}, {0x01, 1}, {0x0E, 0}, {0x12, 6},
|
||||
{0x1D, 7}, {0x2D, 2}, {0x31, 1}, {0xFF, -2}
|
||||
};
|
||||
|
||||
static void do_auto_moves(const auto_move_t *moves)
|
||||
{
|
||||
uint8_t idx;
|
||||
int8_t mv;
|
||||
|
||||
if (demo_time >= 0xFE) return;
|
||||
++demo_time;
|
||||
idx = demo_index;
|
||||
if (moves[idx].time <= demo_time) ++demo_index;
|
||||
else idx = (uint8_t)(demo_index - 1);
|
||||
|
||||
mv = moves[idx].move;
|
||||
switch (mv) {
|
||||
case 0: move_0_nothing(); break;
|
||||
case 1: move_1_forward(); break;
|
||||
case 2: move_2_backward(); break;
|
||||
case 3: move_3_up(); break;
|
||||
case 4: move_4_down(); break;
|
||||
case 5: move_3_up(); move_1_forward(); break;
|
||||
case 6: move_6_shift(); break;
|
||||
case 7: move_7(); break;
|
||||
default: break; /* −1 «ничего», −2 «конец» */
|
||||
}
|
||||
}
|
||||
|
||||
/* autocontrol_shadow_level5 (seg002:1157). Тень стоит, пока не откроются
|
||||
* ворота комнаты (openness >= 80), и только тогда запускает запись ходов:
|
||||
* идёт к зелью, выпивает и уходит за левый край, где растворяется.
|
||||
*
|
||||
* Почему проверка ворот — именно через модификатор, а не через «дверь
|
||||
* открыта»: `pop_leveldoor_open` тут ни при чём, речь о ВОРОТАХ (тайл 4) в
|
||||
* колонке 1 верхнего ряда, и оригинал читает их openness напрямую. */
|
||||
static void autocontrol_shadow_level5(void)
|
||||
{
|
||||
if (Char.room != SHADOW_STEAL_ROOM) return;
|
||||
if (demo_time == 0) {
|
||||
#if !POP_DBG_SHADOW_NOWAIT
|
||||
uint8_t *m = pop_trob_modif(SHADOW_STEAL_ROOM);
|
||||
if (m[SHADOW_DOOR_TILEPOS] < SHADOW_DOOR_OPEN_MIN) return;
|
||||
#endif
|
||||
demo_index = 0;
|
||||
}
|
||||
do_auto_moves(shad_drink_move);
|
||||
if (Char.x < 15) clear_char();
|
||||
}
|
||||
|
||||
/* autocontrol_shadow (seg002:081D) — по уровням. Ветки 6 (шагает по плите)
|
||||
* и 12 появятся со своими уровнями. */
|
||||
static void autocontrol_shadow(void)
|
||||
{
|
||||
if (pop_current_level == MIRROR_LEVEL) autocontrol_shadow_level4();
|
||||
if (pop_current_level == MIRROR_LEVEL) autocontrol_shadow_level4();
|
||||
if (pop_current_level == SHADOW_STEAL_LEVEL) autocontrol_shadow_level5();
|
||||
}
|
||||
|
||||
/* autocontrol_guard (seg002:701) + autocontrol_opponent (seg002:628). */
|
||||
@@ -613,7 +751,17 @@ static void play_guard(void)
|
||||
* отдельным шагом. */
|
||||
void pop_guard_tick(void) __banked
|
||||
{
|
||||
if (Guard.charid == 0) return; /* стража в комнате нет */
|
||||
/* Гейт play_guard_frame (seg000:1248) — `if (Guard.direction !=
|
||||
* dir_56_none)`. Именно им ОТКЛЮЧАЕТСЯ персонаж: clear_char (seg006)
|
||||
* не трогает charid, он ставит direction = dir_56_none, и вся логика
|
||||
* соперника перестаёт вызываться.
|
||||
*
|
||||
* Проверять только charid НЕЛЬЗЯ (кусало 2026-08-11 на тени уровня 5):
|
||||
* тень уходила за левый край, clear_char её «выключал», но тик
|
||||
* продолжал крутиться — do_auto_moves за концом таблицы отдаёт
|
||||
* последнюю запись `{0x31, 1}` («вперёд»), и тень разворачивалась,
|
||||
* вбегала обратно, пробегала верхний ряд и падала в проём. */
|
||||
if (Guard.charid == 0 || Guard.direction == DIR_56_NONE) return;
|
||||
pop_loadshad_and_opp(); /* Char = Guard, Opp = Kid */
|
||||
/* Симметрично Киду (play_guard_frame, seg000:1248): cur_frame один на
|
||||
* всех, и до этой строки в нём лежит кадр КИДА — он тикал раньше. */
|
||||
|
||||
@@ -115,9 +115,11 @@ void pop_do_delta_hp(void)
|
||||
* стража там больше нет и после возврата. У нас тот же смысл — запомнить
|
||||
* состояние с tile = 30 («стража в комнате больше нет»).
|
||||
*
|
||||
* Ветки тени (падает, только если она в свободном полёте) и скелета (тот
|
||||
* возрождается в комнате 3) появятся вместе с L3-SKEL — сейчас в слоте
|
||||
* бывает только обычный страж.
|
||||
* Ветка ТЕНИ (seg002:0241): она исчезает, только если РЕАЛЬНО падает
|
||||
* (`action == actions_4_in_freefall`) — иначе тень, зашедшую низко по
|
||||
* своим делам (уровень 5 идёт по нижнему ряду за зельем), убрало бы с
|
||||
* экрана на ровном месте. Ветка скелета — ниже, он не гибнет, а
|
||||
* возрождается.
|
||||
*
|
||||
* Полосу HP перерисовывать отдельно не надо: pop_hp_draw сравнивает числа
|
||||
* и сам увидит, что стража не стало. */
|
||||
@@ -128,9 +130,23 @@ void pop_do_delta_hp(void)
|
||||
#define SKEL_REAPPEAR_X 133
|
||||
#define SKEL_REAPPEAR_ROW 1
|
||||
|
||||
/* actions_4_in_freefall (seg005) — тот же номер, что ACT_FREEFALL в
|
||||
* pop_map.c; здесь свой define, потому что тот приватный для карты. */
|
||||
#define ACT_4_IN_FREEFALL 4
|
||||
|
||||
void pop_guard_fallout(void)
|
||||
{
|
||||
if (Guard.charid == 0 || Guard.y < 211) return;
|
||||
if (Guard.charid == CHARID_1_SHADOW) {
|
||||
/* Тень исчезает только в свободном падении; иначе — не наше дело. */
|
||||
if (Guard.action != ACT_4_IN_FREEFALL) return;
|
||||
Guard.direction = DIR_56_NONE;
|
||||
Guard.alive = 0;
|
||||
Guard.action = 0;
|
||||
Guard.charid = 0;
|
||||
guardhp_curr = 0;
|
||||
return;
|
||||
}
|
||||
if (Guard.charid == CHARID_4_SKELETON) {
|
||||
uint8_t down = pop_room_link(Guard.room, 3 /* вниз */);
|
||||
if (down == SKEL_REAPPEAR_ROOM) {
|
||||
|
||||
@@ -83,6 +83,17 @@
|
||||
#define MIRROR_ROW 0
|
||||
#define MIRROR_TILEPOS (MIRROR_ROW * 10 + MIRROR_COL)
|
||||
|
||||
/* Спецсобытие «тень крадёт зелье» (custom->shadow_steal_*, дефолты SDLPoP):
|
||||
* уровень 5, комната 24. Тень появляется при ВХОДЕ в комнату, если зелье
|
||||
* (колонка 3, верхний ряд) ещё на месте; ждёт, пока откроются ворота
|
||||
* (колонка 1, верхний ряд), идёт к зелью, выпивает его и уходит за левый
|
||||
* край. Боя нет. Как и MIRROR_*, константы нужны в двух банках. */
|
||||
#define SHADOW_STEAL_LEVEL 5
|
||||
#define SHADOW_STEAL_ROOM 24
|
||||
#define SHADOW_POTION_TILEPOS 3 /* (кол 3, ряд 0) — зелье */
|
||||
#define SHADOW_DOOR_TILEPOS 1 /* (кол 1, ряд 0) — ворота */
|
||||
#define SHADOW_DOOR_OPEN_MIN 80 /* openness, при которой тень идёт */
|
||||
|
||||
/* Градаций мастерства стража (seg002: таблицы вероятностей по 12 записей). */
|
||||
#define NUM_GUARD_SKILLS 12
|
||||
|
||||
@@ -148,6 +159,18 @@ void pop_check_can_guard_see_kid(void) __banked;
|
||||
* в кадр, ДО логики персонажей (порядок play_frame, seg000:868). */
|
||||
void pop_check_skel(void) __banked;
|
||||
|
||||
/* check_shadow (seg002:0064) — СПЕЦСОБЫТИЕ входа в комнату: на «шадоу»-
|
||||
* уровнях слот соперника занимает ТЕНЬ, а не страж из данных. Звать ВМЕСТО
|
||||
* pop_guard_enter при входе в комнату; вернула 1 — событие обработано и
|
||||
* стража поднимать НЕ надо.
|
||||
*
|
||||
* Тонкость оригинала, которую легко потерять: на уровне 5 в комнате 24
|
||||
* страж В ДАННЫХ ЕСТЬ (`guards_tile[23] = 8`), но enter_guard там не
|
||||
* вызывается НИКОГДА — ни когда тень поднимается, ни когда зелье уже
|
||||
* выпито (в обоих случаях seg002:0064 делает return). Поэтому 1
|
||||
* возвращается и во втором случае тоже. */
|
||||
uint8_t pop_check_shadow(uint8_t room) __banked;
|
||||
|
||||
/* Боёвка: пометить попадания (обе стороны) и применить их. Порядок в
|
||||
* кадре как в play_frame: hurting -> hurt -> do_delta_hp. */
|
||||
void pop_check_sword_hurting(void) __banked;
|
||||
|
||||
@@ -1147,9 +1147,17 @@ static void do_pickup(int8_t obj_type, uint8_t tilepos)
|
||||
}
|
||||
|
||||
/* proc_get_object (seg006:16CB): эффект поднятого предмета. Зовётся из
|
||||
* play_seq по опкоду SEQ_GET_ITEM с аргументом 1 (pop_kid). */
|
||||
* play_seq по опкоду SEQ_GET_ITEM с аргументом 1 (pop_kid).
|
||||
*
|
||||
* ПЕРВАЯ СТРОКА ОРИГИНАЛА — `if (Char.charid != charid_0_kid || ...) return`:
|
||||
* предмет ЗАБИРАЕТ любой персонаж (тайл стирает do_pickup), а ЭФФЕКТ
|
||||
* достаётся только Киду. На этом стоит всё спецсобытие уровня 5: тень
|
||||
* выпивает зелье, оно исчезает — и не достаётся никому. Без проверки
|
||||
* hitp_delta уходил бы Киду, то есть тень его лечила бы (найдено при
|
||||
* портировании L5-SHADOW 2026-08-11, до живого прогона). */
|
||||
void pop_proc_get_object(void) __banked
|
||||
{
|
||||
if (Char.charid != CHARID_0_KID) return;
|
||||
if (pickup_obj_type == -1) {
|
||||
pop_have_sword = 1; /* в оригинале have_sword = -1 */
|
||||
/* Вспышка фона: flash_color = color_14_brightyellow, flash_time = 8
|
||||
@@ -2546,7 +2554,10 @@ void pop_phys_tick(void) __banked
|
||||
* Char стоит копию 16 байт. */
|
||||
void pop_guard_phys_tick(void) __banked
|
||||
{
|
||||
if (Guard.charid == 0 || Guard.room != g_room) return;
|
||||
/* Тот же гейт, что в pop_guard_tick: dir_56_none = персонаж выключен
|
||||
* (clear_char), физику ему тоже не крутим. */
|
||||
if (Guard.charid == 0 || Guard.direction == DIR_56_NONE ||
|
||||
Guard.room != g_room) return;
|
||||
pop_loadshad_and_opp();
|
||||
/* seg000:1254 — физика работает, ТОЛЬКО пока страж внутри комнаты: за
|
||||
* пределами полосы персонаж просто перестаёт двигаться, а
|
||||
|
||||
@@ -69,4 +69,14 @@
|
||||
* уровнях моды любят делать чомперы быстрее. Меньше — злее. */
|
||||
#define POP_CHOMPER_SPEED 15
|
||||
|
||||
/* ---- Отладка сцены «тень крадёт зелье» (уровень 5) ------------------ *
|
||||
* Тень появляется в комнате 24 сразу при входе, но СТОИТ, пока не откроют
|
||||
* ворота (кол 1, ряд 0, openness >= 80) — так в оригинале
|
||||
* (autocontrol_shadow_level5, seg002:1157). Чтобы посмотреть саму сцену
|
||||
* (подход, питьё, уход), не проходя уровень до кнопки, поставь 1: тень
|
||||
* стартует запись ходов сразу. ОБЯЗАН быть 0 в игровой сборке. */
|
||||
#ifndef POP_DBG_SHADOW_NOWAIT
|
||||
#define POP_DBG_SHADOW_NOWAIT 0
|
||||
#endif
|
||||
|
||||
#endif /* POP_TUNE_H */
|
||||
|
||||
@@ -329,7 +329,12 @@ static void enter_room_side(uint8_t room, uint8_t side)
|
||||
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
|
||||
pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
|
||||
pop_start_chompers(room, Kid.curr_row); /* seg000:1288: чомперы РЯДА Кида */
|
||||
if (!follow)
|
||||
/* check_shadow (seg002:0064) стоит ПЕРЕД enter_guard и на «шадоу»-
|
||||
* уровнях забирает слот соперника себе: вернула 1 — стража поднимать
|
||||
* НЕЛЬЗЯ (в комнате 24 уровня 5 он есть в данных, но оригинал его туда
|
||||
* не пускает). Порядок ровно как в оригинале, не «сначала страж, потом
|
||||
* заменим». */
|
||||
if (!follow && !pop_check_shadow(room))
|
||||
pop_guard_enter(room); /* страж комнаты (порт enter_guard) */
|
||||
pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */
|
||||
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
|
||||
@@ -651,13 +656,31 @@ int main(void)
|
||||
r = (nav & 1) ? (uint8_t)(r == 24 ? 1 : r + 1)
|
||||
: (uint8_t)(r == 1 ? 24 : r - 1);
|
||||
enter_room(r);
|
||||
/* Предпочитаем обычный пол; иначе — любой tile_is_floor
|
||||
* (seg001): всё, кроме пустоты/стены/верхов колонны и двери. */
|
||||
for (i = 0; i < 30; i++) {
|
||||
c = (uint8_t)(room_fg[i] & 0x1F);
|
||||
if (c == 1) { any = i; break; }
|
||||
if (any == 0xFF && c && c != 9 && c != 12 && c != 20 &&
|
||||
!(c >= 26 && c <= 29)) any = i;
|
||||
/* Куда поставить Кида в новой комнате: пол С МАКСИМАЛЬНЫМ Y —
|
||||
* сначала НИЖНИЙ ряд (2), потом 1, и только потом 0
|
||||
* (пользователь 2026-08-11). Раньше брался первый пол
|
||||
* подряд, то есть почти всегда (0,0) — верхний левый угол:
|
||||
* это и «прилёт» в проём/ворота, и, на уровне 5, посадка
|
||||
* вплотную к тени спецсобытия, которая ждёт в колонке −1
|
||||
* ряда 0. Вместо сцены кражи зелья начиналась схватка,
|
||||
* которой в этой комнате быть не должно.
|
||||
*
|
||||
* В каждом ряду сначала ищем ОБЫЧНЫЙ пол, и только если его
|
||||
* там нет — любой проходимый tile_is_floor (seg001: всё,
|
||||
* кроме пустоты, верха колонны, верха двери, стены и
|
||||
* «половинок» 26..29). */
|
||||
{
|
||||
int8_t row;
|
||||
for (row = 2; row >= 0 && any == 0xFF; row--) {
|
||||
uint8_t fallback = 0xFF;
|
||||
for (i = (uint8_t)(row * 10); i < (uint8_t)(row * 10 + 10); i++) {
|
||||
c = (uint8_t)(room_fg[i] & 0x1F);
|
||||
if (c == 1) { any = i; break; }
|
||||
if (fallback == 0xFF && c && c != 9 && c != 12 &&
|
||||
c != 20 && !(c >= 26 && c <= 29)) fallback = i;
|
||||
}
|
||||
if (any == 0xFF) any = fallback;
|
||||
}
|
||||
}
|
||||
if (any != 0xFF) pos = any;
|
||||
kid_init(SEQ_STAND, (int8_t)(pos % 10), (int8_t)(pos / 10), 0);
|
||||
|
||||
Reference in New Issue
Block a user