755190674c
Спрайты персонажей хранятся column-major (ради бесплатного H-флипа), а вертикальное зеркало у такой раскладки — разворот байтов ВНУТРИ колонки, чего accel не умеет. Поэтому pop_vflip.c готовит зеркальные КОПИИ страниц атласов: копия побайтово повторяет раскладку .atl (заголовок, каталог, смещения лент), развёрнуты только пиксели — значит вызывающему достаточно подменить номер страницы, которую он мапит в W0. * Кэш ленивый (страница зеркалится при первом обращении в перевёрнутом виде) и живёт до смены уровня — зелье переключает состояние туда-обратно, перезеркаливать по 16 КБ на каждый переворот незачем. EMM хватает: 34 страницы из ~215 свободных. * pop_cdraw/pop_kdraw: страница + позиция (top = 191 - obj_y для персонажа, 192 - top - h для клинка). «Пропустить skip строк сверху» у зеркальной ленты = «срезать снизу» у оригинала, поэтому клипы считаются как обычно. * Модуль резидентный: он маппит W3 (приёмник копии) — банку так нельзя. * pop_vflip_reset на смене уровня: копии сделаны из старых страниц. Проверено в MAME: чит U переворачивает всё разом, Кид и факелы вверх ногами, полоса HP на месте, бег без следов на фоне (heal бьёт в те же координаты). ОСТАЁТСЯ (L9-CLIPCHAR): clip_char в перевороте должен резать снизу, а не сверху — пока при pop_upside не режем вовсе. Тени/брызг это не касается.
112 lines
5.3 KiB
C
112 lines
5.3 KiB
C
/*
|
||
* pop_vflip.c — ЗЕРКАЛЬНЫЕ ПО ВЕРТИКАЛИ копии страниц атласов персонажей
|
||
* (зелье инверсии, уровень 9). См. pop_vflip.h.
|
||
*
|
||
* Зачем отдельный набор страниц. Спрайты персонажей хранятся COLUMN-MAJOR
|
||
* (out[col*h + row], transpose_cols в pop_pack_kid.py) — ради БЕСПЛАТНОГО
|
||
* горизонтального флипа: accel копирует колонки, и порядок колонок задаётся
|
||
* знаком страйда. А вертикальное зеркало у такой раскладки — это разворот
|
||
* байтов ВНУТРИ колонки, чего accel не умеет: блок он копирует только
|
||
* вперёд. Значит перевёрнутые кадры надо готовить заранее.
|
||
*
|
||
* Устройство. Копия — ПОСТРАНИЧНАЯ и побайтово совпадает с оригиналом по
|
||
* раскладке: тот же заголовок .atl, тот же каталог, ленты на тех же
|
||
* смещениях; развёрнуты только сами пиксели. Поэтому вызывающему не нужно
|
||
* менять НИЧЕГО, кроме номера страницы, которую он мапит в W0 — все
|
||
* atlas_image-смещения и размеры кадров остаются прежними.
|
||
*
|
||
* Кэш ЛЕНИВЫЙ: страница зеркалится при первом обращении к ней в
|
||
* перевёрнутом виде (~16 КБ копирования = заметно, но однократно на кадр
|
||
* анимации), дальше отдаётся из таблицы. Держится до конца уровня;
|
||
* pop_vflip_reset освобождает всё (смена уровня / зелье выпито обратно —
|
||
* см. pop_vflip.h про то, почему НЕ освобождаем при каждом перевороте).
|
||
*
|
||
* РЕЗИДЕНТ, а не банк: функция маппит окно W3 (приёмник копии), а банковый
|
||
* код в нём же и исполняется — правило из _pop_level.h.
|
||
*/
|
||
#include <stdint.h>
|
||
#include <sprinter.h>
|
||
#include <sprinter_mem.h>
|
||
#include <sprite.h>
|
||
#include "pop_vflip.h"
|
||
|
||
/* Раскладка .atl (sprite.h §Атласы): 0x100-байтовый заголовок, каталог с
|
||
* 0x68 — по 8 байт на ленту: [0..1] смещение ленты, дальше размеры. */
|
||
#define ATL_DIR_OFF 0x68u
|
||
#define ATL_COUNT_OFF 0x04u /* число лент в заголовке */
|
||
|
||
/* Таблица кэша: страница-источник -> страница-копия. Персонажных атласов
|
||
* у нас 28 (Кид) + 5 (страж) + 1 (меч) = 34; берём с запасом. */
|
||
#define VF_MAX 40
|
||
static uint8_t vf_src[VF_MAX], vf_dst[VF_MAX], vf_blk[VF_MAX], vf_n;
|
||
|
||
/* Развернуть ленту на месте В СТРАНИЦЕ-ПРИЁМНИКЕ (она замаплена в W3).
|
||
* off — смещение ленты от начала страницы; формат ленты: w, _, h, _, пиксели
|
||
* колонками по h байт. */
|
||
static void flip_strip(uint16_t off)
|
||
{
|
||
uint8_t *p = (uint8_t *)(0xC000u + off);
|
||
uint8_t w = p[0], h = p[2];
|
||
uint8_t *col = p + 4;
|
||
uint8_t c;
|
||
|
||
if (!w || !h) return;
|
||
for (c = 0; c < w; c++) {
|
||
uint8_t *a = col, *b = col + h - 1;
|
||
while (a < b) { uint8_t t = *a; *a++ = *b; *b-- = t; }
|
||
col += h;
|
||
}
|
||
}
|
||
|
||
/* Сделать зеркальную копию страницы src. Возврат номера копии или 0. */
|
||
static uint8_t make_flipped(uint8_t src)
|
||
{
|
||
uint8_t blk, dst, saved, count, i;
|
||
uint16_t k;
|
||
const uint8_t *s;
|
||
uint8_t *d;
|
||
|
||
blk = mem_alloc_pages(1);
|
||
if (!blk) return 0;
|
||
dst = mem_get_page(blk, 0);
|
||
|
||
/* Побайтовая копия страницы: источник в W0 (0x0000-0x3FFF), приёмник в
|
||
* W3 (0xC000). Оба окна свободны для резидентного кода W1/W2. */
|
||
saved = _io_page_w3;
|
||
gfx_w0_map(src);
|
||
sprinter_page_w3(dst);
|
||
s = (const uint8_t *)0x0000;
|
||
d = (uint8_t *)0xC000;
|
||
for (k = 0; k < 16384u; k++) d[k] = s[k];
|
||
gfx_w0_unmap();
|
||
|
||
/* Развернуть пиксели каждой ленты — уже в приёмнике (он в W3). */
|
||
count = ((const uint8_t *)0xC000)[ATL_COUNT_OFF];
|
||
for (i = 0; i < count; i++) {
|
||
const uint8_t *e = (const uint8_t *)(0xC000u + ATL_DIR_OFF) + (uint16_t)i * 8;
|
||
uint16_t off = (uint16_t)(e[0] | ((uint16_t)e[1] << 8));
|
||
if (off >= 0x100u && off < 0x4000u) flip_strip(off);
|
||
}
|
||
sprinter_page_w3(saved);
|
||
|
||
if (vf_n < VF_MAX) {
|
||
vf_src[vf_n] = src; vf_dst[vf_n] = dst; vf_blk[vf_n] = blk; vf_n++;
|
||
}
|
||
return dst;
|
||
}
|
||
|
||
uint8_t pop_vflip_page(uint8_t src)
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < vf_n; i++)
|
||
if (vf_src[i] == src) return vf_dst[i];
|
||
return make_flipped(src);
|
||
}
|
||
|
||
void pop_vflip_reset(void)
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < vf_n; i++) mem_free_block(vf_blk[i]);
|
||
vf_n = 0;
|
||
}
|