diff --git a/applications/PoP/roomtest/guards.c b/applications/PoP/roomtest/guards.c index 4787d65..1d94f8c 100644 --- a/applications/PoP/roomtest/guards.c +++ b/applications/PoP/roomtest/guards.c @@ -14,9 +14,13 @@ * seg004 fight/collision) добавляются сюда. */ #include "pop_guard.h" +#include "pop_kid.h" void pop_guard_tick(void) __banked { if (Guard.charid == 0) return; /* стража в комнате нет */ + /* Анимация: та же seqtbl, что у Кида, но своя таблица кадров — + * выбирается по Char.charid внутри load_frame (seg006:0293). */ + pop_guard_play(); /* TODO: порт control_guard/fight (seg005/seg004) — состояние в Guard. */ } diff --git a/applications/PoP/roomtest/pop_char.h b/applications/PoP/roomtest/pop_char.h new file mode 100644 index 0000000..b12e3a7 --- /dev/null +++ b/applications/PoP/roomtest/pop_char.h @@ -0,0 +1,32 @@ +/* + * pop_char.h — состояние ПЕРСОНАЖА, общее для Кида, стража и активной + * копии Char. Порт char_type (types.h:302). + * + * Зачем отдельным заголовком: интерпретатор последовательностей в + * оригинале один на всех (работает с Char, а вокруг стоят + * loadkid/savekid и loadshad/saveshad, seg006:809). Пока у Кида был свой + * усечённый тип, а у стража свой, общий интерпретатор был невозможен. + * Поля и их порядок — как в char_type, чтобы порт seg005/seg006 ложился + * один в один. + */ +#ifndef POP_CHAR_H +#define POP_CHAR_H + +#include + +typedef struct { + uint8_t frame; + uint8_t x, y; + int8_t direction; /* -1 влево, 0 вправо */ + int8_t curr_col, curr_row; + uint8_t action; + int8_t fall_x, fall_y; + uint8_t room; + uint8_t repeat; + uint8_t charid; /* 0 = Kid, 2 = страж (см. CHARID_* в pop_guard.h) */ + uint8_t sword; + int8_t alive; /* -1 = жив (как в оригинале), 0 = убит */ + uint16_t curr_seq; /* адрес в seqtbl (база SEQTBL_BASE) */ +} pop_char_t; + +#endif diff --git a/applications/PoP/roomtest/pop_guard.c b/applications/PoP/roomtest/pop_guard.c index c73ea9c..ba645e5 100644 --- a/applications/PoP/roomtest/pop_guard.c +++ b/applications/PoP/roomtest/pop_guard.c @@ -9,7 +9,7 @@ #include "pop_guard.h" #include "pop_geom.h" #include "pop_level.h" -#include "pop_kid.h" /* pop_kid_data_frame — таблицы в EMM-странице */ +#include "pop_kid.h" /* Char/play_seq — интерпретатор общий */ #include "kid_data.h" pop_char_t Guard; @@ -80,12 +80,13 @@ void pop_guard_enter(uint8_t room) Guard.alive = -1; Guard.action = 1; /* actions_1_run_jump */ Guard.fall_x = Guard.fall_y = 0; - /* seq_77_guard_stand_inactive даёт кадр 166 (frame_166_stand_inactive, - * seqtbl.c:266). Интерпретатор последовательностей для стража придёт - * вместе с ИИ (следующая фаза) — сейчас ставим кадр напрямую, чтобы - * страж просто СТОЯЛ и было что рисовать/убивать читом. */ - Guard.frame = 166; guardhp_max = guardhp_curr = 3; /* get_guard_hp: skill 0, уровень 1 */ guardhp_delta = 0; - pop_guard_load_frame(); + /* enter_guard (seg002:0208): seqtbl_offset_char(seq_77_guard_stand_inactive). + * Последовательность крутит общий интерпретатор (pop_guard_play), а + * таблицу кадров он выбирает по Char.charid — у стража своя. */ + pop_loadshad(); + pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE); + pop_saveshad(); + pop_guard_play(); /* до первого кадра */ } diff --git a/applications/PoP/roomtest/pop_guard.h b/applications/PoP/roomtest/pop_guard.h index 6d3e59e..57f9b67 100644 --- a/applications/PoP/roomtest/pop_guard.h +++ b/applications/PoP/roomtest/pop_guard.h @@ -19,6 +19,10 @@ #include #include "kid_data.h" /* kframe */ +#include "pop_char.h" + +/* seqids (types.h:1070) — последовательности стража. */ +#define SEQ_77_GUARD_STAND_INACTIVE 77 /* charids (types.h:320) — нужны хотя бы для «скелет не убивается читом». */ #define CHARID_0_KID 0 @@ -27,20 +31,7 @@ #define CHARID_4_SKELETON 4 #define CHARID_5_PRINCESS 5 -typedef struct { - uint8_t frame; - uint8_t x, y; - int8_t direction; - int8_t curr_col, curr_row; - uint8_t action; - int8_t fall_x, fall_y; - uint8_t room; - uint8_t repeat; - uint8_t charid; - uint8_t sword; - int8_t alive; /* -1 = жив (как в оригинале), 0 = убит */ - uint16_t curr_seq; -} pop_char_t; +/* Тип персонажа — в pop_char.h (общий с Кидом и Char). */ extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты (как в оригинале) */ extern uint8_t guardhp_curr, guardhp_max; diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index bbf6e70..9e16535 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -10,12 +10,13 @@ #include #include #include "pop_kid.h" +#include "pop_guard.h" /* Guard/pop_gframe/CHARID_* — интерпретатор общий */ #include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */ #include "pop_map.h" /* pop_clip_char_top (clip_char) */ #include "pop_geom.h" #include "kid_data.h" -kid_t Kid; +pop_char_t Kid; /* Флаг сотрясения (seg006 knock): ставится seqtbl-командой KNOCK_UP/DOWN * в play_seq, потребляется pop_check_knock (pop_map) — трясёт loose-полы. */ @@ -98,6 +99,16 @@ static const uint8_t *kd_frame_ptr(uint8_t i) off += (uint16_t)(KD_DATA_OFF + KID_BIN_FRAMES_OFF); return (const uint8_t *)off; } + +/* То же для таблицы кадров СТРАЖА (frame_tbl_guard дописана в ту же + * страницу данных, см. pop_extract_kid_data.py). */ +static const uint8_t *kd_gframe_ptr(uint8_t i) +{ + uint16_t off = (uint16_t)i; + off += (uint16_t)(off << 2); + off += (uint16_t)(KD_DATA_OFF + KID_BIN_GFRAMES_OFF); + return (const uint8_t *)off; +} #define KD_SEQ(cs) (*((const uint8_t *)(KD_DATA_OFF + KID_BIN_SEQTBL_OFF) + \ (uint16_t)((cs) - SEQTBL_BASE))) /* Байт seqtbl по адресу оригинала (база SEQTBL_BASE); ТОЛЬКО под замапленной @@ -106,8 +117,25 @@ static const uint8_t *kd_frame_ptr(uint8_t i) static uint8_t kdat_blk, kdat_page, kdat_ok; -/* Текущий кадр (порт глобала cur_frame, seg006): 5 байт вместо таблицы. */ +/* Текущий кадр (порт глобала cur_frame, seg006): 5 байт вместо таблицы. + * Заполняется load_frame для АКТИВНОГО персонажа (Char), поэтому на + * выходе из play_seq его надо сохранить в кэш своего персонажа: страж + * тикает ПОСЛЕ Кида, и без разделения kid_draw взял бы кадр стража. */ static kframe cur_frame; +static kframe kid_frame; /* кадр Kid для kid_draw */ + +/* Char (seg006): АКТИВНЫЙ персонаж. Интерпретатор последовательностей в + * оригинале работает не с Kid и не с Guard, а с этой копией, а вокруг + * стоят loadkid/savekid и loadshad/saveshad (seg006:809..825). Копия, а + * не указатель, — и так же, как в оригинале, и быстрее на Z80: обращения + * в горячем цикле идут абсолютной адресацией глобала, а 16-байтовое + * копирование платится один раз на переключение персонажа. */ +pop_char_t Char; + +void pop_loadkid(void) { Char = Kid; } +void pop_savekid(void) { Kid = Char; kid_frame = cur_frame; } +void pop_loadshad(void) { Char = Guard; } +void pop_saveshad(void) { Guard = Char; pop_gframe = cur_frame; } int pop_kid_data_load(const char *path) { @@ -166,7 +194,19 @@ void pop_kid_data_frame(uint16_t tbl_off, uint8_t idx, uint8_t *out5) * cur_frame. Звать ТОЛЬКО когда страница данных замаплена в W0. */ static void load_frame(void) { - const uint8_t *f = kd_frame_ptr(Kid.frame); + /* Таблица кадров зависит от персонажа (seg006:0293): у Kid своя, у + * стража frame_tbl_guard с индексом frame + add_frame − 149, где + * add_frame = 70 для кадров 102..106 (seg006:535). */ + const uint8_t *f; + if (Char.charid == CHARID_2_GUARD) { + int16_t idx = (int16_t)Char.frame; + if (idx >= 102 && idx < 107) idx += 70; + idx -= 149; + if (idx < 0 || idx >= KID_NGFRAMES) { cur_frame.image = 255; return; } + f = kd_gframe_ptr((uint8_t)idx); + } else { + f = kd_frame_ptr(Char.frame); + } cur_frame.image = f[0]; cur_frame.dx = (int8_t)f[1]; cur_frame.dy = (int8_t)f[2]; @@ -176,8 +216,8 @@ static void load_frame(void) static uint8_t char_dx_forward(int8_t dx) { - if (Kid.direction < 0) dx = (int8_t)-dx; - return (uint8_t)(Kid.x + dx); + if (Char.direction < 0) dx = (int8_t)-dx; + return (uint8_t)(Char.x + dx); } /* Аксессоры к текущему кадру (для pop_map): читают cur_frame, НЕ таблицу — @@ -189,6 +229,11 @@ uint8_t kid_cur_flags(void) { return cur_frame.flags; } * pop_ctrl/pop_map зовут ЕЁ, а не дублируют kid_seq_off/kid_seqtbl). */ void kid_set_seq(uint8_t seq_id) { Kid.curr_seq = kid_seq_off[seq_id]; } +/* То же для АКТИВНОГО персонажа — внутри окна Char (порт + * seqtbl_offset_char, seg006). Нужен стражу: его последовательность + * ставится, когда Char уже загружен из Guard. */ +void pop_char_set_seq(uint8_t seq_id) { Char.curr_seq = kid_seq_off[seq_id]; } + /* play_seq (seg006.c:570): крутит опкоды до КАДРА (байт < 0xF0). * Страница данных маппится в W0 на ВЕСЬ разбор — один map/unmap за вызов * (в тике, до отрисовки: конфликта с атласом в W0 нет). В конце — @@ -198,42 +243,42 @@ void play_seq(void) if (!kdat_ok) return; gfx_w0_map(kdat_page); for (;;) { - uint8_t cmd = SEQ(Kid.curr_seq); - Kid.curr_seq++; + uint8_t cmd = SEQ(Char.curr_seq); + Char.curr_seq++; switch (cmd) { case 0xFB: /* DX */ - Kid.x = char_dx_forward((int8_t)SEQ(Kid.curr_seq)); - Kid.curr_seq++; + Char.x = char_dx_forward((int8_t)SEQ(Char.curr_seq)); + Char.curr_seq++; break; case 0xFA: /* DY */ - Kid.y = (uint8_t)(Kid.y + (int8_t)SEQ(Kid.curr_seq)); - Kid.curr_seq++; + Char.y = (uint8_t)(Char.y + (int8_t)SEQ(Char.curr_seq)); + Char.curr_seq++; break; case 0xFE: /* FLIP */ - Kid.direction = (int8_t)~Kid.direction; + Char.direction = (int8_t)~Char.direction; break; case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */ - Kid.curr_seq += 2; + Char.curr_seq += 2; break; case 0xFF: { /* JMP */ - uint16_t a = (uint16_t)(SEQ(Kid.curr_seq) | - ((uint16_t)SEQ((uint16_t)(Kid.curr_seq + 1)) << 8)); - Kid.curr_seq = a; + uint16_t a = (uint16_t)(SEQ(Char.curr_seq) | + ((uint16_t)SEQ((uint16_t)(Char.curr_seq + 1)) << 8)); + Char.curr_seq = a; break; } case 0xFD: /* UP */ - Kid.curr_row--; + Char.curr_row--; break; case 0xFC: /* DOWN */ - Kid.curr_row++; + Char.curr_row++; break; case 0xF9: /* ACTION */ - Kid.action = SEQ(Kid.curr_seq); - Kid.curr_seq++; + Char.action = SEQ(Char.curr_seq); + Char.curr_seq++; break; case 0xF8: /* SET_FALL */ - Kid.fall_x = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++; - Kid.fall_y = (int8_t)SEQ(Kid.curr_seq); Kid.curr_seq++; + Char.fall_x = (int8_t)SEQ(Char.curr_seq); Char.curr_seq++; + Char.fall_y = (int8_t)SEQ(Char.curr_seq); Char.curr_seq++; break; case 0xF5: /* KNOCK_UP: сотрясение трясёт loose РЯДОМ ВЫШЕ (check_knock) */ knock = 1; @@ -242,20 +287,20 @@ void play_seq(void) knock = -1; break; case 0xF2: /* SOUND — потребить 1 байт */ - Kid.curr_seq++; + Char.curr_seq++; break; case 0xF1: /* END_LEVEL */ break; case 0xF3: /* GET_ITEM: аргумент 1 — применить эффект предмета * (порт play_seq -> proc_get_object, seg006) */ - if (SEQ(Kid.curr_seq) == 1) + if (SEQ(Char.curr_seq) == 1) pop_proc_get_object(); - Kid.curr_seq++; + Char.curr_seq++; break; case 0xF6: /* DIE nop */ break; default: /* КАДР */ - Kid.frame = cmd; + Char.frame = cmd; load_frame(); /* cur_frame := таблица[frame] (страница ещё в W0) */ gfx_w0_unmap(); return; @@ -263,6 +308,12 @@ void play_seq(void) } } +/* Прокрутить последовательность КОНКРЕТНОГО персонажа (порт связки + * loadkid/play_seq/savekid, seg006). Звать вместо голого play_seq: тот + * работает с Char и сам по себе не знает, чей это персонаж. */ +void pop_kid_play(void) { pop_loadkid(); play_seq(); pop_savekid(); } +void pop_guard_play(void) { pop_loadshad(); play_seq(); pop_saveshad(); } + void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir) { Kid.curr_col = col; @@ -272,13 +323,14 @@ void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir) Kid.y = (uint8_t)pop_y_land[row + 1]; Kid.fall_x = Kid.fall_y = 0; Kid.repeat = 0; + Kid.charid = 0; /* charid_0_kid: своя таблица кадров */ Kid.curr_seq = kid_seq_off[seq_id]; - play_seq(); /* до первого кадра */ + pop_kid_play(); /* до первого кадра */ } void kid_tick(void) { - play_seq(); + pop_kid_play(); } /* Отрисовка текущего кадра: obj_x=(char_x+dx)*2-116, obj_y=char_y+dy @@ -387,7 +439,7 @@ void kid_heal(void) void kid_draw(void) { - kframe fr = cur_frame; /* load_frame уже положил кадр (play_seq) */ + kframe fr = kid_frame; /* кадр Kid, сохранённый savekid */ int fwd, obj_x, obj_y, top; uint8_t page, idx; const uint8_t *img; diff --git a/applications/PoP/roomtest/pop_kid.h b/applications/PoP/roomtest/pop_kid.h index a7493bd..52a8865 100644 --- a/applications/PoP/roomtest/pop_kid.h +++ b/applications/PoP/roomtest/pop_kid.h @@ -11,20 +11,10 @@ #define POP_KID_H #include +#include "pop_char.h" -/* Состояние персонажа (подмножество char_type, types.h). */ -typedef struct { - uint8_t frame; - uint8_t x, y; - int8_t direction; /* -1 влево, 0 вправо */ - int8_t curr_col, curr_row; - uint8_t action; - int8_t fall_x, fall_y; - uint8_t repeat; - uint16_t curr_seq; /* адрес в seqtbl (база SEQTBL_BASE) */ -} kid_t; - -extern kid_t Kid; +/* Состояние персонажа — общий тип для Кида, стража и Char (pop_char.h). */ +extern pop_char_t Kid; /* Флаг сотрясения (seg006 knock): +1 = KNOCK_UP (ряд выше), -1 = KNOCK_DOWN * (свой ряд). Ставит play_seq, читает/сбрасывает pop_check_knock. */ @@ -63,7 +53,22 @@ void kid_tick(void); /* play_seq напрямую (для pop_map start_fall — переход в fall-seq в тот же * physics-шаг, как оригинал). */ -void play_seq(void); +void play_seq(void); /* работает с Char — см. pop_kid_play ниже */ + +/* Активный персонаж (порт Char, seg006) и его загрузка/выгрузка. + * Интерпретатор последовательностей общий для Kid и стража: у них общая + * seqtbl, но РАЗНЫЕ таблицы кадров (load_frame выбирает по Char.charid). */ +extern pop_char_t Char; +void pop_loadkid(void); +void pop_savekid(void); +void pop_loadshad(void); +void pop_saveshad(void); + +/* Прокрутить последовательность персонажа целиком (load + play_seq + + * save). Звать вместо голого play_seq. */ +void pop_char_set_seq(uint8_t seq_id); /* seq активного персонажа */ +void pop_kid_play(void); +void pop_guard_play(void); /* Поля текущего кадра kid_frames[Kid.frame] (pop_map, без дублирования * таблицы во втором TU). */ diff --git a/applications/PoP/roomtest/pop_map.c b/applications/PoP/roomtest/pop_map.c index 73c13e4..ded72ee 100644 --- a/applications/PoP/roomtest/pop_map.c +++ b/applications/PoP/roomtest/pop_map.c @@ -442,7 +442,7 @@ static void land(void) if (deadly || take_hp(1)) { /* 3+ этажа или последнее HP */ take_hp(100); kid_set_seq(SEQ_22_CRUSHED); - play_seq(); + pop_kid_play(); determine_col(); pop_kid_dead = 1; return; @@ -451,7 +451,7 @@ static void land(void) } Kid.fall_x = Kid.fall_y = 0; kid_set_seq(seq); - play_seq(); + pop_kid_play(); determine_col(); } @@ -465,7 +465,7 @@ static void start_fall(void) else if (frame == 44) seq = SEQ_21_FALL_RUNJUMP; else seq = SEQ_7_FALL; /* frame 9 + stand/step/crouch */ kid_set_seq(seq); - play_seq(); + pop_kid_play(); determine_col(); /* Проверки «упал в стену» — ТОЛЬКО пока персонаж В комнате (curr_row<=2). * Ниже нижнего ряда (curr_row=3 после inc — падение сквозь дыру loose) @@ -531,7 +531,7 @@ static void check_grab(void) Kid.y = (uint8_t)pop_y_land[Kid.curr_row + 1]; Kid.fall_y = 0; kid_set_seq(SEQ_15_GRAB_LEDGE_MIDAIR); - play_seq(); + pop_kid_play(); determine_col(); grab_timer = 12; /* (TODO: sound grab) */ } @@ -886,7 +886,7 @@ static void bumped_fall(void) Kid.fall_x = 0; } else { kid_set_seq(SEQ_45_BUMPFALL); /* (TODO: bumped_sound) */ - play_seq(); + pop_kid_play(); } } @@ -920,7 +920,7 @@ static void bumped_floor(void) kid_set_seq(SEQ_46_HARDBUMP); else kid_set_seq(SEQ_47_BUMP); /* (TODO: bumped_sound) */ - play_seq(); + pop_kid_play(); } /* check_bumped (seg004): если передний край ЗАШЁЛ за грань стены — выровнять @@ -1157,14 +1157,14 @@ static void fell_on_your_head(void) pop_kid_hurt = 1; /* hitp_delta<0 → draw_hurt_splash */ if (take_hp(1)) { kid_set_seq(SEQ_22_CRUSHED); - play_seq(); + pop_kid_play(); determine_col(); pop_kid_dead = 1; } else if (frame != FRAME_109_CROUCH) { if (get_tile_behind_char() == TILE_EMPTY) Kid.x = (uint8_t)char_dx_forward(-2); kid_set_seq(SEQ_52_LOOSE_FELL_ON_KID); - play_seq(); + pop_kid_play(); determine_col(); } } @@ -1430,7 +1430,7 @@ static void spiked(uint8_t tilepos) Kid.fall_y = 0; take_hp(100); /* (TODO: sound_48_spiked) */ kid_set_seq(SEQ_51_SPIKED); - play_seq(); + pop_kid_play(); determine_col(); pop_kid_dead = 1; }