L2: переход между уровнями и игра уровня 2

Порт levels_plan.md §2 (шаг 1) — машинерия смены уровня целиком.

- Номер уровня стал состоянием: pop_current_level (порт current_level),
  pop_next_level больше не флаг, а НОМЕР; главный цикл срабатывает по
  расхождению next != current (порт play_level_2, seg003:0386).
- pop_level_load_num(n): LEVELS\res20NN.bin (fallback a:\), старая
  EMM-страница отпускается только после успешной загрузки новой.
  На диск кладутся все 15 уровней (34 КБ).
- Потабличные различия (data.h:840..848) — таблицы по 16 в pop_level.c:
  tbl_entry_pose, tbl_guard_hp (HP стража ушло из хардкода 3),
  tbl_guard_type (−1 = стражей на уровне нет), tbl_level_type.
- find_start_level_door (seg003:02E6): на уровне 2 стартовый тайл —
  правая половина двери уровня, без modif=43 + add_trob(...,3) Кид
  материализуется внутри глухой створки.  Тип 3 = дверь захлопывается
  за спиной, как в оригинале.
- HP через уровень: hitp_beg_lev (seg003) — рестарт уровня откатывает
  HP к нему, пройденный уровень подтягивает его к hitp_max.  Заодно
  большая склянка add_life (тип зелья 2: +1 к потолку HP до 10) —
  на уровне 2 она есть, комната 20.
- have_sword = level >= 2 (play_level, seg003:106).
- Чит Shift+L — следующий уровень (seg000:698).

Проверено в MAME: уровень 1 → Shift+L → уровень 2 (комната 5, новые для
нас тайлы 8/9 «большая колонна» на месте, дверь захлопнута, HP 3) →
влево в комнату 4 со стражем → Shift+L → уровень 3.  Вход в стартовую
дверь уровня 2 по Up не срабатывает — как и должно (start_room-гард).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Александр Петров
2026-08-04 11:35:56 +03:00
parent 7ef007757b
commit e36828ae6e
11 changed files with 307 additions and 34 deletions
+73 -25
View File
@@ -90,10 +90,7 @@ static void kbd_idle(void)
#define ROOM_1 1
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
/* Поза входа на уровне (tbl_entry_pose, data.h:848): у уровня 1 — падение
* внутрь плюс нажатие кнопки. Уровень пока один, поэтому константа; когда
* появятся уровни 2+ (levels_plan.md), это станет таблицей в pop_level. */
#define LEVEL_ENTRY_POSE ENTRY_POSE_FALL
#define FIRST_LEVEL 1 /* с какого уровня начинается игра */
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
@@ -337,16 +334,38 @@ static void enter_room(uint8_t room)
* это оно захлопывает решётку за спиной в первой комнате;
* 2 (уровень 13) — вбегание (seq_84_run);
* иначе — разворот на месте (seq_5_turn).
* Таблица индексируется НОМЕРОМ УРОВНЯ, поэтому появится в pop_level, когда
* дойдём до уровней 2+ (levels_plan.md); пока уровень один. */
* Таблица индексируется НОМЕРОМ УРОВНЯ — pop_level_entry_pose(). */
#define SEQ_TURN 5
#define SEQ_FALL 7
#define SEQ_RUN 84
#define ENTRY_POSE_FALL 1 /* tbl_entry_pose[1] */
#define ENTRY_POSE_RUN 2 /* tbl_entry_pose[13] */
#define TILE_LEVELDOOR_LEFT 16 /* tiles_16_level_door_left */
static void pop_start_level(uint8_t entry_pose)
/* find_start_level_door (seg003:02E6): дверь уровня В СТАРТОВОЙ КОМНАТЕ —
* это дверь, через которую Кид ВОШЁЛ, поэтому она открыта полностью
* (modif = 43) и заводится trob'ом типа 3 (start_level_door, seg007:0D72).
* Без этого на уровне 2 Кид материализуется внутри глухой закрытой створки:
* его стартовый тайл (комната 5, ряд 1, колонка 3) — правая половина
* той самой двери. */
static void find_start_level_door(uint8_t room)
{
uint8_t *m = pop_trob_modif(room);
uint8_t tp;
if (!m) return;
/* Батч-доступ к странице уровня тут не нужен: функция отрабатывает раз
* за уровень, а pop_add_trob сам лезет в W0. */
for (tp = 0; tp < 30; tp++) {
if (pop_level_tile(room, tp) != TILE_LEVELDOOR_LEFT) continue;
m[tp] = 43; /* створка поднята полностью */
pop_add_trob(room, tp, 3); /* ...и тут же поедет ВНИЗ (type>=3) */
}
}
static void pop_start_level(void)
{
uint8_t entry_pose = pop_level_entry_pose(pop_current_level);
uint8_t start_room = pop_level_start_room();
uint8_t pos = pop_level_start_pos();
int8_t dir = (int8_t)~pop_level_start_dir();
uint8_t seq = SEQ_TURN;
@@ -375,10 +394,17 @@ static void pop_start_level(uint8_t entry_pose)
seq = SEQ_RUN;
}
enter_room(pop_level_start_room());
/* Тоже ДО отрисовки комнаты: дверь, через которую вошли, обязана быть
* открыта уже в первом кадре (в оригинале find_start_level_door стоит
* между do_startpos и draw_level_first). */
find_start_level_door(start_room);
enter_room(start_room);
kid_init(seq, (int8_t)(pos % 10), (int8_t)(pos / 10), dir);
Kid.sword = 0; /* set_start_pos: sword_0_sheathed */
pop_have_sword = 0; /* start_level: have_sword с уровня 2 */
/* play_level (seg003:106): have_sword = level >= have_sword_from_level
* (=2) — начиная со второго уровня меч у Кида уже есть. */
pop_have_sword = (uint8_t)(pop_current_level >= 2);
pop_kid_hp_reset();
}
@@ -392,7 +418,7 @@ int main(void)
#ifdef ROOMNAV
uint8_t nav_prev = 0;
#endif
uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0;
uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0, nlvl_prev = 0;
if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */
puts("pop_bg_load failed");
@@ -410,11 +436,12 @@ int main(void)
puts("kid atlas load failed");
return 1;
}
if (pop_level_load("LEVELS\\res2001.bin") != 0 && /* сырой уровень 1 в EMM-страницу */
pop_level_load("a:\\res2001.bin") != 0) {
if (pop_level_load_num(FIRST_LEVEL) != 0) { /* сырой уровень в EMM-страницу */
puts("level load failed");
return 1;
}
pop_next_level = pop_current_level; /* current_level = next_level (load_lev_spr) */
pop_kid_hp_newgame(); /* init_game: hitp_beg_lev = start_hitp */
initgraph();
gfx_set_visible_page(0);
@@ -431,7 +458,7 @@ int main(void)
/* Старт — ПО ДАННЫМ УРОВНЯ (комната/тайл/направление/поза входа), см.
* pop_start_level. Управление: ←/→ бег, ↓ присед, ↑+→ прыжок с места,
* Shift шаг, ESC — выход. Карта коллизии задана внутри enter_room. */
pop_start_level(LEVEL_ENTRY_POSE);
pop_start_level();
gfx_set_draw_page(0);
kid_draw();
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
@@ -472,16 +499,27 @@ int main(void)
* I — бессмертие Кида (наш отладочный, в оригинале его нет:
* иначе боёвку проверять получается ровно три раза);
* S — выдать меч, не бегая за ним в комнату 15. Штатно меч
* берётся с пола (pop_have_sword ставит подбор). */
* берётся с пола (pop_have_sword ставит подбор);
* Shift+L — следующий уровень (seg000:698, ветка next_level =
* current_level + 1). Единственный вменяемый способ
* тестировать уровни 2..15. NB: L без Shift — отладочный
* «осторожный шаг вправо» (pop_ctrl KBD_DBG_STEPR); с
* Shift шаг тоже пройдёт, но уровень тут же сменится, так
* что конфликта на практике нет. */
uint8_t k = kbd_raw_down(KBD_CHEAT_KILL);
uint8_t i = kbd_raw_down(KBD_CHEAT_IMMO);
uint8_t s = kbd_raw_down(KBD_CHEAT_SWORD);
uint8_t nl = (uint8_t)(kbd_raw_down(KBD_CHEAT_NEXTLVL) &&
(kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT)));
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
if (pop_cheats && i && !immo_prev) pop_immortal = !pop_immortal;
if (pop_cheats && s && !sword_prev) pop_have_sword = 1;
if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL)
pop_next_level = (uint8_t)(pop_current_level + 1);
cheat_prev = k;
immo_prev = i;
sword_prev = s;
nlvl_prev = nl;
}
if (frozen) {
PROF(0); /* чёрный: ждём vsync */
@@ -545,7 +583,7 @@ int main(void)
enter_room(down); /* координаты Kid уже репроецированы pop_map */
gfx_set_draw_page(back); /* вернуть текущую back для kid_draw */
} else { /* выпал из уровня (нет комнаты снизу) — рестарт */
pop_start_level(LEVEL_ENTRY_POSE);
pop_start_level();
gfx_set_draw_page(back);
}
} else if (pop_leave_dir) { /* L3: уход вбок/вверх (координаты уже репроецированы) */
@@ -581,15 +619,25 @@ int main(void)
}
}
}
if (pop_next_level) { /* END_LEVEL: доиграл seq_70 в двери уровня */
/* Порт play_level_2 (seg003:0386): цикл уровня кончается, когда
* next_level разошёлся с current_level, и start_game грузит
* следующий. Уровень у нас пока один, поэтому «следующий» = тот
* же самый — зато вся цепочка (дверь → seq_70 → END_LEVEL →
* конец уровня) работает целиком и проверяема. Загрузка уровня
* 2 подключается ровно сюда, см. ../docs/levels_plan.md §2.2. */
pop_next_level = 0;
pop_start_level(LEVEL_ENTRY_POSE);
if (pop_next_level != pop_current_level) {
/* Порт play_level_2 (seg003:0386..0410) + play_level (seg003:81):
* цикл уровня кончается, когда next_level разошёлся с
* current_level (END_LEVEL после seq_70 в двери уровня либо чит
* Shift+L), и play_level грузит следующий.
*
* HP переносится: hitp_beg_lev = hitp_max ДО загрузки — так
* поднятая на уровне «жизнь» (большая склянка) переезжает
* дальше, а урон — нет (do_startpos дольёт до потолка). */
hitp_beg_lev = hitp_max;
if (pop_level_load_num(pop_next_level) != 0) {
pop_next_level = pop_current_level; /* файла нет — остаёмся */
} else {
/* Всё состояние прошлого уровня сносит сам pop_start_level:
* pop_trob_reset обнуляет room_seen (модификаторы комнат
* перечитаются из НОВОЙ страницы), тайлы/стражи приезжают
* из неё же, loose чистит enter_room. */
pop_start_level();
}
gfx_set_draw_page(back);
}
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
@@ -597,7 +645,7 @@ int main(void)
if (dead_frames > RESPAWN_SETTLE &&
(kbd_raw_down(KBD_UP) || dead_frames > RESPAWN_DELAY)) {
dead_frames = 0;
pop_start_level(LEVEL_ENTRY_POSE);
pop_start_level();
gfx_set_draw_page(back);
}
}