Files
Sprinter-SDCC/applications/PoP/roomtest/pop_bg.c
T
snark13 dc0bd47368 L3-CHOMP: чомперы — анимация, отрисовка и смерть в челюстях
Порт SDLPoP:
  animate_chomper / start_chompers / start_anim_chomper /
  next_chomper_timing (seg007) -> pop_trob.c;
  draw_tile_anim + draw_tile_fore, ветка tiles_18_chomper (seg008) ->
  pop_room.c (низ/кровь/верх, backtable) и pop_bg.c (передний слой);
  check_chomped_kid / check_chomped_guard / chomped (seg004) -> pop_map.c.

Состояние — в room_modif, как у пик и ворот: младшие 7 бит фаза 1..N,
старший бит «перемололо кого-то» (кровь остаётся на тайле навсегда).
Номер позы chomper_fram1 и передние куски лежат в РЕЗИДЕНТЕ (pop_tile.c):
их читают обе половины слоя фона, а const-таблица банка из чужого банка
не видна.

start_chompers зовётся там же, где в оригинале: SEQ_UP/SEQ_DOWN в play_seq
(pop_kid.c), start_fall и land (pop_map.c), вход в комнату (roomtest.c).
Поэтому чомперы щёлкают только пока персонаж в ИХ ряду — так в оригинале.

check_chomped_guard у оригинала отдельное тело (страж не проходит через
check_collisions).  У нас та же формула уже есть в get_row_collision_data,
поэтому флаги ряда считаются во ВРЕМЕННЫЙ массив: coll_curr/above/below —
это кадр Кида, из них move_coll_to_prev берёт прошлые флаги, затирание
сломало бы ему бамп.

Период смыкания — POP_CHOMPER_SPEED в pop_tune.h (15, как в оригинале).

Заодно: устаревшая заглушка pop_fore_set_clip в tests-host была __banked,
хотя функция давно переехала в резидент — всплыло при пересборке.

Ассеты уже были упакованы (pop_pack_bg.py, 2026-08-07).  Банк 6: 20.0 %,
банк 7: 39.7 %, банк 3: 53.8 %.  tests-host зелёные (5 наборов).
Зубья в MAME рисуются; анимация и смерть — на ручной приёмке.
2026-08-08 19:05:16 +03:00

714 lines
42 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include <fcntl.h>
#include <unistd.h>
#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;
}