PoP roomtest: пики — редрой каждый кадр, полный draw_tile; пламя без heal
Выдвинутая пика живёт ТОЛЬКО в видео-ОЗУ (банк SPRITE, в запечённый фон не входит), а kid_heal каждый кадр возвращает под спрайтом Кида печёный фон — то есть стирает попавшие под него части остриёв. Редрой «только при смене видимой сигнатуры» их не возвращал: hold держится 15 кадров (modif 0x8F..0x81) с неизменным кадром 5. Итог по дампу VRAM (комната 14, Kid на (2,7), modif 0x8E): у спрайта 132 стёрто третье остриё (x 245..247), у 138 — прямое целиком (x 258..260) и низ наклонного, страницы дабл-буфера расходились на 8 пикселей. SDLPoP: animate_spike (seg007:0353) зовёт redraw_21h БЕЗУСЛОВНО, вне всяких if — редрой каждый кадр, пока trob жив. pop_spike_redraw переписан на честный redraw_tile_height (seg007:0218): heal + ПОЛНЫЙ draw_tile своего тайла и правого соседа. Рисовать только два спрайта остриёв нельзя — теряется порядок слоёв внутри тайла (draw_tile_anim_right идёт ПЕРВЫМ, база и fore соседа ложатся поверх), и остриё лезло на колонну (2,9). draw_tile заодно сам восстанавливает вклад соседних пик (ветка lcode==2), так что две пики подряд больше не гасят друг друга. Пламя факела: heal убран. Оригинал рисует его blitters_0_no_transp (seg008 draw_tile_anim_right), все 9 кадров лежат на общем канвасе 16x18 и бокс центрирован в ячейке — непрозрачный блит сам полностью накрывает предыдущий кадр. Кадры пакуются с opaque=True (POT_FLAME_IDS). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -626,28 +626,21 @@ void pop_loose_shake_draw(int row, int col)
|
||||
* heal берёт печёный статич. фон, поверх — динамич. кадр (шипы над полом). */
|
||||
void pop_spike_redraw(int row, int col)
|
||||
{
|
||||
int x = COL_XH[col] * 8;
|
||||
int dmy = 63 * row + 62; /* как в draw_tile (dby-3) */
|
||||
uint8_t sm = g_bg[row * 10 + col]; /* живой modif пики (room_modif) */
|
||||
uint8_t sf = (sm & 0x80) ? 5 : sm; /* видимый кадр (hold 0x80..0x8F → 5) */
|
||||
/* Шипы выдвигаются выше линии пола (env_b на dmy-7) — heal с запасом
|
||||
* вверх (63*row+34 .. +74) покрывает и остриё, и сам пол; ширина 64 =
|
||||
* тайл + правый сосед (остриё рисуется в его ячейке).
|
||||
* heal вернул из ОЗУ-копии ВЕСЬ печёный статичный фон (база пик 127, пол,
|
||||
* грани соседей — всё, что рисовал бы полный draw_tile при modif=0).
|
||||
* Единственное, что реально анимируется — два спрайта острия (LEFT в своей
|
||||
* ячейке + RIGHT в соседней, оба по sf). Поэтому вместо 2×draw_tile
|
||||
* (десятки блитов статики поверх идентичного фона = идемпотентно) рисуем
|
||||
* РОВНО эти два острия — пиксель-в-пиксель тот же результат, во много раз
|
||||
* дешевле (порт redraw_21h: draw_tile_anim + draw_tile_anim_right). */
|
||||
int x = COL_XH[col] * 8;
|
||||
/* Порт redraw_21h -> redraw_tile_height (seg007:0218): wipe + ПОЛНЫЙ
|
||||
* redraw ДВУХ тайлов — своего и правого соседа (остриё рисуется в его
|
||||
* ячейке). Именно ПОЛНЫЙ: рисовать только два спрайта остриёв нельзя —
|
||||
* порядок слоёв внутри тайла (draw_tile_anim_right идёт ПЕРВЫМ, база и
|
||||
* fore соседа ложатся ПОВЕРХ) тогда теряется, и остриё лезет на колонну
|
||||
* справа. Заодно draw_tile сам восстанавливает вклад соседних пик:
|
||||
* в ячейке col — правое остриё пики col-1 (ветка lcode==2).
|
||||
* Шипы выдвигаются выше линии пола (dmy-7), поэтому heal с запасом вверх
|
||||
* (63*row+34 .. +74); ширина 64 = обе ячейки. */
|
||||
heal_off(x, 63 * row + 34, 64, 40);
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
if (sf > 0 && sf < 10) {
|
||||
if (SPIKES_FRAM_LEFT[sf]) /* остриё в своей ячейке */
|
||||
env_b(SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
||||
if (col + 1 < 10 && SPIKES_FRAM_RIGHT[sf]) /* остриё в ячейке соседа */
|
||||
env_b(SPIKES_FRAM_RIGHT[sf], COL_XH[col + 1] * 8, dmy - 7);
|
||||
}
|
||||
draw_tile(row, col);
|
||||
if (col + 1 < 10)
|
||||
draw_tile(row, col + 1);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
|
||||
@@ -723,7 +716,10 @@ void pop_torch_draw(int row, int col, uint8_t modif)
|
||||
int x = (col + 1 < 10 ? COL_XH[col + 1] : COL_XH[col] + 4) * 8 + 8;
|
||||
int yb = 63 * row + 62 - 40;
|
||||
if (modif >= 9) return;
|
||||
heal_off(x - 1, yb - 18, 18, 19);
|
||||
/* heal НЕ нужен: оригинал рисует пламя blitters_0_no_transp (seg008
|
||||
* draw_tile_anim_right), и все 9 кадров лежат на общем канвасе 16x18 —
|
||||
* непрозрачный блит сам полностью накрывает предыдущий кадр. Кадры
|
||||
* упакованы с opaque=True (pop_pack_bg POT_FLAME_IDS). */
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
pot_b((uint8_t)(modif + 1), x, yb);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
|
||||
@@ -389,26 +389,34 @@ void pop_process_trobs(uint8_t cur_room)
|
||||
if (code == TILE_POTION) pop_potion_draw(trow, tcol, mod[tp]);
|
||||
else pop_torch_draw(trow, tcol, mod[tp]);
|
||||
}
|
||||
if (room == cur_room &&
|
||||
(code == TILE_SPIKE || code == TILE_OPENER || code == TILE_CLOSER)) {
|
||||
/* Редрой ТОЛЬКО при изменении видимого состояния (redraw_11h из
|
||||
* SDLPoP). spike: draw-сигнатура = modifier; button: = «нажата»
|
||||
* (таймер связи >1). Пока Кид держит кнопку — сигнатура стабильна
|
||||
* → редроя нет. При изменении рисуем ТЕКУЩУЮ страницу сразу +
|
||||
* <rest>=1 (вторая страница дабл-буфера следующим кадром через
|
||||
* rest-цикл). tp/10,tp%10 считаем только в момент редроя (редко),
|
||||
* а не каждый кадр. */
|
||||
/* Сигнатура = ВИДИМЫЙ кадр, а не сырой modifier. Для пики кадр =
|
||||
* (mod&0x80)?5:mod (как в draw_tile): весь ретракт-hold 0x80..0x8F
|
||||
* (16 значений modifier) рисуется одним кадром 5 → сырой modifier
|
||||
* дал бы 16 ложных редроёв «выдвинутой» неподвижной пики. */
|
||||
uint8_t sig = (code == TILE_SPIKE)
|
||||
? (uint8_t)((mod[tp] & 0x80) ? 5 : mod[tp])
|
||||
: (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
|
||||
if (room == cur_room && code == TILE_SPIKE) {
|
||||
/* ПИКИ — редрой КАЖДЫЙ кадр, пока trob жив: animate_spike
|
||||
* (seg007:0353) зовёт redraw_21h БЕЗУСЛОВНО, а не по смене кадра.
|
||||
* Это не «лишняя работа оригинала»: выдвинутая пика живёт только в
|
||||
* видео-ОЗУ (банк SPRITE, в фон не запечена), а kid_heal каждый кадр
|
||||
* возвращает под спрайтом Кида ПЕЧЁНЫЙ фон — то есть стирает те
|
||||
* части остриёв, что попали под его прямоугольник. При редрое
|
||||
* по смене кадра пика держится 15 кадров (modif 0x8F..0x81) с
|
||||
* неизменной сигнатурой, и стёртое уже не возвращается: рядом с
|
||||
* Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной
|
||||
* странице дабл-буфера из двух). */
|
||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
||||
pop_spike_redraw(trow, tcol);
|
||||
/* Последний кадр (пика убралась, trob умирает) — на ВТОРУЮ страницу
|
||||
* его уже никто не положит: ставим отложенный редрой. */
|
||||
if (type < 0) { spike_rest[tp] = 1; rest_pending++; }
|
||||
}
|
||||
if (room == cur_room && (code == TILE_OPENER || code == TILE_CLOSER)) {
|
||||
/* КНОПКА — редрой только при смене видимого состояния (нажата =
|
||||
* таймер связи >1): пока Кид её держит, картинка стабильна.
|
||||
* Рисуем ТЕКУЩУЮ страницу сразу + <rest>=1 (вторая страница
|
||||
* дабл-буфера следующим кадром через rest-цикл). tp/10,tp%10
|
||||
* считаем только в момент редроя (редко), а не каждый кадр. */
|
||||
uint8_t sig = (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
|
||||
if (sig != trob_drawn[tp]) {
|
||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
||||
if (code == TILE_SPIKE) { pop_spike_redraw(trow, tcol); spike_rest[tp] = 1; rest_pending++; }
|
||||
else { pop_button_redraw(trow, tcol); button_rest[tp] = 1; rest_pending++; }
|
||||
pop_button_redraw(trow, tcol);
|
||||
button_rest[tp] = 1; rest_pending++;
|
||||
trob_drawn[tp] = sig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ __sfr __at (0xFE) io_border;
|
||||
/* Тест-старт для отладки пик (Фаза S): комната 6 = тестовая (пики в col3/col4
|
||||
* row2, шахта col3). Kid на полу row0 col1, идёт вправо → падает в шахту →
|
||||
* пики. TODO: вернуть на pop_level_start_room (комната 1) после отладки. */
|
||||
#define START_ROOM 20 /* ОТЛАДКА: кнопка-closer (1,4) — отрисовка нажатой */
|
||||
#define START_COL 8
|
||||
#define START_ROW 1
|
||||
#define START_ROOM 14 /* ОТЛАДКА: пики (2,7)/(2,8) — редрой каждый кадр */
|
||||
#define START_COL 9
|
||||
#define START_ROW 2
|
||||
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
|
||||
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
|
||||
|
||||
|
||||
@@ -194,10 +194,11 @@ def load_indexed(chtab, id, opaque=False):
|
||||
return im.width, im.height, out
|
||||
|
||||
|
||||
def load_potion(id, mono=None):
|
||||
def load_potion(id, mono=None, opaque=False):
|
||||
"""chtab_1: res(150+id).png из data/PRINCE. mono=None — обычный спрайт
|
||||
(палитра chtab_1 -> слоты POT_PAL_BASE+i); mono=цвет — силуэт этим цветом
|
||||
(порт blitters_40h_mono)."""
|
||||
(порт blitters_40h_mono); opaque=True — пиксель 0 остаётся ЦВЕТОМ
|
||||
(порт blitters_0_no_transp, см. POT_FLAME_IDS)."""
|
||||
path = os.path.join(os.path.dirname(R.VDUNGEON_DIR), POT_DIR_NAME,
|
||||
f"res{150 + id}.png")
|
||||
if not os.path.exists(path):
|
||||
@@ -206,10 +207,12 @@ def load_potion(id, mono=None):
|
||||
if im.mode != "P":
|
||||
die(f"res{150+id}: mode {im.mode}, ожидался P")
|
||||
src = im.tobytes()
|
||||
if mono is None:
|
||||
out = bytes((0xFF if v == 0 else POT_PAL_BASE + v) for v in src)
|
||||
else:
|
||||
if mono is not None:
|
||||
out = bytes((0xFF if v == 0 else mono) for v in src)
|
||||
elif opaque:
|
||||
out = bytes((POT_PAL_BASE + v) for v in src)
|
||||
else:
|
||||
out = bytes((0xFF if v == 0 else POT_PAL_BASE + v) for v in src)
|
||||
return im.width, im.height, out
|
||||
|
||||
|
||||
@@ -399,6 +402,12 @@ def main():
|
||||
pot_entries[id] = load_potion(id, POT_BUBBLE_COLOR)
|
||||
elif id == POT_MASK_ID:
|
||||
pot_entries[id] = load_potion(id, POT_MASK_COLOR)
|
||||
elif id in POT_FLAME_IDS:
|
||||
# Пламя оригинал рисует blitters_0_no_transp (seg008
|
||||
# draw_tile_anim_right) — непрозрачным боксом поверх всего, что
|
||||
# сзади. Все 9 кадров лежат на одном канвасе 16x18, поэтому
|
||||
# непрозрачный блит сам стирает предыдущий кадр: heal не нужен.
|
||||
pot_entries[id] = load_potion(id, opaque=True)
|
||||
else:
|
||||
pot_entries[id] = load_potion(id)
|
||||
cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_pot.atl"), pot_entries)
|
||||
|
||||
Reference in New Issue
Block a user