/* * pop_kid.c — движок Kid (Фаза K1): порт seg006.c play_seq/load_frame + * seqtbl-интерпретатор. Данные — kid_data.h. */ #include #include #include #include "pop_kid.h" #include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */ #include "kid_data.h" kid_t Kid; /* Флаг сотрясения (seg006 knock): ставится seqtbl-командой KNOCK_UP/DOWN * в play_seq, потребляется pop_check_knock (pop_map) — трясёт loose-полы. */ int8_t knock; /* Координатные таблицы (data.h). */ static const uint8_t x_bump[20] = { (uint8_t)-12, 2, 16, 30, 44, 58, 72, 86, 100, 114, 128, 142, 156, 170, 184, 198, 212, 226, 240, 254 }; static const int16_t y_land[5] = {-8, 55, 118, 181, 244}; #define FIRST_ONSCREEN_COLUMN 5 #define TILE_SIZEX 14 /* ---- Атласы Kid (прямая адресация image>>3) ------------------------ */ #define KID_MAXPAGES 28 static atlas_t kidp[KID_MAXPAGES]; static uint8_t kid_npages; int pop_kid_load(uint8_t npages) { static const char *const names[KID_MAXPAGES] = { "kid0.atl","kid1.atl","kid2.atl","kid3.atl","kid4.atl","kid5.atl", "kid6.atl","kid7.atl","kid8.atl","kid9.atl","kid10.atl","kid11.atl", "kid12.atl","kid13.atl","kid14.atl","kid15.atl","kid16.atl","kid17.atl", "kid18.atl","kid19.atl","kid20.atl","kid21.atl","kid22.atl","kid23.atl", "kid24.atl","kid25.atl","kid26.atl","kid27.atl" }; uint8_t i; if (npages > KID_MAXPAGES) npages = KID_MAXPAGES; for (i = 0; i < npages; i++) if (atlas_load(&kidp[i], names[i]) != 0) return -1; kid_npages = npages; return 0; } void pop_kid_free(void) { uint8_t i; for (i = 0; i < kid_npages; i++) atlas_free(&kidp[i]); kid_npages = 0; } /* ---- seqtbl-доступ (база SEQTBL_BASE) ------------------------------ */ #define SEQ(cs) kid_seqtbl[(uint16_t)((cs) - SEQTBL_BASE)] static uint8_t char_dx_forward(int8_t dx) { if (Kid.direction < 0) dx = (int8_t)-dx; return (uint8_t)(Kid.x + dx); } /* Аксессоры к текущему кадру (для pop_map — без дублирования kid_frames). */ int8_t kid_cur_dx(void) { return kid_frames[Kid.frame].dx; } uint8_t kid_cur_flags(void) { return kid_frames[Kid.frame].flags; } /* seqtbl_offset_char: сменить последовательность Kid (единая точка — * 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]; } /* play_seq (seg006.c:570): крутит опкоды до КАДРА (байт < 0xF0). */ void play_seq(void) { for (;;) { uint8_t cmd = SEQ(Kid.curr_seq); Kid.curr_seq++; switch (cmd) { case 0xFB: /* DX */ Kid.x = char_dx_forward((int8_t)SEQ(Kid.curr_seq)); Kid.curr_seq++; break; case 0xFA: /* DY */ Kid.y = (uint8_t)(Kid.y + (int8_t)SEQ(Kid.curr_seq)); Kid.curr_seq++; break; case 0xFE: /* FLIP */ Kid.direction = (int8_t)~Kid.direction; break; case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */ Kid.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; break; } case 0xFD: /* UP */ Kid.curr_row--; break; case 0xFC: /* DOWN */ Kid.curr_row++; break; case 0xF9: /* ACTION */ Kid.action = SEQ(Kid.curr_seq); Kid.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++; break; case 0xF5: /* KNOCK_UP: сотрясение трясёт loose РЯДОМ ВЫШЕ (check_knock) */ knock = 1; break; case 0xF4: /* KNOCK_DOWN: приземление трясёт loose В СВОЁМ РЯДУ */ knock = -1; break; case 0xF2: /* SOUND — потребить 1 байт */ Kid.curr_seq++; break; case 0xF1: /* END_LEVEL */ break; case 0xF3: /* GET_ITEM — потребить 1 байт */ Kid.curr_seq++; break; case 0xF6: /* DIE nop */ break; default: /* КАДР */ Kid.frame = cmd; return; } } } void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir) { Kid.curr_col = col; Kid.curr_row = row; Kid.direction = dir; Kid.x = (uint8_t)(x_bump[col + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX); Kid.y = (uint8_t)y_land[row + 1]; Kid.fall_x = Kid.fall_y = 0; Kid.repeat = 0; Kid.curr_seq = kid_seq_off[seq_id]; play_seq(); /* до первого кадра */ } void kid_tick(void) { play_seq(); } /* Отрисовка текущего кадра: obj_x=(char_x+dx)*2-116, obj_y=char_y+dy * (seg008.c:1728 load_frame_to_obj). НИЗ спрайта на obj_y, левый край * на obj_x. Калибровка XOFF/YOFF — сверить с эталоном. * * Зеркалирование (K2a): спрайты Kid хранятся ЛИЦОМ ВЛЕВО (как в оригинале). * seg008.c:864 — флип, когда персонаж смотрит ВПРАВО (direction==dir_0_right * ==0), и тогда xpos -= image->w (draw_mid). Реализуем бесплатным * горизонтальным флипом column-major спрайта (gfx_blit_cols flip=1): * зеркало происходит ВНУТРИ [x,x+w), поэтому при флипе подаём x=obj_x-w, * чтобы «hot-point» кадра остался на месте. * * Kid рисуется банком GFX_BANK_SPRITE (только видео-ОЗУ, НЕ теневая копия * — 0xFF прозрачен), чтобы kid_heal() мог восстановить чистый фон из * теневой копии (фон запёкся в неё при pop_room_draw). Прямоугольник * последней отрисовки запоминается для heal. */ #define KID_XOFF 0 #define KID_YOFF 0 /* Прямоугольник последней отрисовки Kid — ПО СТРАНИЦЕ (0/1). У каждой * графической страницы своя ОЗУ-копия/видео-ОЗУ, поэтому heal должен * стирать пиксели Kid именно той страницы, в которую сейчас рисуем * (при дабл-буфере страница чередуется — прошлый Kid на этой странице * был 2 логических кадра назад). В однобуфере используется только [0]. */ static int kid_lx[2], kid_ly[2]; static uint16_t kid_lw[2], kid_lh[2]; static uint8_t kid_lvalid[2]; /* Метрики последнего kid_draw для футпринта fore-over-Kid (pop_bg): * obj_x — ЛОГИЧЕСКАЯ X кадра (ДО ×8/7), obj_y — низ, w/h — размер. */ static int kid_fp_x, kid_fp_y; static uint16_t kid_fp_w, kid_fp_h; /* Рендер-смещение по ЛОГИЧЕСКОЙ X (straddle у шва, порт xpos_in_drawn_room): * ∓140 когда комната Kid ≠ отрисованной; 0 обычно. */ static int kid_render_dx; void pop_kid_set_render_dx(int dx) { kid_render_dx = dx; } int kid_fp_obj_x(void) { return kid_fp_x; } int kid_fp_obj_y(void) { return kid_fp_y; } uint16_t kid_fp_width(void) { return kid_fp_w; } uint16_t kid_fp_height(void) { return kid_fp_h; } /* draw_hurt_splash (seg006:2003): «брызги» урона поверх персонажа — спрайт * chtab_2_kid id 218 (res619.png; в наших атласах это image 218, стр. 27). * Позиция от кадра Kid: obj_y −= 15, obj_x += 5 ВПЕРЁД (obj_dx_forward). * Кадры смерти/падения (185, 106..110) и пик (177) в оригинале смещают * иначе — тут только общий случай + «на пиках» (порт по мере надобности). * Рисовать ПОСЛЕ kid_draw (splash кладётся в objtable позже Kid). */ #define KID_SPLASH_IMAGE 218 void kid_draw_splash(void) { int obj_x = kid_fp_x, obj_y = kid_fp_y - 15; uint8_t page = KID_SPLASH_IMAGE >> 3, idx = KID_SPLASH_IMAGE & 7; const uint8_t *img; uint16_t w, h; if (page >= kid_npages) return; if (Kid.frame == 177) /* напоролся на пики */ obj_y = kid_fp_y, obj_x -= (Kid.direction < 0) ? -5 : 5; else obj_x += (Kid.direction < 0) ? -5 : 5; obj_x = obj_x * 8 / 7; /* лог. X -> экран (calc_screen_x_coord) */ img = (const uint8_t *)atlas_image(&kidp[page], idx); gfx_w0_map(kidp[page].page); w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); if (w && h) { uint8_t flip = (Kid.direction >= 0); int bx = obj_x; int top = obj_y - (int)h + 1; if (flip) bx -= (int)w; gfx_set_bank(GFX_BANK_SPRITE); gfx_blit_cols(bx, top + POP_YOFF, img, flip); gfx_set_bank(GFX_BANK_NORMAL); pop_clip_sprite(bx, w, top + POP_YOFF, (int)h); /* Прямоугольник splash расширяет область heal ЭТОЙ страницы: иначе * брызги останутся на экране после кадра. */ { uint8_t dp = gfx_get_draw_page(); if (kid_lvalid[dp]) { int x0 = kid_lx[dp] < bx ? kid_lx[dp] : bx; int y0 = kid_ly[dp] < top ? kid_ly[dp] : top; int x1 = kid_lx[dp] + (int)kid_lw[dp]; int y1 = kid_ly[dp] + (int)kid_lh[dp]; if (bx + (int)w > x1) x1 = bx + (int)w; if (top + (int)h > y1) y1 = top + (int)h; kid_lx[dp] = x0; kid_ly[dp] = y0; kid_lw[dp] = (uint16_t)(x1 - x0); kid_lh[dp] = (uint16_t)(y1 - y0); } } } gfx_w0_unmap(); } /* Восстановить фон из теневой копии на месте ПРЕДЫДУЩЕГО кадра Kid. */ void kid_heal(void) { uint8_t p = gfx_get_draw_page(); if (kid_lvalid[p]) gfx_heal(kid_lx[p], kid_ly[p] + POP_YOFF, kid_lw[p], kid_lh[p]); /* +YOFF: центрирование */ kid_lvalid[p] = 0; } void kid_draw(void) { kframe fr = kid_frames[Kid.frame]; int fwd, obj_x, obj_y, top; uint8_t page, idx; const uint8_t *img; uint16_t w, h; if (fr.image == 255) return; /* пустой кадр */ /* +kid_render_dx: straddle-смещение (∓140) когда комната Kid ≠ * отрисованной — спрайт за кромкой. Коллизия (Kid.x в pop_map) НЕ * смещается: она в кадре комнаты Kid; смещение только рендерное. */ fwd = (int)Kid.x + ((Kid.direction < 0) ? -fr.dx : fr.dx) + kid_render_dx; obj_x = (fwd << 1) - 116; obj_y = (int)Kid.y + fr.dy; if ((int8_t)(fr.flags ^ Kid.direction) >= 0) obj_x++; /* even/odd pixel (в ЛОГИЧЕСКОЙ координате) */ kid_fp_x = obj_x; /* ЛОГИЧЕСКАЯ X (до ×8/7) — для футпринта fore */ kid_fp_y = obj_y; /* seg008 calc_screen_x_coord: логическая X (280 шир = 28px/тайл) -> * экран (320 шир = 32px/тайл), ×320/280 = ×8/7. БЕЗ этого Kid уезжает * влево от тайлов тем сильнее, чем правее (col9 ~36px). Коллизия — в * логической системе (не масштабируется). */ obj_x = obj_x * 8 / 7; page = (uint8_t)(fr.image >> 3); idx = (uint8_t)(fr.image & 7); if (page >= kid_npages) return; /* страница не загружена */ img = (const uint8_t *)atlas_image(&kidp[page], idx); gfx_w0_map(kidp[page].page); w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8)); h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8)); if (w && h) { uint8_t flip = (Kid.direction >= 0); /* dir_0_right -> зеркало */ int bx = obj_x + KID_XOFF; if (flip) bx -= (int)w; /* seg008: xpos -= image->w */ top = obj_y - (int)h + 1 + KID_YOFF; uint8_t dp = gfx_get_draw_page(); /* в какую страницу рисуем */ gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */ gfx_blit_cols(bx, top + POP_YOFF, img, flip); /* +YOFF: центрирование */ gfx_set_bank(GFX_BANK_NORMAL); /* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) — * узко, только по прямоугольнику Кида и только при выходе. */ pop_clip_sprite(bx, w, top + POP_YOFF, (int)h); kid_lx[dp] = bx; kid_ly[dp] = top; kid_lw[dp] = w; kid_lh[dp] = h; kid_lvalid[dp] = 1; kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */ } gfx_w0_unmap(); }