/* * pop_bg.c — ГОРЯЧАЯ половина слоя фона PoP: то, что работает КАЖДЫЙ кадр — * передний слой поверх персонажей (порт seg003 redraw_at_char + seg008 * set_char_collision), оверлеи кромки, кладка стены и клип. Полная * отрисовка комнаты и точечные перерисовки уехали в pop_room.c (банк 7); * граница реза и её цена расписаны в _pop_bg.h. * * Подробности алгоритма кладки — в toolchain/render_room.py и * memory/pop_background_strategy. Только DUNGEON-стиль (Фаза 1). */ #include #include #include #include #include #include "pop_bg.h" #include "_pop_bg.h" /* контракт с холодной половиной (pop_room.c, банк 7) */ #include "pop_tile.h" /* общие листья слоя фона (резидент): блит, тайлы, таблица */ #include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */ #include "pop_state.h" #include "pop_geom.h" #include "pop_level.h" /* pop_doorlink2 — состояние нажатия кнопки */ #include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев (см. ниже) */ #include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */ #include "pop_guard.h" /* SWORD_2_DRAWN — расширение футпринта под клинок */ /* Область, куда в этом кадре рисовался ОВЕРЛЕЙ (не футпринт fore, а * draw_other_overlay/floor_overlay/полоса потолка). Оверлей включается * только в позах виса/полёта/подъёма и рисует НЕ по прямоугольнику Kid, * поэтому heal персонажа его следы не убирает: на второй странице дабл-буфера * остаётся кадр «с оверлеем», и он мерцает через кадр. Копим bbox и * восстанавливаем фон на ОБЕИХ страницах (pop_fore_heal). */ static int ov_x0, ov_y0, ov_x1, ov_y1; static uint8_t ov_pending; /* биты страниц, где надо восстановить фон */ static void ov_mark(int x, int y, int w, int h) { if (!ov_pending) { ov_x0 = x; ov_y0 = y; ov_x1 = x + w; ov_y1 = y + h; } else { if (x < ov_x0) ov_x0 = x; if (y < ov_y0) ov_y0 = y; if (x + w > ov_x1) ov_x1 = x + w; if (y + h > ov_y1) ov_y1 = y + h; } ov_pending = 3; /* обе страницы */ } void pop_fore_heal(void) __banked { uint8_t pg = gfx_get_draw_page() & 1; if (!(ov_pending & (1 << pg))) return; pop_heal_off(ov_x0, ov_y0, ov_x1 - ov_x0, ov_y1 - ov_y0); ov_pending = (uint8_t)(ov_pending & ~(1 << pg)); /* Восстановленный прямоугольник шире персонажа и запросто накрывает * СОСЕДА — пометить область (DRAW-COST): heal идёт мимо pop_blit_b. */ pop_cd_touch(ov_x0, ov_y0 + POP_YOFF, ov_x1 - ov_x0, ov_y1 - ov_y0); } /* ---- Константные таблицы (render_room.py) -------------------------- */ static const uint8_t TBL_LINE[3] = {0, 10, 20}; static const uint8_t DOOR_FRAM_SLICE[9] = {67, 59, 58, 57, 56, 55, 54, 53, 52}; static const uint8_t SPIKES_FRAM_FORE[10] = {0, 139, 140, 141, 142, 143, 142, 140, 139, 0}; static const uint8_t LPOS[5] = {58, 41, 37, 20, 16}; static const uint8_t RPOS[4] = {52, 42, 31, 21}; /* Wall (chtab_7) id: делители/блок/марки. */ #define RES_WALL_RNDBLOCK 13 #define RES_WALL_DIVIDER1 11 #define RES_WALL_MARK_TL 14 #define RES_WALL_MARK_BL 15 #define RES_WALL_MARK_TR 16 #define RES_WALL_MARK_BR 17 /* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- * * Сид (pop_t_seed) живёт в резиденте: его ставит полная отрисовка комнаты, а * читает кэш раскладки кладки. */ #define prandom(maxv) pop_prandom(&pop_t_seed, (maxv)) /* ---- Декали-марки стен (seg008.c:2041/2056) ------------------------ */ static void draw_left_mark(uint16_t dv, int arg2, int arg1, int row, int col) { uint8_t image_id = (dv & 1) ? RES_WALL_MARK_BL : RES_WALL_MARK_TL; int lv2 = 0; int xh = POP_COL_XH[col]; int dby = 63 * row + 65; if (dv > 3) lv2 = arg1 + 6; else if (dv > 1) lv2 = arg2 + 6; pop_wall_b(image_id, (xh + ((dv == 2 || dv == 3) ? 1 : 0)) * 8 + lv2, dby - LPOS[dv]); } static void draw_right_mark(uint16_t dv, int arg1, int row, int col) { uint8_t image_id = (dv & 1) ? RES_WALL_MARK_BR : RES_WALL_MARK_TR; int a = (dv < 2) ? 24 : arg1 - 3; int xh = POP_COL_XH[col]; int dby = 63 * row + 65; pop_wall_b(image_id, (xh + ((dv > 1) ? 1 : 0)) * 8 + a, dby - RPOS[dv]); } /* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- */ /* КЭШ РАСКЛАДКИ КЛАДКИ. Серия prandom для тайла зависит только от * (комната, ряд, колонка) — пока мы в комнате, это КОНСТАНТА. А * wall_pattern зовётся из fore-слоя КАЖДЫЙ кадр, и на Z80 это самая * дорогая вещь в кадре: замер в MAME 2026-07-29 (комната 3) — 18 вызовов * pop_prandom за один fore-проход, ~10 700 тактов каждый (32-битный LCG: * __mullong ~8 000 + __moduint), то есть 193 К из 341 К всего прохода при * бюджете кадра ~430 К. Кэшируем не сид, а ГОТОВЫЕ РЕШЕНИЯ (что рисовать * и с каким смещением) — тогда в кадре не остаётся ни одного prandom. * * Порядок вызовов prandom воспроизведён один в один, включая то, что * значение метки берётся ТОЛЬКО когда сработало её условие: иначе * последовательность разъедется и кладка станет другой. which_part=0 * использует префикс той же серии, поэтому кэш общий. * * Упаковка: wp_a = mid_div | mid_off<<1 | bot_div<<4 | bot_off<<5; * wp_b = rndblk | right<<1 | right_val<<2 | left<<4 | left_val<<5. * Индекс — (row+1)*10+col, ряды −1..2. */ #define WP_N 40 static uint8_t wp_a[WP_N], wp_b[WP_N], wp_ok[WP_N]; static void wall_pattern_reset(void) /* смена комнаты — сид другой */ { uint8_t i; for (i = 0; i < WP_N; i++) wp_ok[i] = 0; } /* Выходы wall_rnd — в файловых статиках: возвращать девять значений * через указатели на Z80 дороже самой функции. */ static uint8_t wr_mid_div, wr_mid_off, wr_bot_div, wr_bot_off; static uint8_t wr_rndblk, wr_right, wr_rightv, wr_left, wr_leftv; /* Правила серии prandom по модификатору стены (bg): что вообще есть у этого * вида кладки и с какими порогами. Столбцы: * [0] есть «случайный блок» (тогда prandom(4)==0) * [1] порог условия метки СПРАВА (0 = метки справа нет, prandom не зовётся) * [2] порог значения метки справа, [3] прибавка к нему * [4] порог условия метки СЛЕВА (есть у всех четырёх), [5] порог значения * Порядок строк — по bg: 0 SWS, 1 SWW, 2 WWS, 3 WWW (seg008:1928). */ static const uint8_t WR_RULE[4][6] = { {0, 0, 0, 0, 6, 1}, /* SWS: только метка слева */ {1, 4, 3, 0, 4, 3}, /* SWW */ {0, 4, 1, 2, 4, 4}, /* WWS: значение справа со сдвигом 2 */ {1, 4, 3, 0, 4, 4}, /* WWW: полный набор */ }; static void wall_rnd(int row, int col) { /* Кэшируем только штатную сетку: колонка вне 0..9 дала бы индекс * соседнего РЯДА и чужую раскладку (тихий баг вместо промаха). */ int idx = (col >= 0 && col < 10) ? (row + 1) * 10 + col : -1; uint8_t a, b, bg; pop_rnd_t saved; if (idx >= 0 && idx < WP_N && wp_ok[idx]) { a = wp_a[idx]; b = wp_b[idx]; wr_mid_div = (uint8_t)(a & 1); wr_mid_off = (uint8_t)((a >> 1) & 7); wr_bot_div = (uint8_t)((a >> 4) & 1); wr_bot_off = (uint8_t)((a >> 5) & 7); wr_rndblk = (uint8_t)(b & 1); wr_right = (uint8_t)((b >> 1) & 1); wr_rightv = (uint8_t)((b >> 2) & 3); wr_left = (uint8_t)((b >> 4) & 1); wr_leftv = (uint8_t)((b >> 5) & 7); return; } saved = pop_t_seed; /* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху * (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */ pop_prandom_set(pop_t_seed, pop_t_room + TBL_LINE[row < 0 ? 2 : row] + col); prandom(1); /* сброс */ wr_mid_div = (uint8_t)prandom(1); wr_mid_off = (uint8_t)prandom(4); wr_bot_div = (uint8_t)prandom(1); wr_bot_off = (uint8_t)prandom(4); wr_rndblk = wr_right = wr_left = 0; wr_rightv = wr_leftv = 0; bg = pop_wall_modifier(row, col); { /* Четыре ветки bg отличались ТОЛЬКО числами в одной и той же серии * prandom, поэтому они сведены к правилу (WR_RULE). ПОРЯДОК вызовов * prandom обязан совпадать с оригиналом дословно, включая то, что * значение метки берётся ТОЛЬКО когда сработало её условие: иначе * последовательность разъедется и кладка станет другой. «Нет метки * справа» = нулевой порог условия — тогда prandom не зовётся вовсе, * ровно как в ветке SWS. */ const uint8_t *r = WR_RULE[bg]; if (r[0]) wr_rndblk = (uint8_t)(prandom(4) == 0); if (r[1] && prandom(r[1]) == 0) { wr_right = 1; wr_rightv = (uint8_t)(prandom(r[2]) + r[3]); } if (prandom(r[4]) == 0) { wr_left = 1; wr_leftv = (uint8_t)prandom(r[5]); } } pop_t_seed = saved; if (idx >= 0 && idx < WP_N) { wp_a[idx] = (uint8_t)(wr_mid_div | (wr_mid_off << 1) | (wr_bot_div << 4) | (wr_bot_off << 5)); wp_b[idx] = (uint8_t)(wr_rndblk | (wr_right << 1) | (wr_rightv << 2) | (wr_left << 4) | (wr_leftv << 5)); wp_ok[idx] = 1; } } /* ---- wall_pattern (seg008.c:1928, dungeon-ветка) ------------------- * * Куски кладки одни и те же для всех четырёх видов стены, различается ровно * НАБОР присутствующих (WP_PARTS) — раньше это были четыре ветки с * повторяющимся телом. Порядок кусков внутри вызова сохранён: случайный * блок, средний делитель, нижний делитель, метка справа, метка слева. * * which_part == 0 — только нижний делитель (слой draw_tile_bottom), 1 — всё * остальное (draw_tile_fore). У SWW нижнего делителя нет вовсе, у SWS — * ничего, кроме метки слева. */ #define WP_RND 1 #define WP_MID 2 #define WP_BOT 4 #define WP_RIGHT 8 #define WP_LEFT 16 static const uint8_t WP_PARTS[4] = { WP_LEFT, /* 0 SWS */ WP_RND | WP_MID | WP_RIGHT | WP_LEFT, /* 1 SWW */ WP_MID | WP_BOT | WP_RIGHT | WP_LEFT, /* 2 WWS */ WP_RND | WP_MID | WP_BOT | WP_RIGHT | WP_LEFT, /* 3 WWW */ }; static void wall_pattern(int row, int col, int which_part) { int xh = POP_COL_XH[col]; int dby = 63 * row + 65; uint8_t parts = WP_PARTS[pop_wall_modifier(row, col)]; wall_rnd(row, col); if (which_part) { if ((parts & WP_RND) && wr_rndblk) pop_wall_b(RES_WALL_RNDBLOCK, xh * 8, dby - 42); if (parts & WP_MID) pop_wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_mid_div), (xh + 1) * 8 + wr_mid_off, dby - 21); } if (parts & WP_BOT) pop_wall_b((uint8_t)(RES_WALL_DIVIDER1 + wr_bot_div), xh * 8 + wr_bot_off, dby); if (which_part) { if ((parts & WP_RIGHT) && wr_right) draw_right_mark(wr_rightv, wr_mid_off, row, col); if ((parts & WP_LEFT) && wr_left) draw_left_mark(wr_leftv, wr_mid_off - wr_mid_div, wr_bot_off - wr_bot_div, row, col); } } /* Решётка ворот (portcullis) из ЛЕВОЙ комнаты (seg008.c:1110). */ static void draw_gate_back(uint8_t modl, int xh, int dby, int dmy) { int x = xh * 8; int gate_top_y = dby - 62; int openness = (((modl > 188) ? 188 : modl) >> 2) + 1; int gate_bot_y = dmy - openness; int yb, gate_frame; if (gate_bot_y + 12 < dmy) pop_env_b(50, x, gate_bot_y); else pop_env_b(51, x, gate_bot_y - 2); yb = gate_bot_y - 12; if (yb < 192) { while (yb >= 0 && yb > 7 && (yb - 7) > gate_top_y) { pop_env_b(52, x, yb); yb -= 8; } } gate_frame = yb - gate_top_y + 1; if (gate_frame > 0 && gate_frame < 9) pop_env_b(DOOR_FRAM_SLICE[gate_frame], x, yb); } /* ---- Точки входа для ХОЛОДНОЙ половины (pop_room.c, банк 7) ---------- * * Тонкие обёртки, а не пометка самих тел `__banked`: трамплин выбирается * ОБЪЯВЛЕНИЕМ, поэтому пометка тела заставила бы платить 654 такта и * ГОРЯЧИХ вызывающих — fore-проход зовёт wall_pattern до девяти раз за кадр. * С обёрткой горячий путь идёт прямым call, а трамплин достаётся только * холодному (полная отрисовка комнаты: ~40 вызовов, 26 000 тактов РАЗОВО на * вход в комнату). См. _pop_bg.h и memory/sdcc_banked_call_rules. */ void pop_wall_pattern_b(int row, int col, int which_part) __banked { wall_pattern(row, col, which_part); } void pop_wall_pattern_reset_b(void) __banked { wall_pattern_reset(); } void pop_gate_back_b(uint8_t modl, int xh, int dby, int dmy) __banked { draw_gate_back(modl, xh, dby, dmy); } /* ---- fore-over-Kid (порт seg003 redraw_at_char + seg008 set_char_collision) -- * Передний слой (fore_id = foretable-кусок) тайлов ФУТПРИНТА спрайта Kid * — рядов top..bottom × колонок left..right (<= 2×2 = 4 тайла) — рисуется * ПОВЕРХ Kid: то, что по изометрии перед ним (передние грани колонн/ворот/ * большой колонны/щебня). Стены и факелы имеют fore_id=0 → остаются сзади. * Рисуем в GFX_BANK_SPRITE (видео-ОЗУ, 0xFF прозрачен); heal персонажа восстановит * из теневого фона (fore в нём уже запечён pop_room_draw). obj_x — ЛОГИЧЕСКАЯ * X кадра Kid (до ×8/7), obj_y — низ спрайта, w/h — размер, dir — направление. */ static int8_t col_from_x(int xpos) /* get_tile_div_mod: колонка (58/14) */ { int x = xpos - 58; int xh = x / 14; if (x % 14 < 0) xh--; /* округление вниз для отрицательных */ return (int8_t)xh; } #define y_to_row(y) pop_y_to_row((int16_t)(y)) /* общий (pop_geom) */ static void fore_only_tile(int row, int col) { /* Нажатая кнопка → пол/stuck (get_tile_to_draw), иначе поверх Kid лезет * передняя грань КНОПКИ. */ uint8_t code = pop_tile_code_drawn(row, col); const piece *t; t = &pop_tile_table[code]; int x = POP_COL_XH[col] * 8; int dmy = 63 * row + 62; if (code == 20) { pop_wall_b(POP_WALL_FRAM_MAIN[pop_wall_modifier(row, col)], x, dmy); wall_pattern(row, col, 1); return; } if (code == 2) { /* пики: fore-кадр */ uint8_t sm = pop_tile_mod(row, col); uint8_t sf = (sm & 0x80) ? 5 : sm; if (sf > 0 && sf < 10 && SPIKES_FRAM_FORE[sf]) pop_env_b(SPIKES_FRAM_FORE[sf], x, dmy - 2); } if (code == 0x12) { /* чомпер: передний слой челюстей */ uint8_t cm = pop_tile_mod(row, col); uint8_t pose = pop_chomp_pose(cm); pop_env_b(POP_CHOMP_FRAM_FOR[pose], x, dmy); if (cm & 0x80) /* кровь поверх переднего слоя */ pop_env_b((uint8_t)(pose + 119), x + 8, dmy - 6); } if (code == 10) { /* зелье: склянка (пузырёк — динамика) */ pop_potion_flask(row, col, pop_tile_mod(row, col)); } else if (t->fore_id) { pop_fore_b(t->fore_id, t->fore_x * 8 + x, t->fore_y + dmy); } } /* Полоса кладки У ПОТОЛКА поверх Kid — порт redraw_needed_above (seg008:0221). * Там draw_tile_bottom(1) и draw_loose(1): аргумент 1 = «добавить ещё и в * FOREtable», т.е. нижняя грань потолка и кадр плиты-потолка рисуются ПОВЕРХ * персонажа (иначе руки прыгающего Kid лезут на кромку потолка). Всё * остальное из этой функции (floorright, anim_topright, right) идёт в * backtable — под персонажем. */ static void ceil_over_kid_tile(int col) { uint8_t code = pop_tile_code(-1, col); const piece *t = &pop_tile_table[code]; int x = POP_COL_XH[col] * 8; int dby = 63 * (-1) + 65; /* = 2 */ if (code == 20) { /* draw_tile_bottom: стена */ pop_wall_b(POP_WALL_FRAM_BOTTOM[pop_wall_modifier(-1, col)], x, dby); wall_pattern(-1, col, 0); } else if (t->bottom_id) { pop_env_b(t->bottom_id, x, dby); } if (code == 11) /* draw_loose(1): плита-потолок */ pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(pop_ceil_modif[col])], x, dby); fore_only_tile(-1, col); /* draw_tile_fore */ } /* Пересекается ли габарит тайла с окном fore-слоя. Шаг тайла — 32 px * (POP_COL_XH[col]*8 = col*32), куски выходят вправо максимум на +8 (дивайдеры * и метки рисуются в ячейке правого соседа) и вверх на 42+высота кадра. * Даёт отсев ЦЕЛОГО тайла ДО обращения к атласу: спрайт 27..40 px шириной * задевает не все тайлы футпринта, а платили за каждый (atlas_image + * gfx_w0_map + разбор заголовка на КАЖДЫЙ кусок). */ static uint8_t tile_in_fclip(int row, int col) { int x0, y0; if (!pop_t_fclip_on) return 1; x0 = POP_COL_XH[col] * 8; y0 = POP_YOFF + 63 * row; return (uint8_t)(x0 < pop_t_fclip_x1 && x0 + 40 > pop_t_fclip_x0 && y0 - 8 < pop_t_fclip_y1 && y0 + 70 > pop_t_fclip_y0); } static void fore_tile(int row, int col) /* передний слой одного тайла */ { if (!tile_in_fclip(row, col)) return; /* draw_loose (seg008:0A38) — единственный кусок ТАЙЛА, который оригинал * кладёт В ОБЕ таблицы БЕЗУСЛОВНО: нижняя грань loose-плиты идёт и в * backtable, и в foretable. Значит передняя грань плиты рисуется ПОВЕРХ * персонажа ВСЕГДА, а не только у потолка (ceil_over_kid_tile). Верх * плиты (draw_tile_base) остаётся в backtable — поэтому голова и руки * висящего/подтягивающегося Kid видны НАД плитой, а тело уходит ЗА неё. * Без этого Kid лез на плиту «с переднего края» (комната 12: вис на * кромке loose над дырой от соседней упавшей плиты). */ if (row >= 0 && pop_tile_code_drawn(row, col) == 11) pop_env_b(POP_LOOSE_FRAM_BOTTOM[pop_loose_frame(pop_loose_modif[row * 10 + col])], POP_COL_XH[col] * 8, 63 * row + 65); /* draw_tile_fore (seg008:690) — ТОЛЬКО add_foretable-часть. Раньше здесь * рисовалась ещё и передняя кромка пола (bottom_id) «чтобы перекрывала * Kid», но в оригинале bottom идёт через draw_tile_bottom в backtable * (под персонажем), а поверх него попадает лишь при draw_other_overlay. * Из-за лишнего bottom у основания колонны проскакивала «тень». */ fore_only_tile(row, col); } /* Определение общих с pop_room.c obj-координат (объявление — _pop_bg.h). */ int8_t pop_bg_obj_col, pop_bg_obj_row; /* Пол-оверлей при подъёме (draw_floor_overlay, seg008:1457): когда Kid * подтягивается на floor-подобный тайл назначения (floor/pillar/stuck/torch), * а тайл СЛЕВА пуст (т.е. кромка), передний край пола перекрывает нижнюю * часть Kid — иначе видна часть, что должна быть за полом. Кадр подъёма * 137..144 индексирует floor_left_overlay. */ static const uint8_t FLOOR_LEFT_OVERLAY[8] = {32, 151, 151, 150, 150, 151, 32, 32}; static void other_overlay_tile(int row, int col); /* fwd (draw_floor_overlay -> else) */ static void climb_overlay_tile(int row, int col, uint8_t fidx) { /* тип — ПОДСТАВЛЕННЫЙ (нажатая кнопка = пол): иначе кромка, за которую * Kid спускается/подтягивается, закрашивается целым тайлом */ uint8_t code; if (!tile_in_fclip(row, col)) return; code = pop_tile_code_drawn(row, col); int x, dmy, dby; const piece *t; if (pop_tile_code(row, col - 1) != 0) return; /* слева не пусто → не кромка */ if (code != 1 && code != 3 && code != 5 && code != 19) { /* seg008:1E3A draw_floor_overlay: floor-оверлей рисуется только для * floor/pillar/stuck/torch, а для ОСТАЛЬНЫХ тайлов вызывается * draw_other_overlay() — иначе кнопка (opener/closer), за кромку * которой Kid спускается, не перекрывает его. */ other_overlay_tile(row, col); return; } x = POP_COL_XH[col] * 8; dmy = 63 * row + 62; dby = dmy + 3; t = &pop_tile_table[code]; ov_mark(x, 63 * row + 2, 64, 64); pop_env_b(FLOOR_LEFT_OVERLAY[fidx], x, dmy + (code == 5 ? 1 : 0)); if (t->bottom_id) pop_env_b(t->bottom_id, x, dby); /* draw_tile_bottom */ } /* draw_tile2 (seg008:1F48) — но ТОЛЬКО та его часть, что реально может лечь * ПОВЕРХ персонажа. Ключ (подтверждено трассой оригинала): draw_tile_right, * draw_tile_anim_right и draw_loose добавляют спрайты через add_backtable * НАПРЯМУЮ, игнорируя ptr_add_table — значит правая грань левого соседа * («шахматка» столба 93, blueline, грани пик/loose соседа, кадр loose) * ВСЕГДА рисуется ПОД персонажем. Через ptr_add_table (→ midtable при * draw_other_overlay) идут только: «floor B» 42 при левом соседе-поле, * draw_tile_base, draw_tile_anim (свои пики) и draw_tile_bottom. */ static void overlay_mid_tile(int row, int col) { uint8_t code = pop_tile_code_drawn(row, col); uint8_t lcode = pop_tile_code_drawn(row, col - 1); const piece *t; int x = POP_COL_XH[col] * 8; int dby = 63 * row + 65; int dmy = dby - 3; t = &pop_tile_table[code]; ov_mark(x, 63 * row + 2, 64, 64); /* тайл + правый сосед (грани) */ if (code != 20 && lcode == 1) /* draw_tile_right: только 42 */ pop_env_b(42, x, pop_tile_table[1].right_y + dmy); { /* draw_tile_base (seg008:0A8E) — ЦЕЛИКОМ, вместе с подстановками id: * у loose верх плиты берётся из loose_fram_left (в pop_tile_table base_id=0), * у opener'а без пола слева — 148. Раньше здесь стоял голый base_id, и * верх loose-плиты в оверлей не попадал: передняя грань ложилась поверх * Kid (foretable), а сама плита — нет, и подтягивающийся Kid рисовался * поверх её верхней плоскости (комната 12). */ uint8_t base_id = t->base_id; if (code == 11 && row >= 0) base_id = POP_LOOSE_FRAM_LEFT[pop_loose_frame(pop_loose_modif[row * 10 + col])]; else if (code == 0x0F && lcode == 0) base_id = 148; if (base_id) pop_env_b(base_id, x, t->base_y + dmy); } if (code == 2) { /* draw_tile_anim: свои пики */ uint8_t sm = pop_tile_mod(row, col); uint8_t sf = (sm & 0x80) ? 5 : sm; if (sf > 0 && sf < 10 && POP_SPIKES_FRAM_LEFT[sf]) pop_env_b(POP_SPIKES_FRAM_LEFT[sf], x, dmy - 2); } if (code == 20) { /* draw_tile_bottom */ uint8_t wmod = pop_wall_modifier(row, col); pop_wall_b(POP_WALL_FRAM_BOTTOM[wmod], x, dby); wall_pattern(row, col, 0); } else if (t->bottom_id) { pop_env_b(t->bottom_id, x, dby); } } /* draw_tile_fore (seg008:690) — ТОЛЬКО передний слой (add_foretable): он * рисуется после всего и поверх персонажа. Нужен для полосы кладки у * потолка: в redraw_needed_above всё остальное идёт в backtable (под Kid). */ /* draw_other_overlay (seg008:1492): для поз виса/прыжка/падения/захвата на * КРОМКЕ (сосед слева пуст, либо тайл через один слева пуст) — перерисовать * ВЕСЬ тайл поверх Kid. Тело пола перекрывает висящего/прыгающего/падающего * ПОД ним. Срабатывает только в этих позах (см. redraw_at_char2 ниже) — * поэтому при стоянии/залезании ложной окклюзии нет. * * ПОРЯДОК (redraw_needed_tiles, seg008:1B06 + redraw_needed, seg008:0211): * тайлы обходятся ряды 2,1,0, внутри ряда колонки 0..9; в тайле сначала * оверлей (redraw_frames2 -> midtable), потом ОБЪЕКТЫ этого тайла. Kid — тоже * запись midtable, вставляемая при обработке СВОЕГО тайла (obj_row/obj_col). * Значит оверлей ложится ПОВЕРХ Kid только у тайлов, идущих в обходе ПОЗЖЕ * его собственного; всё, что раньше, — под ним. У нас списков нет (рисуем * сразу), поэтому порядок эмулируется явной проверкой. */ static void other_overlay_tile(int row, int col) { uint8_t code, left; if (!tile_in_fclip(row, col)) return; code = pop_tile_code(row, col); /* Пустой тайл НЕ окклюдирует: у него нет тела пола/стены, а draw_tile для * него рисует лишь ФОНОВУЮ сетку (BLUELINE «силуэт кладки» — правая грань * пустого соседа). В оригинале она уходит в y-сортируемый midtable и * ложится ПОЗАДИ Kid; у нас draw_tile кладёт её прямо поверх → сетка на * висящем/падающем Kid. Фон запечён один раз в pop_room_draw — над * спрайтом его перерисовывать не нужно. */ if (code == 0) return; /* Тайл идёт в обходе РАНЬШЕ тайла Kid → его оверлей лёг бы ПОД спрайт. */ if (row > pop_bg_obj_row || (row == pop_bg_obj_row && col <= pop_bg_obj_col)) return; left = pop_tile_code(row, col - 1); if (left == 0) { overlay_mid_tile(row, col); return; } /* ветка 1: сосед слева пуст */ if (col > 0 && pop_tile_code(row, col - 2) == 0) { /* Ветка 2 (тайл ЧЕРЕЗ ОДИН слева пуст): в оригинале она ставит * tile_object_redraw[tilepos]=0xFF, т.е. объекты тайлов tilepos−1 и * tilepos рисуются СРАЗУ ПОСЛЕ оверлея — Kid возвращается наверх. * Без этого правая грань соседнего столба (кадр 93) остаётся поверх * прыгающего Kid (баг «тень на дальнем столбе поверх Кида»). */ if (row == pop_bg_obj_row && (col == pop_bg_obj_col + 1)) return; overlay_mid_tile(row, col); } } /* Футпринт персонажа в тайлах (порт set_char_collision, seg006:0723). * Общий счёт для любого Char (Кид, страж, скелет) — в оригинале это один * код на всех. Результат — в файловых статиках, а не через 6 указателей: на Z80 такой * возврат стоил бы дороже самой функции. */ static int8_t fp_cL, fp_cR, fp_cLraw, fp_rT, fp_rB, fp_rTraw; static void char_footprint(int obj_x, int obj_y, uint16_t w, uint16_t h, int8_t dir, uint8_t sword) { int wh = (int)((w + 1) >> 1); /* char_width_half */ int xl = obj_x / 2 + 58; /* char_x_left (58-база) */ int xr; int8_t cL, cR, cLraw, rT, rB, rTraw; if (dir >= 0) xl -= wh; /* лицом вправо: левый край внутрь */ xr = xl + wh; cLraw = col_from_x(xl); /* до клампа: <0 = футпринт за левым швом */ cL = cLraw; if (cL < 0) cL = 0; else if (cL > 9) cL = 9; cR = col_from_x(xr); if (cR < 0) cR = 0; else if (cR > 9) cR = 9; /* redraw_at_char (seg003:0430): «If char is holding sword, it makes * redraw-area bigger» — КЛИНОК торчит на колонку дальше габарита кадра, * и без этого расширения передние грани в той колонке не * перерисовываются: меч остаётся поверх столба, а его след — на фоне. */ if (sword >= SWORD_2_DRAWN) { if (dir >= 0) { if (++cR > 9) cR = 9; } else { if (--cL < 0) cL = 0; } } /* Порт set_char_collision (seg006:0723): нижний ряд = y_to_row_mod4(obj_y), * причём обёртка -1 (Kid НИЖЕ комнаты — climbdown/провал, obj_y в полосе * «ряд 3») трактуется как ряд 3, НЕ ряд 0. Верхний ряд считается от * char_top_y с ресетом в 0, если весь спрайт ниже комнаты (>=192). */ { int top_y = obj_y - (int)h + 1; if (top_y >= 192) top_y = 0; rT = y_to_row(top_y); rB = y_to_row(obj_y); if (rB == -1) rB = 3; /* обёртка → ряд 3 (ниже комнаты) */ rTraw = rT; /* −1 = верх спрайта в полосе у потолка */ } /* single-room roomtest: ряд 3 (комната снизу) в этом виде не рисуется → * окклюдер спереди = ряд 2 (передняя грань пола, за которой нога). */ if (rB < 0) rB = 0; else if (rB > 2) rB = 2; if (rT < 0) rT = 0; else if (rT > 2) rT = 2; /* Голова/руки почти всегда торчат в ряд НАД опорным (Kid выше тайла). * y_to_row(верх спрайта) из-за +60-сдвига может схлопнуться в опорный * ряд (rT==rB) — тогда ряд над ним не попадёт в перебор и его окклюдер * (нижняя грань стены / кромка пола) не перерисуется поверх Kid. Форсим * включение ряда rB-1. */ if (rB > 0 && rT >= rB) rT = rB - 1; fp_cL = cL; fp_cR = cR; fp_cLraw = cLraw; fp_rT = rT; fp_rB = rB; fp_rTraw = rTraw; } /* Передние грани тайлов и оверлеи ПОВЕРХ персонажа — ОДИН проход на всех * Char. В оригинале add_kid_to_objtable (seg008:22F0) и * add_guard_to_objtable (seg008:2324) имеют ИДЕНТИЧНОЕ тело и различаются * только окном (loadkid/loadshad), типом объекта и спецкейсом тени у * зеркала; redraw_at_char / redraw_at_char2 (seg003:0576/0645) гейтов по * charid не имеют вовсе. * * До DRAW-CHAR тут стояли ДВА прохода — свой у Кида, свой у соперника, — и * расхождение уже стоило бага: падающий скелет рисовался поверх верхней * грани пола соседней колонки, потому что порт redraw_at_char2 был только у * Кида. Тем же путём пошли бы тень (ур. 4/5/6/12), визирь (13) и толстяк * (12) — поэтому проход сведён к одному. * * obj_x — ЛОГИЧЕСКАЯ X кадра (до ×8/7), obj_y — низ спрайта, w/h — размер * кадра; всё остальное берётся из самого персонажа (ch). Окно fore-клипа к * этому моменту уже стоит: его задаёт тот, кто нарисовал спрайт. */ void pop_fore_over_char(const pop_char_t *ch, int obj_x, int obj_y, uint16_t w, uint16_t h) __banked { int8_t cL, cR, cLraw, rT, rB, rTraw, r, c; uint8_t frame = ch->frame, action = ch->action; pop_t_fclip_on = 1; /* окно = прямоугольник спрайта */ char_footprint(obj_x, obj_y, w, h, ch->direction, ch->sword); /* Футпринт считается по КАДРУ персонажа (char_x_left/right, seg006:1021) * и потому УЖЕ спрайта, а рядом с ним рисуются ещё клинок и «брызги» * удара — они уходят ВПЕРЁД-ВВЕРХ и запросто попадают в соседнюю * колонку или ряд выше. Оригинал этим не страдает: он помечает тайлы * на перерисовку и рисует foretable целиком, а у нас проход сужен окном * ради скорости (см. pop_fore_layer_cost). Поэтому перебор расширяется * САМИМ ОКНОМ fore-клипа — оно и есть объединение «персонаж + клинок + * брызги», считать его второй раз не надо. */ if (pop_t_fclip_x1 > pop_t_fclip_x0 && pop_t_fclip_y1 > pop_t_fclip_y0) { int8_t c2, r2; for (c2 = 0; c2 <= 9; c2++) { int x0 = POP_COL_XH[c2] * 8; if (x0 + 40 > pop_t_fclip_x0 && x0 < pop_t_fclip_x1) { if (c2 < fp_cL) fp_cL = c2; if (c2 > fp_cR) fp_cR = c2; } } for (r2 = 0; r2 <= 2; r2++) { int y0 = POP_YOFF + 63 * r2; if (y0 + 70 > pop_t_fclip_y0 && y0 - 8 < pop_t_fclip_y1) { if (r2 < fp_rT) fp_rT = r2; if (r2 > fp_rB) fp_rB = r2; } } } cL = fp_cL; cR = fp_cR; cLraw = fp_cLraw; rT = fp_rT; rB = fp_rB; rTraw = fp_rTraw; /* set_objtile_at_char (seg006:1833): тайл, при обработке которого спрайт * попадает в midtable — база порядка окклюзии в other_overlay_tile. * run_jump (action 1) — нижний ряд/левая колонка габарита; вис/полёт/ * подъём — колонка на одну ЛЕВЕЕ curr_col. */ pop_bg_obj_row = ch->curr_row; pop_bg_obj_col = ch->curr_col; if (action == 1) { pop_bg_obj_row = rB; pop_bg_obj_col = cL; } if ((frame >= 135 && frame < 149) || action == 2 || action == 3 || action == 4 || action == 6) pop_bg_obj_col--; gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, heal восстановит */ /* Шовная окклюзия ворот (порт redraw_at_char для шва): футпринт зашёл за * ЛЕВЫЙ шов (cLraw<0) и у соседа слева в этом ряду решётка ворот (код 4) — * перерисовать её бары ПОВЕРХ персонажа. Иначе он, стоя за закрытыми * воротами (col-1), рисуется поверх решётки. Бары = draw_gate_back по * живому modif соседа (pop_t_lbg = lcol_bg, openness). */ if (cLraw < 0 && pop_t_lfg) for (r = rT; r <= rB; r++) if ((pop_t_lfg[r] & 0x1F) == 4) { int dby = 63 * r + 65; draw_gate_back(pop_t_lbg ? pop_t_lbg[r] : 0, POP_COL_XH[0], dby, dby - 3); } if (frame >= 137 && frame <= 144) { /* подъём: пол назначения поверх него */ uint8_t fidx = (uint8_t)(frame - 137); for (r = rT; r <= rB; r++) for (c = cL; c <= cR; c++) climb_overlay_tile(r, c, fidx); } else { /* redraw_at_char2 (seg003:449) — путь set_redraw2/draw_other_overlay: * тело пола на кромке перекрывает Kid в позах захвата (78-79), виса * (action 2/6), полёта (3/4) и старта падения (bumped 102-106). * Верхний ряд — всегда; опорный — кроме action 2 (hang_climb). */ uint8_t do_ov = 0; if (frame >= 78 && frame < 80) do_ov = 1; /* захват */ else if (action == 2 || action == 3 || action == 4 || action == 6) do_ov = 1; else if (action == 5 && frame >= 102 && frame <= 106) do_ov = 1; else if (frame == 135 || frame == 136) do_ov = 1; /* начало подъёма (SEQ_UP */ /* на 141 — до него Kid ещё */ /* в ряду 1 ПОД полом [0,3]) */ if (do_ov) { for (c = cR; c >= cL; c--) { if (action != 2) other_overlay_tile(rB, c); /* опорный ряд */ if (rT != rB) other_overlay_tile(rT, c); /* верхний ряд */ } } } /* draw_tile_fore (seg008:690) — foretable, рисуется ПОСЛЕ midtable, т.е. * ПОСЛЕ оверлеев выше: передняя грань колонны перекрывает скос пола * (floor_left_overlay), а не наоборот. Раньше fore шёл первым, и тёмный * скос ложился поверх колонны — «тень у её основания». */ for (r = rT; r <= rB; r++) for (c = cL; c <= cR; c++) fore_tile(r, c); /* redraw_needed_above (seg008:0221): полоса кладки У ПОТОЛКА (ряд −1 = * ряд 2 комнаты сверху). В оригинале она рисуется в самом конце обхода * тайлов — ПОСЛЕ объектов, т.е. поверх персонажа (redraw_frames_above, * куда set_redraw* уводит отрицательные tilepos). Без этого руки Kid, * прыгающего в ряду 0, лезли поверх потолка. */ if (rTraw < 0) { pop_t_clip_top = POP_YOFF; for (c = cL; c <= cR; c++) { ov_mark(POP_COL_XH[c] * 8, 0, 64, CEIL_BAND_H); ceil_over_kid_tile(c); } pop_t_clip_top = 0; } gfx_set_bank(GFX_BANK_NORMAL); pop_t_fclip_on = 0; }