361c9d9f78
Спецсобытие уровня 8 (SDLPoP seg003:0545/0ABA, seg002:07EB): когда дверь уровня открыта, а Кид остаётся в комнате 16, через 150 кадров приходит мышь, пробегает справа налево по верхнему ряду, наступает на кнопку (0,7) — решётка (0,3) открывается — и убегает. Ассетов не потребовалось: кадры 186..188 идут по таблице Кида, их спрайты (images 130..132) уже лежат в kid16.atl, отрисовка подхватывает мышь сама. - guards.c: pop_check_mouse, autocontrol_mouse, ветки мыши в autocontrol_opponent / play_guard / check_can_guard_see_kid; - pop_leveldoor_open стал word, как в оригинале (в него же считается задержка — байт переполнился бы через 255 кадров); - leave_guard больше не записывает тень и мышь в данные уровня (seg002:02F5); - полосы HP у мыши нет (seg000:1159); - tests-host/t_mouse: 17 проверок, единственный набор с guards.c. Разгрузка резидента (куча в huge упала до 879 Б): pop_start_level, find_start_level_door, чит-навигация по комнатам и лейбл номера уехали в roomtest_cold.c (--bank 8, n_banks = 8). _CODE 25653 -> 24825, куча -> 1707 Б; кадровый путь не тронут. Проверено: tests-host 6/6, make size-check OK, живьём в MAME (мышь появилась, нажала кнопку, решётка поехала вверх, мышь ушла). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
829 lines
50 KiB
C
829 lines
50 KiB
C
/*
|
||
* pop_cdraw.c — ОТРИСОВКА ПЕРСОНАЖЕЙ (Кид, страж, скелет; дальше тень,
|
||
* визирь, толстяк) + полоса HP. Банк 4.
|
||
*
|
||
* Одно тело на всех Char — порт add_kid_to_objtable/add_guard_to_objtable
|
||
* (seg008:22F0/2324), различия сведены к слоту; мотивация и порядок вызовов
|
||
* в кадре — в шапке pop_cdraw.h. До DRAW-CHAR это были kid_draw в
|
||
* резиденте и pop_guard_draw здесь же, каждый со своей математикой кадра,
|
||
* своими heal-прямоугольниками и своим проходом окклюзии.
|
||
*
|
||
* Почему банк, а не резидент: зовёт только главный цикл (раз в кадр на
|
||
* персонажа), горячих банк→банк переходов отсюда нет, а резидентное
|
||
* пространство W1+W2 делят код и данные — килобайт кода, уехавший в банк,
|
||
* это килобайт для кучи. Писучие статики банкового модуля (--dataseg не
|
||
* задаём) остаются в обычном _DATA, то есть в W2: pop_cd читают и физика, и
|
||
* фон из СВОИХ банков — без трамплина, обычной адресацией.
|
||
*/
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include <sprite.h>
|
||
#include "pop_guard.h"
|
||
#include "pop_kid.h" /* атласы/кадр Кида, pop_sword_draw, окна Char */
|
||
#include "pop_cdraw.h"
|
||
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char, pop_fore_set_clip */
|
||
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||
#include "pop_map.h" /* hitp_curr/hitp_max — HP Кида; clip_char */
|
||
#include "pop_level.h" /* тип соперника уровня (набор спрайтов) */
|
||
#include "pop_tile.h" /* pop_heal_off — клип тени/отражения через фон */
|
||
#include "kid_data.h"
|
||
#include "pop_guard_pal.h" /* таблица палитр стражей (сгенерена упаковщиком) */
|
||
|
||
/* Цвет стража (set_chtab_palette, seg003:257). Оригинал держит ОДИН набор
|
||
* спрайтов и подменяет 16 цветов палитры chtab_5 по level.guards_color —
|
||
* так на уровне 2 стражи комнат 4/7/11/15/24 разного цвета (1/3/1/1/6).
|
||
* Полоса HP стража рисуется тем же атласом, поэтому меняется заодно.
|
||
* color = 0 — палитру не трогаем (оригинал в этом случае set_chtab_palette
|
||
* не зовёт: тип уровня не «обычный страж»).
|
||
* Грузим В ОБЕ палитры: у страниц дабл-буфера они раздельные. */
|
||
void pop_guard_set_palette(uint8_t color) __banked
|
||
{
|
||
/* Таблица — rodata ЭТОГО модуля, то есть страница банка (0xC000+), а
|
||
* gfx_pal_load отдаёт указатель в BIOS ($A4 через rst #0x08). BIOS
|
||
* читает только #4000-#BFFF (см. корневой CLAUDE.md), поэтому копируем
|
||
* запись в локальный буфер — стек гарантированно в W2. Без копии BIOS
|
||
* забирал мусор с чужой страницы и страж становился невидимым. */
|
||
uint8_t buf[64];
|
||
const uint8_t *p;
|
||
uint8_t i;
|
||
if (color == 0 || color > POP_GUARD_NPAL) return;
|
||
p = pop_guard_pal[color - 1];
|
||
for (i = 0; i < 64; i++) buf[i] = p[i];
|
||
gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf);
|
||
gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf);
|
||
}
|
||
|
||
/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */
|
||
#define GUARD_PAGES 5
|
||
static atlas_t gp[GUARD_PAGES];
|
||
static uint8_t g_ok;
|
||
|
||
/* Состояние отрисовки по слотам (см. pop_cdraw.h): heal-прямоугольники по
|
||
* страницам дабл-буфера, габарит кадра и окно fore-клипа. */
|
||
pop_cdraw_t pop_cd[POP_CH_N];
|
||
|
||
/* «Брызги» урона Кида — спрайт chtab_2 id 218 (res619.png, в наших атласах
|
||
* страница 27). У соперника это image 1 его собственного набора. */
|
||
#define KID_SPLASH_IMAGE 218
|
||
|
||
/* Набор спрайтов соперника выбирает ТИП УРОВНЯ (load_lev_spr, seg000:1092):
|
||
* tbl_guard_type = {0 guard, 1 fat, 2 skeleton, 3 vizier, 4 shadow, −1 нет}.
|
||
* У нас пока два набора: обычный страж (уровни 1-2) и скелет (уровень 3);
|
||
* у скелета и кадров меньше (28 против 34), поэтому пятую страницу он не
|
||
* грузит. Уровень БЕЗ соперников (type < 0) не грузит ничего. */
|
||
int pop_guard_load(void) __banked
|
||
{
|
||
static const char *const guard_names[GUARD_PAGES] = {
|
||
"GUARD\\g0.atl", "GUARD\\g1.atl", "GUARD\\g2.atl",
|
||
"GUARD\\g3.atl", "GUARD\\g4.atl"
|
||
};
|
||
static const char *const skel_names[GUARD_PAGES] = {
|
||
"SKEL\\g0.atl", "SKEL\\g1.atl", "SKEL\\g2.atl",
|
||
"SKEL\\g3.atl", 0
|
||
};
|
||
const char *const *names;
|
||
int8_t type = pop_level_guard_type(pop_current_level);
|
||
uint8_t i;
|
||
if (type < 0) return 0; /* соперников на уровне нет */
|
||
names = (type == 2) ? skel_names : guard_names;
|
||
for (i = 0; i < GUARD_PAGES; i++) {
|
||
if (names[i] == 0) break;
|
||
if (atlas_load(&gp[i], names[i]) != 0) return -1;
|
||
}
|
||
g_ok = 1;
|
||
/* Цвет: у скелета он СВОЙ (палитра из его набора), у стража подменяется
|
||
* из res10.bin по guards_color — это делает pop_guard_set_palette при
|
||
* входе в комнату (seg002:186: у не-guard curr_guard_color = 0). */
|
||
if (type == 2) {
|
||
uint8_t buf[64], k;
|
||
for (k = 0; k < 64; k++) buf[k] = pop_skel_pal[k]; /* BIOS: буфер в W2 */
|
||
gfx_pal_load(0, POP_GUARD_PAL_BASE, 16, buf);
|
||
gfx_pal_load(1, POP_GUARD_PAL_BASE, 16, buf);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
void pop_guard_free(void) __banked
|
||
{
|
||
uint8_t i;
|
||
if (!g_ok) return;
|
||
for (i = 0; i < GUARD_PAGES; i++) atlas_free(&gp[i]);
|
||
g_ok = 0;
|
||
}
|
||
|
||
/* ---- calc_screen_x_coord (seg008:2602) БЕЗ деления ------------------- *
|
||
*
|
||
* Логическая X (280 шир = 28px/тайл) -> экранная (320 = 32px/тайл): ×8/7.
|
||
* SDLPoP пишет это как `logical_x*320/280`, у нас SDCC разворачивал в
|
||
* `__divsint` — 2 400 тактов на вызов, и зовётся оно на КАЖДОГО персонажа
|
||
* КАЖДЫЙ кадр (последнее деление, остававшееся в горячем пути).
|
||
*
|
||
* ХРАНИМ x/7 БАЙТОМ, а не готовое x*8/7 словом. Тождество: 8x/7 == x + x/7
|
||
* (7x делится нацело, поэтому усечение обеих частей совпадает — и для
|
||
* отрицательных с трункейтом к нулю тоже). Оба варианта собраны и
|
||
* посчитаны по сгенерированному asm:
|
||
*
|
||
* int16_t готовое: ... add hl,hl / add hl,de / ld e,(hl) / inc hl /
|
||
* ld d,(hl) = 132 такта, 2 304 байта
|
||
* int8_t x/7: ... add hl,bc / ld a,(hl) / ld l,a / rlca / sbc a,a /
|
||
* ld h,a / add hl,de = 131 такт, 1 152 байта
|
||
*
|
||
* Расширение знака и 16-битное сложение стоят РОВНО столько же, сколько
|
||
* лишний `add hl,hl` при двухбайтном индексе, а чтение из памяти на одно
|
||
* меньше — под wait-state'ами Sprinter (такт ≈ 2,4× номинала на обращениях
|
||
* к ОЗУ) байтовый вариант даже чуть выгоднее номинала. Плюс килобайт
|
||
* банка, который в pop_cdraw ещё понадобится.
|
||
*
|
||
* ДИАПАЗОН — весь, а не «обычный случай». Отрицательные X реальны:
|
||
* obj_x = 2*fwd − 116 уходит в минус, как только fwd < 58 (левее
|
||
* x_bump[5]), то есть у левой кромки комнаты — это не экзотика. По
|
||
* фактическим данным кадров (kid_data.bin: dx Кида −5..+10, стража
|
||
* −2..+10) при x типа uint8_t и render_dx ∈ {−140, 0, +140} полный
|
||
* диапазон obj_x = −416..695; таблица кроет −448..703 с запасом.
|
||
* Деление ниже поэтому недостижимо и оставлено страховкой на случай
|
||
* третьего персонажа или другого render_dx.
|
||
*
|
||
* SCRX7[x + SCRX_BIAS] == x/7 (усечение к нулю, как в C и как у 8086 idiv
|
||
* в оригинале); экранная X = x + SCRX7[...]. */
|
||
#define SCRX_BIAS 448
|
||
#define SCRX_N 1152
|
||
static const int8_t SCRX7[SCRX_N] = {
|
||
-64, -63, -63, -63, -63, -63, -63, -63, -62, -62, -62, -62, -62, -62, -62, -61, -61, -61, -61, -61,
|
||
-61, -61, -60, -60, -60, -60, -60, -60, -60, -59, -59, -59, -59, -59, -59, -59, -58, -58, -58, -58,
|
||
-58, -58, -58, -57, -57, -57, -57, -57, -57, -57, -56, -56, -56, -56, -56, -56, -56, -55, -55, -55,
|
||
-55, -55, -55, -55, -54, -54, -54, -54, -54, -54, -54, -53, -53, -53, -53, -53, -53, -53, -52, -52,
|
||
-52, -52, -52, -52, -52, -51, -51, -51, -51, -51, -51, -51, -50, -50, -50, -50, -50, -50, -50, -49,
|
||
-49, -49, -49, -49, -49, -49, -48, -48, -48, -48, -48, -48, -48, -47, -47, -47, -47, -47, -47, -47,
|
||
-46, -46, -46, -46, -46, -46, -46, -45, -45, -45, -45, -45, -45, -45, -44, -44, -44, -44, -44, -44,
|
||
-44, -43, -43, -43, -43, -43, -43, -43, -42, -42, -42, -42, -42, -42, -42, -41, -41, -41, -41, -41,
|
||
-41, -41, -40, -40, -40, -40, -40, -40, -40, -39, -39, -39, -39, -39, -39, -39, -38, -38, -38, -38,
|
||
-38, -38, -38, -37, -37, -37, -37, -37, -37, -37, -36, -36, -36, -36, -36, -36, -36, -35, -35, -35,
|
||
-35, -35, -35, -35, -34, -34, -34, -34, -34, -34, -34, -33, -33, -33, -33, -33, -33, -33, -32, -32,
|
||
-32, -32, -32, -32, -32, -31, -31, -31, -31, -31, -31, -31, -30, -30, -30, -30, -30, -30, -30, -29,
|
||
-29, -29, -29, -29, -29, -29, -28, -28, -28, -28, -28, -28, -28, -27, -27, -27, -27, -27, -27, -27,
|
||
-26, -26, -26, -26, -26, -26, -26, -25, -25, -25, -25, -25, -25, -25, -24, -24, -24, -24, -24, -24,
|
||
-24, -23, -23, -23, -23, -23, -23, -23, -22, -22, -22, -22, -22, -22, -22, -21, -21, -21, -21, -21,
|
||
-21, -21, -20, -20, -20, -20, -20, -20, -20, -19, -19, -19, -19, -19, -19, -19, -18, -18, -18, -18,
|
||
-18, -18, -18, -17, -17, -17, -17, -17, -17, -17, -16, -16, -16, -16, -16, -16, -16, -15, -15, -15,
|
||
-15, -15, -15, -15, -14, -14, -14, -14, -14, -14, -14, -13, -13, -13, -13, -13, -13, -13, -12, -12,
|
||
-12, -12, -12, -12, -12, -11, -11, -11, -11, -11, -11, -11, -10, -10, -10, -10, -10, -10, -10, -9,
|
||
-9, -9, -9, -9, -9, -9, -8, -8, -8, -8, -8, -8, -8, -7, -7, -7, -7, -7, -7, -7,
|
||
-6, -6, -6, -6, -6, -6, -6, -5, -5, -5, -5, -5, -5, -5, -4, -4, -4, -4, -4, -4,
|
||
-4, -3, -3, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1,
|
||
-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
|
||
1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,
|
||
4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7,
|
||
7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 10, 10,
|
||
10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13,
|
||
13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15,
|
||
16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18,
|
||
18, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21,
|
||
21, 21, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24,
|
||
24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27,
|
||
27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 30, 30,
|
||
30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 33,
|
||
33, 33, 33, 33, 33, 33, 34, 34, 34, 34, 34, 34, 34, 35, 35, 35, 35, 35, 35, 35,
|
||
36, 36, 36, 36, 36, 36, 36, 37, 37, 37, 37, 37, 37, 37, 38, 38, 38, 38, 38, 38,
|
||
38, 39, 39, 39, 39, 39, 39, 39, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41,
|
||
41, 41, 42, 42, 42, 42, 42, 42, 42, 43, 43, 43, 43, 43, 43, 43, 44, 44, 44, 44,
|
||
44, 44, 44, 45, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 46, 46, 46, 47, 47, 47,
|
||
47, 47, 47, 47, 48, 48, 48, 48, 48, 48, 48, 49, 49, 49, 49, 49, 49, 49, 50, 50,
|
||
50, 50, 50, 50, 50, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 53,
|
||
53, 53, 53, 53, 53, 53, 54, 54, 54, 54, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55,
|
||
56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58,
|
||
58, 59, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61,
|
||
61, 61, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64,
|
||
64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 67, 67, 67,
|
||
67, 67, 67, 67, 68, 68, 68, 68, 68, 68, 68, 69, 69, 69, 69, 69, 69, 69, 70, 70,
|
||
70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72, 72, 72, 73,
|
||
73, 73, 73, 73, 73, 73, 74, 74, 74, 74, 74, 74, 74, 75, 75, 75, 75, 75, 75, 75,
|
||
76, 76, 76, 76, 76, 76, 76, 77, 77, 77, 77, 77, 77, 77, 78, 78, 78, 78, 78, 78,
|
||
78, 79, 79, 79, 79, 79, 79, 79, 80, 80, 80, 80, 80, 80, 80, 81, 81, 81, 81, 81,
|
||
81, 81, 82, 82, 82, 82, 82, 82, 82, 83, 83, 83, 83, 83, 83, 83, 84, 84, 84, 84,
|
||
84, 84, 84, 85, 85, 85, 85, 85, 85, 85, 86, 86, 86, 86, 86, 86, 86, 87, 87, 87,
|
||
87, 87, 87, 87, 88, 88, 88, 88, 88, 88, 88, 89, 89, 89, 89, 89, 89, 89, 90, 90,
|
||
90, 90, 90, 90, 90, 91, 91, 91, 91, 91, 91, 91, 92, 92, 92, 92, 92, 92, 92, 93,
|
||
93, 93, 93, 93, 93, 93, 94, 94, 94, 94, 94, 94, 94, 95, 95, 95, 95, 95, 95, 95,
|
||
96, 96, 96, 96, 96, 96, 96, 97, 97, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 98,
|
||
98, 99, 99, 99, 99, 99, 99, 99, 100, 100, 100, 100,
|
||
};
|
||
|
||
static int scr_x(int x)
|
||
{
|
||
/* Индекс считаем в uint16_t ОТДЕЛЬНОЙ переменной, а не выражением внутри
|
||
* скобок: на `(const T*)КОНСТ + var*K` SDCC z80 умеет терять старший байт
|
||
* индекса (memory sdcc_z80_const_ptr_index_bug), а тут индекс как раз
|
||
* больше 255. */
|
||
uint16_t i = (uint16_t)(x + SCRX_BIAS);
|
||
if (i < SCRX_N) return x + SCRX7[i];
|
||
return x * 8 / 7; /* по данным кадров недостижимо */
|
||
}
|
||
|
||
/* Добавить накладной спрайт (клинок / брызги) в overlay-прямоугольник
|
||
* страницы. Оба мелкие и в одном кадре встречаются редко — их объединение
|
||
* дешевле третьего heal-слота. */
|
||
static void cd_overlay_add(pop_cdraw_t *s, uint8_t dp, int x, int y,
|
||
uint16_t w, uint16_t h)
|
||
{
|
||
if (!s->ovalid[dp]) {
|
||
s->ox[dp] = x; s->oy[dp] = y; s->ow[dp] = w; s->oh[dp] = h;
|
||
s->ovalid[dp] = 1;
|
||
return;
|
||
}
|
||
{
|
||
int x0 = s->ox[dp], y0 = s->oy[dp];
|
||
int x1 = x0 + (int)s->ow[dp], y1 = y0 + (int)s->oh[dp];
|
||
if (x < x0) x0 = x;
|
||
if (y < y0) y0 = y;
|
||
if (x + (int)w > x1) x1 = x + (int)w;
|
||
if (y + (int)h > y1) y1 = y + (int)h;
|
||
s->ox[dp] = x0; s->oy[dp] = y0;
|
||
s->ow[dp] = (uint16_t)(x1 - x0); s->oh[dp] = (uint16_t)(y1 - y0);
|
||
}
|
||
}
|
||
|
||
/* Добавить прямоугольник в окно fore-клипа слота (объединение «спрайт +
|
||
* накладные»): по нему fore-проход возвращает куски тайлов и по нему же
|
||
* расширяет перебор колонок/рядов. */
|
||
static void cd_clip_add(pop_cdraw_t *s, int x, int y, uint16_t w, uint16_t h)
|
||
{
|
||
if (!s->cw) {
|
||
s->cx = x; s->cy = y; s->cw = w; s->ch = h;
|
||
return;
|
||
}
|
||
{
|
||
int x1 = s->cx + (int)s->cw, y1 = s->cy + (int)s->ch;
|
||
if (x < s->cx) s->cx = x;
|
||
if (y < s->cy) s->cy = y;
|
||
if (x + (int)w > x1) x1 = x + (int)w;
|
||
if (y + (int)h > y1) y1 = y + (int)h;
|
||
s->cw = (uint16_t)(x1 - s->cx);
|
||
s->ch = (uint16_t)(y1 - s->cy);
|
||
}
|
||
}
|
||
|
||
/* Стирание прошлого кадра персонажа и его накладных. pop_heal_fast, а не
|
||
* голый gfx_heal: стираем ровно то, что рисовали, а рисовали в подавляющем
|
||
* большинстве кадров noclip-ядром; клипающий путь остаётся для straddle и
|
||
* выхода за поле.
|
||
*
|
||
* Тело — static: pop_char_draw зовёт его же «страховкой» (см. ниже), а
|
||
* трамплин выбирается ОБЪЯВЛЕНИЕМ, и вызов через `__banked`-имя стоил бы
|
||
* 654 такта внутри собственного банка (memory sdcc_banked_call_rules). */
|
||
static void cd_heal(uint8_t who)
|
||
{
|
||
pop_cdraw_t *s = &pop_cd[who];
|
||
uint8_t p = gfx_get_draw_page() & 1;
|
||
if (s->valid[p]) pop_heal_fast(s->x[p], s->y[p] + POP_YOFF, s->w[p], s->h[p]);
|
||
if (s->ovalid[p]) pop_heal_fast(s->ox[p], s->oy[p] + POP_YOFF, s->ow[p], s->oh[p]);
|
||
s->valid[p] = s->ovalid[p] = 0;
|
||
}
|
||
|
||
void pop_char_heal(uint8_t who) __banked { cd_heal(who); }
|
||
|
||
/* ---- Пропуск неизменившегося кадра (DRAW-COST; контракт — pop_cdraw.h) -- */
|
||
|
||
/* Снимок ВХОДОВ отрисовки слота. Кадр анимации (kid_frame/pop_gframe)
|
||
* однозначно определяется полем frame, поэтому его самого в снимке нет;
|
||
* тайлы вокруг персонажа (clip_char, fore-проход) в снимок тоже не входят —
|
||
* их изменение ловит pop_cd_dirty. */
|
||
typedef struct {
|
||
uint8_t frame, x, y, action, sword, charid, room, hurt;
|
||
int8_t dir, ccol, crow;
|
||
int dx; /* render_dx: straddle-смещение */
|
||
} cd_sig_t;
|
||
|
||
static cd_sig_t cd_sig[POP_CH_N][2];
|
||
|
||
static void cd_sig_make(uint8_t who, cd_sig_t *g)
|
||
{
|
||
const pop_char_t *ch = (who == POP_CH_KID) ? &Kid : &Guard;
|
||
g->frame = ch->frame; g->x = ch->x; g->y = ch->y;
|
||
g->action = ch->action; g->sword = ch->sword; g->charid = ch->charid;
|
||
g->room = ch->room;
|
||
g->hurt = (uint8_t)((who == POP_CH_KID) ? pop_kid_hurt : pop_guard_hurt);
|
||
g->dir = ch->direction;
|
||
g->ccol = ch->curr_col; g->crow = ch->curr_row;
|
||
g->dx = pop_cd[who].render_dx;
|
||
}
|
||
|
||
/* Габарит слота на странице: спрайт + накладные (клинок/брызги). y —
|
||
* КОМНАТНЫЙ (как в pop_cd), перевод в экранный делает вызывающий. */
|
||
static void cd_bbox(uint8_t who, uint8_t p, int *x0, int *y0, int *x1, int *y1)
|
||
{
|
||
const pop_cdraw_t *s = &pop_cd[who];
|
||
*x0 = s->x[p]; *y0 = s->y[p];
|
||
*x1 = *x0 + (int)s->w[p]; *y1 = *y0 + (int)s->h[p];
|
||
if (s->ovalid[p]) {
|
||
if (s->ox[p] < *x0) *x0 = s->ox[p];
|
||
if (s->oy[p] < *y0) *y0 = s->oy[p];
|
||
if (s->ox[p] + (int)s->ow[p] > *x1) *x1 = s->ox[p] + (int)s->ow[p];
|
||
if (s->oy[p] + (int)s->oh[p] > *y1) *y1 = s->oy[p] + (int)s->oh[p];
|
||
}
|
||
}
|
||
|
||
/* «Тихий» слот: спрайт этой страницы цел (heal его не трогал), фон в его
|
||
* прямоугольнике не трогали, и все входы отрисовки те же, что в момент,
|
||
* когда этот кадр на эту страницу и рисовали. valid[p] здесь ключевой: он
|
||
* снимается heal'ом, поэтому решение остаётся согласованным, сколько раз его
|
||
* ни перепроверяй за кадр. */
|
||
static uint8_t cd_quiet(uint8_t who, uint8_t p)
|
||
{
|
||
cd_sig_t g;
|
||
const uint8_t *a, *b;
|
||
uint8_t i;
|
||
/* Соперника на сцене нет и на ЭТОЙ странице от него ничего не осталось —
|
||
* стирать и рисовать нечего, слот «тихий». Без этого пустой слот каждый
|
||
* кадр честно проходил heal + вход в pop_char_draw + fore-проход и стоил
|
||
* ~8 200 тактов из ничего (замер MAME, комната без стража). */
|
||
if (who == POP_CH_OPP && Guard.charid == 0 &&
|
||
!pop_cd[who].valid[p] && !pop_cd[who].ovalid[p])
|
||
return 1;
|
||
if (!pop_cd[who].valid[p]) return 0;
|
||
if (pop_cd_dirty & (1 << p)) { /* фон правили — но задели ли нас? */
|
||
int x0, y0, x1, y1;
|
||
cd_bbox(who, p, &x0, &y0, &x1, &y1);
|
||
y0 += POP_YOFF; y1 += POP_YOFF; /* метка фона — в ЭКРАННЫХ */
|
||
/* cd_bbox отдаёт x1/y1 ИСКЛЮЧИТЕЛЬНО, pop_cd_hit ждёт включительно. */
|
||
if (pop_cd_hit(p, x0, y0, x1 - 1, y1 - 1)) return 0;
|
||
}
|
||
cd_sig_make(who, &g);
|
||
a = (const uint8_t *)&g; b = (const uint8_t *)&cd_sig[who][p];
|
||
for (i = 0; i < sizeof(cd_sig_t); i++)
|
||
if (*a++ != *b++) return 0;
|
||
return 1;
|
||
}
|
||
|
||
/* Запас вокруг габарита соседа: за кадр персонаж проходит заметно меньше
|
||
* (бег — ~11 экранных px), а прыжок с падением укладывается сюда же. */
|
||
#define CD_MARGIN 32
|
||
|
||
uint8_t pop_char_skip_mask(void) __banked
|
||
{
|
||
uint8_t p = gfx_get_draw_page() & 1;
|
||
uint8_t q0 = cd_quiet(POP_CH_KID, p);
|
||
uint8_t q1 = cd_quiet(POP_CH_OPP, p);
|
||
if (q0 && q1) return 3; /* оба стоят — пересечься нечему */
|
||
if (q0 || q1) {
|
||
/* Один перерисовывается: его heal (и fore-проход по его окну) может
|
||
* задеть «тихого» соседа и выесть из него кусок. Прямоугольники
|
||
* рядом — перерисовываем ОБОИХ. */
|
||
uint8_t st = (uint8_t)(q0 ? POP_CH_KID : POP_CH_OPP);
|
||
uint8_t mv = (uint8_t)(st ^ 1);
|
||
/* valid[st] тоже: «тихий» слот, в котором на этой странице НИЧЕГО не
|
||
* нарисовано (соперника на сцене нет), выесть невозможно, а bbox у
|
||
* него мусорный — правило соседства к нему неприменимо. */
|
||
if (pop_cd[st].valid[p] && pop_cd[mv].valid[p]) {
|
||
int ax0, ay0, ax1, ay1, bx0, by0, bx1, by1;
|
||
cd_bbox(st, p, &ax0, &ay0, &ax1, &ay1);
|
||
cd_bbox(mv, p, &bx0, &by0, &bx1, &by1);
|
||
if (ax0 < bx1 + CD_MARGIN && bx0 < ax1 + CD_MARGIN &&
|
||
ay0 < by1 + CD_MARGIN && by0 < ay1 + CD_MARGIN) return 0;
|
||
}
|
||
}
|
||
return (uint8_t)((q0 ? 1 : 0) | (q1 ? 2 : 0));
|
||
}
|
||
|
||
void pop_char_set_render_dx(uint8_t who, int dx) __banked
|
||
{
|
||
pop_cd[who].render_dx = dx;
|
||
}
|
||
|
||
/* Брызги урона (draw_hurt_splash, seg006:2003) — ОДИН порт на всех: у Кида
|
||
* это image 218 своего chtab_2, у соперника image 1 его chtab_5 (у
|
||
* add_midtable аргумент `obj_id + 1`, а get_image вычитает единицу обратно
|
||
* — тот же off-by-one, что расписан в шапке pop_pack_guard.py). Подъём:
|
||
* `((charid == kid) << 2) + 11`, то есть 15 у Кида и 11 у остальных.
|
||
*
|
||
* Рисуются МЕЖДУ персонажем и клинком: draw_kid/draw_guard кладут их в
|
||
* objtable сразу после персонажа и до клинка (seg008:22A2/22C9), а порядок
|
||
* записей и есть порядок рисования. */
|
||
static void cd_splash(pop_cdraw_t *s, uint8_t who, const atlas_t *pages,
|
||
int fp_x, int obj_y)
|
||
{
|
||
const atlas_t *ap;
|
||
uint8_t aidx;
|
||
const uint8_t *img;
|
||
uint16_t w, h;
|
||
int qx = fp_x, qy = obj_y;
|
||
int fw = (Char.direction < 0) ? -5 : 5; /* obj_dx_forward(5) */
|
||
|
||
if (Char.frame == 178) return; /* chomped: брызг нет */
|
||
if (who == POP_CH_KID) {
|
||
if ((KID_SPLASH_IMAGE >> 3) >= kid_npages) return; /* страница не загружена */
|
||
ap = &pages[KID_SPLASH_IMAGE >> 3]; aidx = KID_SPLASH_IMAGE & 7;
|
||
} else {
|
||
ap = &pages[0]; aidx = 1;
|
||
}
|
||
if (Char.frame == 185 || /* мёртв */
|
||
(Char.frame >= 106 && Char.frame < 111)) { /* падение+посадка */
|
||
qy += 4;
|
||
} else if (Char.frame == 177) { /* напоролся на пики */
|
||
fw = -fw; /* obj_dx_forward(-5) */
|
||
} else {
|
||
qy -= (who == POP_CH_KID) ? 15 : 11;
|
||
}
|
||
qx = scr_x(qx + fw); /* calc_screen_x_coord */
|
||
img = (const uint8_t *)atlas_image(ap, aidx);
|
||
gfx_w0_map(ap->page);
|
||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||
if (w && h) {
|
||
int top = qy - (int)h + 1;
|
||
uint8_t flip = (uint8_t)(Char.direction >= 0);
|
||
int skip = 0, rows = (int)h; /* поле комнаты 0..192 */
|
||
uint8_t dp;
|
||
if (flip) qx -= (int)w;
|
||
if (top < 0) { skip = -top; rows -= skip; top = 0; }
|
||
if (top + rows > 192) rows = 192 - top;
|
||
if (rows <= 0) return;
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
if (pop_onscreen_cols(qx, top + POP_YOFF, w, (uint16_t)rows))
|
||
gfx_blit_cols_part_noclip(qx, top + POP_YOFF, img, flip,
|
||
(uint8_t)skip, (uint8_t)rows);
|
||
else
|
||
gfx_blit_cols_part(qx, top + POP_YOFF, img, flip, skip, rows);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
dp = gfx_get_draw_page() & 1;
|
||
cd_overlay_add(s, dp, qx, top, w, (uint16_t)rows);
|
||
cd_clip_add(s, qx, top, w, (uint16_t)rows);
|
||
}
|
||
}
|
||
|
||
void pop_char_draw(uint8_t who) __banked
|
||
{
|
||
pop_cdraw_t *s = &pop_cd[who];
|
||
const pop_char_t *ch;
|
||
const kframe *fr;
|
||
const atlas_t *pages;
|
||
const uint8_t *img;
|
||
uint8_t npages, page, idx, dp, flip, hurt, lskip;
|
||
uint16_t w, h, vis_w;
|
||
int obj_x, obj_y, fp_x, fwd, top, bx, skip, rows, ct, cr;
|
||
|
||
/* Страховка пропуска (DRAW-COST): главный цикл мог решить, что слот
|
||
* «тихий», и heal не звать — а тик после этого персонажа всё-таки
|
||
* сдвинул. Здесь прошлый кадр уже точно надо стереть. Если heal в этом
|
||
* кадре был, вызов бесплатен: valid/ovalid уже сняты. */
|
||
cd_heal(who);
|
||
|
||
s->cw = 0; /* окно fore-клипа: пусто, пока не нарисовали */
|
||
s->fpw = 0; /* габарит кадра: нечего перекрывать */
|
||
|
||
/* Окно Char (loadkid/loadshad) — как в оригинале: clip_char, футпринт и
|
||
* брызги работают с АКТИВНЫМ персонажем, а к этому моменту в Char лежит
|
||
* тот, кто тикал последним. Выгружать назад нечего: отрисовка только
|
||
* читает. */
|
||
if (who == POP_CH_KID) {
|
||
pop_loadkid();
|
||
ch = &Kid; fr = &kid_frame; pages = kidp; npages = kid_npages;
|
||
hurt = pop_kid_hurt;
|
||
} else {
|
||
pop_loadshad();
|
||
ch = &Guard; fr = &pop_gframe; pages = gp;
|
||
npages = g_ok ? GUARD_PAGES : 0;
|
||
hurt = pop_guard_hurt;
|
||
if (Guard.charid == 0) return; /* соперника на сцене нет */
|
||
/* ТЕНЬ (ур. 4) — это зеркальный Кид: вне боевых кадров 150..189 она
|
||
* ходит по таблице КИДА (seg006:0532), а `image` из неё индексирует
|
||
* спрайты Кида, а не стража. Набор атласов обязан идти за таблицей —
|
||
* условие берём из того же места, что load_frame, чтобы они не
|
||
* разъехались. */
|
||
if (!pop_frame_tbl_is_guard(Guard.charid, Guard.frame)) {
|
||
pages = kidp; npages = kid_npages;
|
||
}
|
||
}
|
||
if (fr->image == 255) return; /* пустой кадр */
|
||
|
||
/* load_frame_to_obj (seg008:1728): НИЗ спрайта на obj_y, левый край на
|
||
* obj_x. +render_dx — straddle-смещение (∓140), когда комната персонажа
|
||
* не совпадает с отрисованной: спрайт за кромкой. Коллизия НЕ смещается
|
||
* — она в кадре своей комнаты, смещение чисто рендерное. */
|
||
fwd = (int)ch->x + ((ch->direction < 0) ? -fr->dx : fr->dx) + s->render_dx;
|
||
obj_x = (fwd << 1) - 116;
|
||
obj_y = (int)ch->y + fr->dy;
|
||
if ((int8_t)(fr->flags ^ ch->direction) >= 0)
|
||
obj_x++; /* even/odd pixel (в ЛОГИЧЕСКОЙ X) */
|
||
fp_x = obj_x; /* ЛОГИЧЕСКАЯ X — для футпринта */
|
||
/* Позицию кадра публикуем СРАЗУ: её читает физика (check_spike_below —
|
||
* char_x_left активного персонажа), и она обязана обновиться даже в
|
||
* кадре, который рисовать нечем. Размер (fpw) — ниже, по факту кадра:
|
||
* он же служит признаком «есть что перекрывать» для fore-прохода. */
|
||
s->fpx = fp_x; s->fpy = obj_y;
|
||
/* seg008 calc_screen_x_coord: логическая X (280 шир = 28px/тайл) ->
|
||
* экран (320 шир = 32px/тайл), ×320/280 = ×8/7. БЕЗ этого персонаж
|
||
* уезжает влево от тайлов тем сильнее, чем правее. Через scr_x —
|
||
* таблицей, без __divsint (см. шапку DIV7). */
|
||
obj_x = scr_x(obj_x);
|
||
|
||
page = (uint8_t)(fr->image >> 3);
|
||
idx = (uint8_t)(fr->image & 7);
|
||
if (page >= npages) return; /* страница набора не загружена */
|
||
img = (const uint8_t *)atlas_image(&pages[page], idx);
|
||
gfx_w0_map(pages[page].page);
|
||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||
dp = gfx_get_draw_page() & 1;
|
||
if (w && h) {
|
||
/* Спрайты нарисованы ЛИЦОМ ВЛЕВО (как в оригинале); seg008:864 —
|
||
* флип, когда персонаж смотрит ВПРАВО, и тогда xpos -= image->w
|
||
* (draw_mid). Флип column-major бесплатен, но зеркалит ВНУТРИ
|
||
* [x,x+w), поэтому при флипе подаём x−w. */
|
||
flip = (uint8_t)(ch->direction >= 0);
|
||
bx = obj_x;
|
||
if (flip) bx -= (int)w;
|
||
top = obj_y - (int)h + 1;
|
||
vis_w = w;
|
||
lskip = 0;
|
||
skip = 0;
|
||
/* clip_char (seg006:1231/1749): тайл над головой — стена/пол → всё
|
||
* выше линии полосы своего ряда НЕ рисуется, иначе персонаж
|
||
* просвечивает сквозь щели кладки при спуске/висе. */
|
||
ct = pop_clip_char_top(fp_x, obj_y, w, h);
|
||
if (ct > top) {
|
||
skip = ct - top;
|
||
if (skip > (int)h) skip = (int)h; /* обрезан целиком */
|
||
top = ct;
|
||
}
|
||
/* Вторая половина clip_char: правый косяк двери уровня. Дверь
|
||
* рисуется ПОД персонажем (вся в backtable), поэтому спрятать
|
||
* поднимающегося по лестнице можно только срезав спрайт. */
|
||
cr = pop_clip_char_right();
|
||
if (cr) {
|
||
int avail = cr - bx;
|
||
if (avail <= 0) vis_w = 0; /* весь спрайт за косяком */
|
||
else if (avail < (int)w) vis_w = (uint16_t)avail;
|
||
}
|
||
/* КЛИП ТЕНИ СЛЕВА (seg008:1699): на уровне зеркала она может
|
||
* показываться ТОЛЬКО СПРАВА от него —
|
||
* obj_clip_left = 137 + (mirror_column-4)*32. */
|
||
if (ch->charid == CHARID_1_SHADOW &&
|
||
pop_current_level == MIRROR_LEVEL && ch->room == MIRROR_ROOM) {
|
||
int cl = 137 + (MIRROR_COL - 4) * 32;
|
||
if (bx < cl) {
|
||
int d = cl - bx;
|
||
if (d >= (int)vis_w) vis_w = 0; /* вся тень левее линии */
|
||
else lskip = (uint8_t)d;
|
||
}
|
||
}
|
||
/* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507:
|
||
* obj_clip_top = 0, obj_clip_bottom = 192). Персонаж, вышедший за
|
||
* него (скелет, падающий в пропасть), обязан обрезаться: ниже поля
|
||
* идёт полоса HP, и без клипа спрайт лез прямо на неё. */
|
||
rows = (int)h - skip;
|
||
if (top < 0) { skip += -top; rows += top; top = 0; }
|
||
if (top + rows > 192) rows = 192 - top;
|
||
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
|
||
if (rows > 0 && vis_w) { /* +YOFF: центрирование */
|
||
/* noclip-ядро ширину не ограничивает, поэтому при обрезке справа
|
||
* идём общим путём: это редкие кадры (подъём в двери уровня раз
|
||
* за уровень), цена подготовки там не видна. */
|
||
if (lskip) /* тень у зеркала: срез слева */
|
||
gfx_blit_cols_part_wx(bx, top + POP_YOFF, img, flip, skip, rows,
|
||
lskip, (uint8_t)(vis_w - lskip));
|
||
else if (vis_w == w && pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
|
||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip,
|
||
(uint8_t)skip, (uint8_t)rows);
|
||
else
|
||
gfx_blit_cols_part_w(bx, top + POP_YOFF, img, flip, skip, rows,
|
||
(uint8_t)(vis_w == w ? 0 : vis_w));
|
||
} else rows = 0;
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
/* heal чистит ТОЛЬКО нарисованное: иначе стирается кромка пола над
|
||
* срезом (мусор/дыра в кладке на второй странице дабл-буфера). */
|
||
s->x[dp] = bx + lskip; s->y[dp] = top;
|
||
s->w[dp] = (uint16_t)(vis_w - lskip); s->h[dp] = (uint16_t)rows;
|
||
s->valid[dp] = (uint8_t)(rows != 0);
|
||
if (rows) cd_clip_add(s, bx, top, vis_w, (uint16_t)rows);
|
||
s->fpw = w; s->fph = h; /* габарит КАДРА (не обрезанный): */
|
||
/* по нему считается футпринт */
|
||
}
|
||
if (hurt) cd_splash(s, who, pages, fp_x, obj_y);
|
||
/* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх
|
||
* персонажа — со своим прямоугольником heal. */
|
||
{
|
||
int sx, sy; uint16_t sw, sh;
|
||
if (pop_sword_draw(ch, fr, obj_x, obj_y, &sx, &sy, &sw, &sh)) {
|
||
cd_overlay_add(s, dp, sx, sy, sw, sh);
|
||
cd_clip_add(s, sx, sy, sw, sh);
|
||
}
|
||
}
|
||
gfx_w0_unmap();
|
||
/* Что именно нарисовано на ЭТОЙ странице — снимок для пропуска
|
||
* следующих кадров (DRAW-COST). Только если кадр РЕАЛЬНО рисовали:
|
||
* при нулевом спрайте (`w && h` ложно — пустая запись атласа) блок выше
|
||
* не выполняется, и x/y/w/h/valid остаются от ПРОШЛОГО кадра этой
|
||
* страницы. Снимок же брался всегда — и слот залипал: cd_quiet видел
|
||
* valid = 1 и совпавшую сигнатуру, то есть считал «на странице
|
||
* нарисовано ровно то, что нужно», а на ней стоял старый спрайт. Так
|
||
* тень оставалась на двух страницах дабл-буфера в РАЗНЫХ позах и мигала
|
||
* между ними (BUG-CHAR-STALE-PAGE). Без снимка слот просто не будет
|
||
* «тихим», и следующий кадр начнётся с heal — старое сотрётся. */
|
||
if (w && h) cd_sig_make(who, &cd_sig[who][dp]);
|
||
}
|
||
|
||
void pop_char_fore(uint8_t who) __banked
|
||
{
|
||
pop_cdraw_t *s = &pop_cd[who];
|
||
/* Окно fore-клипа — одно на всех, и ставит его КАЖДЫЙ, кто рисует
|
||
* персонажа. Пока соперник рисовался строго ДО Кида, к моменту прохода
|
||
* в окне оказывался Кид и всё сходилось само; как только порядок стал
|
||
* переменным (кто ниже — тот поверх), при «соперник поверх» окно
|
||
* оставалось ЕГО, и fore-проход Кида отсекался целиком — Кид рисовался
|
||
* поверх передних столбов. Поэтому окно ставится здесь, а не держится
|
||
* «по счастливому порядку вызовов». */
|
||
pop_fore_set_clip(s->cx, s->cy + POP_YOFF, (int)s->cw, (int)s->ch);
|
||
if (s->fpw)
|
||
pop_fore_over_char(who == POP_CH_KID ? &Kid : &Guard,
|
||
s->fpx, s->fpy, s->fpw, s->fph);
|
||
}
|
||
|
||
/* ---- ОТРАЖЕНИЕ В ЗЕРКАЛЕ (check_mirror, seg003:0798) ---------------- *
|
||
*
|
||
* Пока Кид стоит на тайле зеркала, оригинал каждый кадр рисует его
|
||
* зеркальную копию. ОТДЕЛЬНОЙ функцией, а не третьим слотом pop_cd — и это
|
||
* не экономия, а структура САМОГО оригинала: отражение идёт сокращённым
|
||
* путём `load_frame_to_obj(); reset_obj_clip(); add_objtable(4)`, а НЕ через
|
||
* add_kid_to_objtable. У него нет ни клинка, ни брызг, ни fore-прохода, ни
|
||
* пропуска неизменившегося кадра — гейтить всё это в общем теле значило бы
|
||
* добавить ветки в самый горячий путь ради двух секунд за игру.
|
||
*
|
||
* Char к моменту вызова уже ОТРАЖЁН вызывающим (pop_check_mirror ->
|
||
* mirror_image), поэтому здесь никакого «зеркалим» нет — обычная математика
|
||
* кадра, та же, что в pop_char_draw (см. там load_frame_to_obj, seg008:1728;
|
||
* шесть строк намеренно продублированы, а не вынесены: трогать горячее тело
|
||
* ради холодного вызова дороже, чем держать их синхронными по комментарию).
|
||
*/
|
||
static int mi_x[2], mi_y[2];
|
||
static uint16_t mi_w[2], mi_h[2];
|
||
static uint8_t mi_valid[2];
|
||
|
||
void pop_mirror_heal(void) __banked
|
||
{
|
||
uint8_t p = gfx_get_draw_page() & 1;
|
||
if (!mi_valid[p]) return;
|
||
pop_heal_fast(mi_x[p], mi_y[p] + POP_YOFF, mi_w[p], mi_h[p]);
|
||
mi_valid[p] = 0;
|
||
}
|
||
|
||
void pop_mirror_draw(int clip_top) __banked
|
||
{
|
||
uint8_t dp = gfx_get_draw_page() & 1;
|
||
const kframe *fr = &kid_frame;
|
||
const uint8_t *img;
|
||
uint8_t page, idx, flip;
|
||
uint16_t w, h, vis_w;
|
||
int fwd, obj_x, obj_y, fp_x, bx, top, skip = 0, rows, cl;
|
||
uint8_t lskip = 0;
|
||
|
||
if (fr->image == 255) return;
|
||
page = (uint8_t)(fr->image >> 3);
|
||
idx = (uint8_t)(fr->image & 7);
|
||
if (page >= kid_npages) return;
|
||
|
||
fwd = (int)Char.x + ((Char.direction < 0) ? -fr->dx : fr->dx);
|
||
obj_x = (fwd << 1) - 116;
|
||
obj_y = (int)Char.y + fr->dy;
|
||
if ((int8_t)(fr->flags ^ Char.direction) >= 0) obj_x++;
|
||
fp_x = obj_x; /* ЛОГИЧЕСКАЯ X — её ждёт fore-проход */
|
||
obj_x = scr_x(obj_x);
|
||
|
||
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) { gfx_w0_unmap(); return; }
|
||
flip = (uint8_t)(Char.direction >= 0);
|
||
bx = flip ? obj_x - (int)w : obj_x;
|
||
top = obj_y - (int)h + 1;
|
||
|
||
/* Клип СВЕРХУ — y_clip[curr_row+1] оригинала: отражение не должно
|
||
* вылезать из проёма зеркала вверх. Значение считает вызывающий
|
||
* (таблица y_clip живёт в pop_map). */
|
||
if (clip_top > top) { skip = clip_top - top; top = clip_top; }
|
||
rows = (int)h - skip;
|
||
if (top < 0) { skip += -top; rows += top; top = 0; }
|
||
if (top + rows > 192) rows = 192 - top;
|
||
vis_w = w;
|
||
if (rows <= 0) { gfx_w0_unmap(); return; }
|
||
|
||
/* Клип СЛЕВА — obj_clip_left = (curr_col<<5)+9: отражение видно только
|
||
* внутри проёма зеркала. Настоящей обрезкой колонок
|
||
* (gfx_blit_cols_part_wx), а не «нарисовать и вернуть фон»: для
|
||
* column-major левая обрезка стоит столько же, сколько правая. */
|
||
cl = ((int)Char.curr_col << 5) + 9;
|
||
if (bx < cl) {
|
||
int d = cl - bx;
|
||
if (d >= (int)vis_w) { gfx_w0_unmap(); return; } /* целиком за краем */
|
||
lskip = (uint8_t)d;
|
||
}
|
||
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
if (lskip)
|
||
gfx_blit_cols_part_wx(bx, top + POP_YOFF, img, flip, skip, rows,
|
||
lskip, (uint8_t)(vis_w - lskip));
|
||
else if (pop_onscreen_cols(bx, top + POP_YOFF, vis_w, (uint16_t)rows))
|
||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip,
|
||
(uint8_t)skip, (uint8_t)rows);
|
||
else
|
||
gfx_blit_cols_part_w(bx, top + POP_YOFF, img, flip, skip, rows, 0);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
gfx_w0_unmap();
|
||
|
||
mi_x[dp] = bx + lskip; mi_y[dp] = top;
|
||
mi_w[dp] = (uint16_t)(vis_w - lskip); mi_h[dp] = (uint16_t)rows;
|
||
mi_valid[dp] = 1;
|
||
|
||
/* FORE-ПРОХОД ПОВЕРХ ОТРАЖЕНИЯ — обязателен, и без него картинка неверна.
|
||
* В оригинале отражение уходит в objtable (add_objtable(4)), а дальше его
|
||
* накрывает передний слой тайлов на общих основаниях; у зеркала это
|
||
* fore_id 77 — ПЕРЕДНЯЯ ЧАСТЬ АРКИ. Именно она прячет то, что вылезло из
|
||
* проёма: ноги ниже нижней кромки и голову с руками выше верхней (видно
|
||
* на прыжке). Клип obj_clip_top этого НЕ делает — при curr_row = 0 он
|
||
* равен y_clip[1] = 3, то есть почти не режет; работу выполняет фон.
|
||
*
|
||
* Мы рисуем отражение мимо конвейера персонажей, поэтому проход надо
|
||
* позвать руками — ровно как это делает pop_char_fore для слотов. */
|
||
pop_fore_set_clip(bx + lskip, top + POP_YOFF,
|
||
(int)(vis_w - lskip), rows);
|
||
pop_fore_over_char(&Char, fp_x, obj_y, w, h);
|
||
}
|
||
|
||
/* ---- Индикаторы HP (порт draw_kid_hp / draw_guard_hp, seg000:1416) --- *
|
||
* Оригинал рисует их в статус-полосе на y = 194 (ниже поля тайлов, внутри
|
||
* тех же 200 строк экрана PoP): HP Кида слева направо шагом 7, HP стража
|
||
* справа налево от x = 314. Пустые деления у Кида — свой спрайт (chtab_2
|
||
* id 217); у стража оригинал печатает тот же спрайт ЧЁРНЫМ силуэтом, а мы
|
||
* просто не печатаем — полоса и так на чёрном фоне.
|
||
*
|
||
* Спрайты Кида (216/217) лежат в его же атласах, поэтому блитим через
|
||
* pop_kid_img_blit; спрайт стража — image 0 его chtab (наша страница 0). */
|
||
#define HP_Y 194
|
||
#define HP_STEP 7
|
||
#define HP_KID_FULL 216
|
||
#define HP_KID_EMPTY 217
|
||
#define HP_MAXDRAW 10 /* больше в полосу всё равно не влезает */
|
||
|
||
/* Полоса перерисовывается ТОЛЬКО когда числа изменились — и тогда на ОБЕИХ
|
||
* страницах дабл-буфера (иначе на второй останется старое значение и его
|
||
* будет видно как мерцание через кадр). Отсюда счётчик страниц, а не флаг.
|
||
*
|
||
* Снимки прошлых значений пишем ДО сравнения: `if (n != s) s = n;` SDCC z80
|
||
* собирает неверно — SUB затирает A, и в s уезжает разность (memory
|
||
* sdcc_z80_cmp_store_a_bug). */
|
||
static uint8_t hp_todo; /* сколько страниц ещё обновить */
|
||
static uint8_t hp_kid_prev, hp_kidmax_prev, hp_gd_prev, hp_gdmax_prev;
|
||
|
||
/* Заставить перерисовать полосу на обеих страницах: звать при входе в
|
||
* комнату (фон перерисован целиком и стёр её) и при старте. */
|
||
void pop_hp_invalidate(void) __banked { hp_todo = 2; }
|
||
|
||
void pop_hp_draw(void) __banked
|
||
{
|
||
uint8_t i, n;
|
||
uint8_t gd = (uint8_t)(Guard.charid ? guardhp_curr : 0);
|
||
uint8_t gm = (uint8_t)(Guard.charid ? guardhp_max : 0);
|
||
|
||
{ /* Изменилось хоть одно число — обновить обе страницы. */
|
||
uint8_t k = hitp_curr, km = hitp_max;
|
||
uint8_t changed = (uint8_t)(k != hp_kid_prev || km != hp_kidmax_prev ||
|
||
gd != hp_gd_prev || gm != hp_gdmax_prev);
|
||
hp_kid_prev = k; hp_kidmax_prev = km;
|
||
hp_gd_prev = gd; hp_gdmax_prev = gm;
|
||
if (changed) hp_todo = 2;
|
||
}
|
||
if (hp_todo == 0) return;
|
||
hp_todo--;
|
||
|
||
/* Стереть прошлую полосу: фон под ней — чёрный борт. */
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
setfillstyle(SOLID_FILL, BLACK);
|
||
bar(0, HP_Y + POP_YOFF, 319, HP_Y + POP_YOFF + 6);
|
||
|
||
/* Кид: сначала пустые деления, поверх — полные (порядок оригинала). */
|
||
n = (uint8_t)(hitp_max > HP_MAXDRAW ? HP_MAXDRAW : hitp_max);
|
||
for (i = hitp_curr; i < n; i++)
|
||
pop_kid_img_blit(HP_KID_EMPTY, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
||
for (i = 0; i < hitp_curr && i < HP_MAXDRAW; i++)
|
||
pop_kid_img_blit(HP_KID_FULL, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
||
|
||
/* Страж: только полные деления, справа налево. Скелет полосы не имеет,
|
||
* мышь (спецсобытие уровня 8) — тоже: draw_guard_hp, seg000:1159. */
|
||
if (g_ok && Guard.charid != 0 && Guard.charid != CHARID_4_SKELETON &&
|
||
Guard.charid != CHARID_24_MOUSE && gd) {
|
||
const uint8_t *img = (const uint8_t *)atlas_image(&gp[0], 0);
|
||
uint16_t w, h;
|
||
gfx_w0_map(gp[0].page);
|
||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||
if (w && h) {
|
||
/* Атлас стража тоже column-major — блит колоночный. */
|
||
n = (uint8_t)(gd > HP_MAXDRAW ? HP_MAXDRAW : gd);
|
||
for (i = 0; i < n; i++)
|
||
gfx_blit_cols_part_noclip(314 - (int)i * HP_STEP,
|
||
HP_Y + POP_YOFF, img, 0, 0, 0);
|
||
}
|
||
gfx_w0_unmap();
|
||
}
|
||
}
|