PoP: скелет уровня 3, цвета стражей, окклюзия соперника; кэш соседних комнат
Скелет (L3-SKEL, ассеты + механика): - pop_pack_guard.py получил параметр набора (GUARD/SKEL): атлас скелета poc/res/skel/g0..g3.atl (28 кадров), палитра — из его res750.pal (на ур. 3 curr_guard_color = 0, оригинал палитру не подменяет); - pop_guard_load выбирает набор по tbl_guard_type и перезагружается ПРИ СМЕНЕ УРОВНЯ (load_lev_spr, seg000:1092) — без этого скелет рисовался атласом стража и был невидим; - load_frame: charid_4_skeleton идёт по таблице стража (seg006:529), тень — только в кадрах 150..189. Пока ветка была одна (charid_2_guard), скелет получал image из таблицы Кида (180 при 28 спрайтах) и не рисовался; - check_skel (seg002:1042), ветка charid_4 в enter_guard, возрождение в комнате 3 при падении (seg002:252), autocontrol_skeleton; - leveldoor_open (seg007:456) — новый флаг, сбрасывается стартом уровня. Цвета стражей (BUG-GUARD-COLOR-1, закрыт): - все 7 палитр res10.bin -> pop_guard_pal.h, заливка 16 слотов по guards_color комнаты перед отрисовкой (set_chtab_palette, seg003:257). Проверено в MAME: ур. 2 комн. 11 = цвет 1, комн. 7 = цвет 3, полоса HP меняется вместе со стражем. Грабля: gfx_pal_load отдаёт указатель в BIOS, а тот читает только #4000-#BFFF — таблицу из банка копируем в стек. Кэш соседних комнат (BUG-SWORD-GHOST-1, закрыт): - pop_map кэширует fg соседей слева/справа ЦЕЛИКОМ и резолвит col -10..19. Было -2..11, дальше мнимая стена: луч видимости упирался в неё (страж после follow_guard в col 12), Кид прятал меч посреди боя и не мог достать обратно. +48 байт W2. Окклюзия соперника: - pop_fore_over_char получил проход other_overlay_tile (порядок midtable, seg008:1B06) и расширение перебора объединённым прямоугольником «персонаж + клинок + брызги» — падающий скелет больше не рисуется поверх кладки и верхней грани пола; - клип полем 192 строк (reset_obj_clip, seg006:0507) для спрайта, клинка (общий pop_sword_draw) и брызг — спрайт не залезает на полосу HP; - ROOMNAV после смерти Кида делает честный pop_start_level: телепорт «оживлял» мёртвого мимо старта уровня, оставляя живого скелета рядом с вернувшейся кучей костей. Ассеты чомпера (под L3-CHOMP): весь набор кадров в атласе явным списком (101-105 низ, 111-113 верх, 106-110 фронт, 114-123 кровь mono-силуэтом) — render_room анимированные тайлы пропускает, и в атласе не было ни одного. Число EMM-страниц не изменилось. Тесты: tests-host все 5 наборов зелёные, t_char вырос до 65 проверок (резолв колонок за краем комнаты, возрождение скелета); в testkit добавлен гард «код наехал на данные» (DATA_LOC). Доски: TASKS.md разнесён на TASKS_OPEN/TASKS_CLOSED, закрытые баги с разбором корней — в bug_closed.md; заведены DRAW-CHAR (отрисовка одна на всех Char, как физика после GUARD-PHYS) и L3-COLOR (зелёная кладка уровня 3: level_var_palettes = ресурс 20, есть в MSDOS/PRINCE.DAT). В roomtest.c временно оставлен автостоп на падении соперника (отладка падений скелета) — помечен ВРЕМЕННО. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,11 +11,13 @@
|
||||
#include "pop_level.h"
|
||||
#include "pop_kid.h" /* Char/play_seq — интерпретатор общий */
|
||||
#include "pop_map.h" /* hitp_* — HP Кида */
|
||||
#include "pop_state.h" /* pop_immortal — чит бессмертия */
|
||||
#include "kid_data.h"
|
||||
|
||||
pop_char_t Guard;
|
||||
uint8_t guardhp_curr, guardhp_max;
|
||||
int8_t guardhp_delta;
|
||||
uint8_t pop_guard_hurt;
|
||||
int8_t can_guard_see_kid;
|
||||
int8_t is_guard_notice;
|
||||
uint8_t guard_skill;
|
||||
@@ -66,6 +68,20 @@ void pop_guard_kill(void)
|
||||
Guard.alive = 0;
|
||||
}
|
||||
|
||||
/* take_hp (seg006:06BD) — общий для боёвки и физики; см. pop_guard.h. */
|
||||
uint8_t pop_take_hp(uint8_t count)
|
||||
{
|
||||
if (Char.charid == CHARID_0_KID) {
|
||||
if (pop_immortal) return 0;
|
||||
if (count >= hitp_curr) { hitp_delta = (int8_t)-hitp_curr; return 1; }
|
||||
hitp_delta = (int8_t)-(int8_t)count;
|
||||
} else {
|
||||
if (count >= guardhp_curr) { guardhp_delta = (int8_t)-guardhp_curr; return 1; }
|
||||
guardhp_delta = (int8_t)-(int8_t)count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* do_delta_hp (seg003): применить накопленную за кадр дельту HP и обнулить
|
||||
* её. Звать раз в кадр ПОСЛЕ тиков персонажей, как в play_frame. */
|
||||
void pop_do_delta_hp(void)
|
||||
@@ -76,7 +92,10 @@ void pop_do_delta_hp(void)
|
||||
if (hp < 0) hp = 0;
|
||||
if (hp > (int16_t)hitp_max) hp = (int16_t)hitp_max;
|
||||
hitp_curr = (uint8_t)hp;
|
||||
pop_kid_hurt = 1; /* -> «брызги» поверх Кида */
|
||||
/* Брызги и красная вспышка — только на УРОН: и draw_kid (seg008:1643),
|
||||
* и flash_if_hurt (seg003:785) смотрят именно `hitp_delta < 0`. С
|
||||
* условием «дельта не ноль» их получало и зелье здоровья (+1). */
|
||||
if (hitp_delta < 0) pop_kid_hurt = 1;
|
||||
hitp_delta = 0;
|
||||
}
|
||||
if (guardhp_delta) {
|
||||
@@ -84,10 +103,52 @@ void pop_do_delta_hp(void)
|
||||
if (hp < 0) hp = 0;
|
||||
if (hp > (int16_t)guardhp_max) hp = (int16_t)guardhp_max;
|
||||
guardhp_curr = (uint8_t)hp;
|
||||
if (guardhp_delta < 0) pop_guard_hurt = 1; /* -> брызги поверх стража */
|
||||
guardhp_delta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* check_guard_fallout (seg002:0241): персонаж слота Guard провалился НИЖЕ
|
||||
* комнаты. Уйти в комнату снизу он не может (у стража нет перехода), и
|
||||
* оригинал его просто убирает: guards_tile комнаты становится −1, то есть
|
||||
* стража там больше нет и после возврата. У нас тот же смысл — запомнить
|
||||
* состояние с tile = 30 («стража в комнате больше нет»).
|
||||
*
|
||||
* Ветки тени (падает, только если она в свободном полёте) и скелета (тот
|
||||
* возрождается в комнате 3) появятся вместе с L3-SKEL — сейчас в слоте
|
||||
* бывает только обычный страж.
|
||||
*
|
||||
* Полосу HP перерисовывать отдельно не надо: pop_hp_draw сравнивает числа
|
||||
* и сам увидит, что стража не стало. */
|
||||
/* Куда и как возрождается упавший СКЕЛЕТ (seg002:252): если под комнатой,
|
||||
* из которой он выпал, лежит комната 3 — он появляется там снова. Убить
|
||||
* его нельзя вовсе, падение лишь переносит. */
|
||||
#define SKEL_REAPPEAR_ROOM 3
|
||||
#define SKEL_REAPPEAR_X 133
|
||||
#define SKEL_REAPPEAR_ROW 1
|
||||
|
||||
void pop_guard_fallout(void)
|
||||
{
|
||||
if (Guard.charid == 0 || Guard.y < 211) return;
|
||||
if (Guard.charid == CHARID_4_SKELETON) {
|
||||
uint8_t down = pop_room_link(Guard.room, 3 /* вниз */);
|
||||
if (down == SKEL_REAPPEAR_ROOM) {
|
||||
Guard.room = down;
|
||||
Guard.x = SKEL_REAPPEAR_X;
|
||||
Guard.curr_row = SKEL_REAPPEAR_ROW;
|
||||
Guard.direction = 0; /* dir_0_right */
|
||||
Guard.alive = -1;
|
||||
pop_guard_leave(); /* запомнить его в новой комнате */
|
||||
return;
|
||||
}
|
||||
}
|
||||
pop_guard_state_save(Guard.room, 30, 0, 0, 0, 0, 0);
|
||||
Guard.charid = 0;
|
||||
Guard.direction = 0;
|
||||
Guard.alive = 0;
|
||||
guardhp_curr = 0;
|
||||
}
|
||||
|
||||
/* load_frame для стража (seg006:0293 ветка charid_2_guard): таблица у
|
||||
* стражей СВОЯ (frame_tbl_guard), индекс = frame + add_frame − 149, где
|
||||
* add_frame = 70 для кадров 102..106 (seg006:535). */
|
||||
@@ -114,6 +175,60 @@ void pop_guard_leave(void)
|
||||
(uint8_t)(Guard.alive >= 0));
|
||||
}
|
||||
|
||||
static int8_t guard_col_from_x(uint8_t xpos); /* определена ниже */
|
||||
|
||||
/* ---- Страж ИДЁТ ЗА КИДОМ в соседнюю комнату ------------------------- *
|
||||
* Порт ветки стража из exit_room (seg002:0340..0382) + follow_guard
|
||||
* (seg002:039E). Своим ходом персонаж слота Guard из комнаты не уходит
|
||||
* (его физика гейтится x ∈ [44,211), см. pop_guard_phys_tick), но когда
|
||||
* комнату меняет КИД, движок может перенести стража следом — именно так в
|
||||
* оригинале страж из комнаты 3 оказывается в комнате 2 за отступающим
|
||||
* Кидом.
|
||||
*
|
||||
* Условия оригинала, все обязательные:
|
||||
* - страж есть, ЖИВ и с ВЫНУТЫМ МЕЧОМ (то есть идёт бой);
|
||||
* - переход ВБОК: вверх и вниз оригинал не пускает никогда (там условия
|
||||
* `curr_row >= 0` и `curr_row < 3` истинны всегда → leave_guard);
|
||||
* - в целевой комнате НЕТ своего ЖИВОГО стража (иначе их стало бы два:
|
||||
* проверка `guards_tile >= 30 || guards_seq_hi != 0` — «пусто или
|
||||
* труп»);
|
||||
* - страж прижат к тому краю, в который ушёл Кид: x < 91 при уходе
|
||||
* влево, x >= 165 при уходе вправо.
|
||||
*
|
||||
* side — куда ушёл КИД (0 = left, 1 = right, 2 = up, 3 = down), то есть
|
||||
* roomleave_result оригинала. Возврат: 1 — страж перенесён в new_room
|
||||
* (вызывающий НЕ должен звать ни pop_guard_leave, ни pop_guard_enter),
|
||||
* 0 — остаётся, всё как раньше. */
|
||||
uint8_t pop_guard_follow(uint8_t new_room, uint8_t side)
|
||||
{
|
||||
uint8_t tile, color, skill;
|
||||
int8_t dir;
|
||||
|
||||
if (Guard.charid == 0) return 0; /* dir_56_none */
|
||||
if (Guard.alive >= 0 || Guard.sword != SWORD_2_DRAWN) return 0;
|
||||
if (side > 1) return 0; /* только вбок */
|
||||
/* «В целевой комнате уже есть живой страж» = страж по данным есть И это
|
||||
* не запомненный труп (у трупа сохранена последовательность). */
|
||||
if (pop_level_guard(new_room, &tile, &dir, &color, &skill) &&
|
||||
pop_guard_state_seq(new_room) == 0) return 0;
|
||||
if (side == 0) { if (Guard.x >= 91) return 0; } /* Кид ушёл влево */
|
||||
else { if (Guard.x < 165) return 0; } /* Кид ушёл вправо */
|
||||
|
||||
/* follow_guard: сначала «стража нет» в ОБЕИХ комнатах (в оригинале
|
||||
* guards_tile = 0xFF). Без этого он раздвоится: в старой комнате его
|
||||
* поднимут из данных при возврате, а в новой — при следующем входе. */
|
||||
pop_guard_state_save(Guard.room, 30, 0, 0, 0, 0, 0);
|
||||
pop_guard_state_save(new_room, 30, 0, 0, 0, 0, 0);
|
||||
|
||||
/* goto_other_room (seg002:0486) для стража: комната + перенос X через
|
||||
* шов (влево +140, вправо −140). Ряд при боковом переходе не меняется;
|
||||
* колонку пересчитываем из новой X, как enter_guard. */
|
||||
Guard.room = new_room;
|
||||
Guard.x = (uint8_t)(side == 0 ? Guard.x + 140 : Guard.x - 140);
|
||||
Guard.curr_col = guard_col_from_x(Guard.x);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* get_tile_div_mod_m7 (seg006:697): колонка комнаты по X персонажа —
|
||||
* floor((xpos − 7 − 58) / 14), где 58 = pop_x_bump[FIRST_ONSCREEN_COLUMN]. */
|
||||
static int8_t guard_col_from_x(uint8_t xpos)
|
||||
@@ -134,6 +249,10 @@ void pop_guard_enter(uint8_t room)
|
||||
* на таком уровне полезли бы стражи из данных комнат. */
|
||||
if (pop_level_guard_type(pop_current_level) < 0) return;
|
||||
if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return;
|
||||
/* Цвет — ДО отрисовки комнаты (как redraw_screen, seg003:255): слоты
|
||||
* палитры общие на экран, и смена посреди кадра дала бы стража в новой
|
||||
* палитре с полосой HP в старой. */
|
||||
pop_guard_set_palette(color);
|
||||
/* enter_guard (seg002:0112) + pos_guards (seg003): X считается из
|
||||
* колонки тайла, а не берётся из файла. */
|
||||
Guard.room = room;
|
||||
@@ -142,8 +261,13 @@ void pop_guard_enter(uint8_t room)
|
||||
Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
|
||||
Guard.curr_col = (int8_t)(tile % 10);
|
||||
Guard.direction = dir;
|
||||
Guard.charid = CHARID_2_GUARD;
|
||||
Guard.sword = 0; /* sword_0_sheathed */
|
||||
/* seg002:196: на уровне со скелетами слот поднимает СКЕЛЕТА, и он
|
||||
* встаёт сразу активным — с вынутым мечом (seq_63), а не в стойке
|
||||
* покоя (seq_77). Стойку ставит ветка ниже, вместе с seq трупа. */
|
||||
Guard.charid = (pop_level_guard_type(pop_current_level) == 2)
|
||||
? CHARID_4_SKELETON : CHARID_2_GUARD;
|
||||
Guard.sword = (Guard.charid == CHARID_4_SKELETON)
|
||||
? SWORD_2_DRAWN : 0; /* sword_0_sheathed */
|
||||
Guard.alive = -1;
|
||||
Guard.action = 1; /* actions_1_run_jump */
|
||||
Guard.fall_x = Guard.fall_y = 0;
|
||||
|
||||
Reference in New Issue
Block a user