From 4a939e42f8384b06365b20f65fee4e8fc89f912a Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 26 Aug 2026 09:28:36 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=B0=D1=82=D1=81=D1=86=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B:=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B8=D0=BD=D0=B3=D0=B8,=20?= =?UTF-8?q?=D1=87=D0=B0=D1=81=D1=8B=20=D0=B8=20=C2=AB=D0=B2=D1=80=D0=B5?= =?UTF-8?q?=D0=BC=D1=8F=20=D0=B2=D1=8B=D1=88=D0=BB=D0=BE=C2=BB=20=E2=80=94?= =?UTF-8?q?=20=D0=BF=D0=BE=20=D0=BE=D1=80=D0=B8=D0=B3=D0=B8=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D1=83;=20=D1=82=D1=83=D0=BC=D0=B1=D0=BB=D0=B5=D1=80=20?= =?UTF-8?q?=D0=BC=D1=83=D0=B7=D1=8B=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ТАЙМИНГИ. Треки заставок между уровнями длиннее самих сцен (27 — 10,7 с против 2,6 с картинки), и оригинал их не обрывает: load_intro гасит экран и возвращает управление, а звук продолжает играть поверх загрузки уровня и первых секунд игры (seg001:690). Мы глушили CBL на выходе — музыка обрывалась на полуслове. Больше не глушим. «ВРЕМЯ ВЫШЛО». Оригинал (seg001:04D3) не зовёт ни одного init_*: на экране пустая комната и ОСТАНОВИВШИЕСЯ часы (state 7, струйки песка нет). Мы показывали готовую композицию с принцессой и полными часами — противоположное по смыслу. Теперь это живая сцена без персонажей, 102 кадра, и трек 36 дослушивается под чёрным экраном, как в оригинале. ПЕСОЧНЫЕ ЧАСЫ. Сцена «времени мало» (cutscene_12) шла статической композицией, куда упаковщик запёк res953 — ПОЛНЫЕ часы. Отсюда и наблюдение «чем меньше времени, тем полнее часы»: картинка была одна и та же независимо от таймера. Сделана живой (cut_scene_12_short: принцесса стоит правее, через два кадра оборачивается), часы берутся от pre_hourglass_state. Заодно: при state 7 струйка песка не рисуется. МУЗЫКА — СВОЙ ТУМБЛЕР. Settings -> MUSIC, Ctrl+M и статус в debug bar. Флаг pop_mus_want отдельный от звукового: музыка у нас поток с диска, и выключают её по другим причинам, чем эффекты; выключение обрывает текущий трек, эффекты продолжают звучать. Строка-заглушка GAMEPLAY PROFILE («VANILLA ONLY») уступила место MUSIC — на десятую строку экрана не хватает. Подписи тумблеров в debug bar сокращены до S:/M:/I:. БАГИ. После меню персонаж оставался невидимым до первого движения: снимок слота продолжал утверждать, что кадр на странице уже нарисован. Общая pop_cd_forget() теперь зовётся после меню, QuickLoad и переворота экрана. «QUICKLOAD» мигал через кадр — pop_status_show_now снимал бит видимой страницы, а её успевало переписать восстановление комнаты. Host-тесты: 15 наборов. Проверено в MAME: debug bar, выход из меню. Co-Authored-By: Claude Opus 5 --- applications/PoP/roomtest/TASKS_OPEN.md | 32 ++++++ applications/PoP/roomtest/_pop_config.h | 7 +- applications/PoP/roomtest/pop_cdraw.c | 19 ++++ applications/PoP/roomtest/pop_cdraw.h | 5 + applications/PoP/roomtest/pop_cheat.h | 3 + applications/PoP/roomtest/pop_config.c | 16 ++- applications/PoP/roomtest/pop_intro.c | 111 ++++++++++++++----- applications/PoP/roomtest/pop_menu.c | 41 +++---- applications/PoP/roomtest/pop_music.c | 7 ++ applications/PoP/roomtest/pop_settings.h | 1 + applications/PoP/roomtest/pop_sfx.c | 8 +- applications/PoP/roomtest/pop_sfx.h | 5 + applications/PoP/roomtest/pop_status.c | 39 +++++-- applications/PoP/roomtest/pop_status.h | 2 + applications/PoP/roomtest/roomtest.c | 12 +- applications/PoP/roomtest/roomtest_cold.c | 25 +++-- applications/PoP/roomtest/tests-host/t_cfg.c | 2 +- 17 files changed, 257 insertions(+), 78 deletions(-) diff --git a/applications/PoP/roomtest/TASKS_OPEN.md b/applications/PoP/roomtest/TASKS_OPEN.md index 290a65b..bb74c28 100644 --- a/applications/PoP/roomtest/TASKS_OPEN.md +++ b/applications/PoP/roomtest/TASKS_OPEN.md @@ -1206,6 +1206,38 @@ N навык M» — отдельная надобность, если пона Фазы З1..З5 и список «проверить артефактом до кодинга» — в доке. +### PAL-ARC. Палитры — в архив своей группы (полишинг) + +Файлов палитр в игре шесть: `KID\kid.pal` (1024 Б), `PV\story.pal`, +`PV\pv.pal` (по 1024), `BG\pop_tile.pal`, `BG\pal_tile.pal` (по 128), +`TITLE\title.pal` (64). Всё, что лежит в `poc/res/tiles/` (36 файлов) и +`bg/pop_bg.pal`, на образ не попадает и кодом не читается — осадок ранних +экспериментов, его стоит просто убрать. + +**Что предлагается.** Класть палитру в архив ТОЙ ЖЕ группы, где её +графика: `kid.pal` → `kid.arc`, оба `*_tile.pal` → `bg.arc`, `title.pal` → +`title.arc`, `story`/`pv.pal` → `pv.arc`. Тогда правило одно на все +ресурсы («файл лежит в архиве своей группы»), и уходят последние шесть +открытий: `pop_bg_load` берёт тайлсет и палитру за одно открытие, заставка +так же. Отдельно ценно, что `kid.pal` перестанет читаться своим `open` на +КАЖДОЙ загрузке уровня (её перезаливает `pop_pal_game_load`). + +**Чего это стоит.** Элемент архива читается только в EMM-страницу, +поэтому нужна маленькая функция «палитра из страницы»: `bank_read` по 256 +байт в буфер и четыре `gfx_pal_load`. Буфер уже есть — `pop_pal_buf` по +0x4000. + +**Что теряется.** Возможность подменить палитру файлом рядом с игрой +(`pal_file_load` сейчас откатывается на `a:\kid.pal`). Решить явно: +либо файл остаётся приоритетным и архив — запасным путём, либо fallback +выбрасывается. + +**Почему НЕ отдельная папка `PAL\` и не один общий атлас палитр.** Папка +не уменьшает числа файлов и рвёт связь «палитра ↔ её графика». Общий +атлас экономил бы открытия только при загрузке всех палитр разом, а они +нужны в разное время: `kid.pal` — каждому уровню, заставочные — только +заставке. + ### MUS-WON. Финальная тема — кольцевой стриминг с диска Трек 56 (`won`, 115 с) — это 1,2 МБ, 78 страниц EMM. Ни в `POP_MUS_PAGES` diff --git a/applications/PoP/roomtest/_pop_config.h b/applications/PoP/roomtest/_pop_config.h index 881f554..cbd7e74 100644 --- a/applications/PoP/roomtest/_pop_config.h +++ b/applications/PoP/roomtest/_pop_config.h @@ -14,11 +14,12 @@ #define POP_CFG_VERSION 1 #define POP_CFG_HEADER_SIZE 7 /* Ранний MS0-файл имел семь полей. Его продолжаем принимать: отсутствующие - * поля берут исторический дефолт (лимит времени ON, отладочная полоса ON), - * а новая запись содержит все девять. */ + * поля берут исторический дефолт (лимит времени ON, отладочная полоса ON, + * музыка ON), а новая запись содержит все десять. */ #define POP_CFG_LEGACY_PAYLOAD_SIZE 7 #define POP_CFG_TIMELIMIT_PAYLOAD_SIZE 8 -#define POP_CFG_PAYLOAD_SIZE 9 +#define POP_CFG_DEBUGBAR_PAYLOAD_SIZE 9 +#define POP_CFG_PAYLOAD_SIZE 10 #define POP_CFG_TAIL_SIZE 2 /* CFG остаётся маленьким. Верхняя граница не даёт ручному мусору занять * стек W2, но оставляет запас для будущих полей версии 1. */ diff --git a/applications/PoP/roomtest/pop_cdraw.c b/applications/PoP/roomtest/pop_cdraw.c index 6de3d35..9f9ec6f 100644 --- a/applications/PoP/roomtest/pop_cdraw.c +++ b/applications/PoP/roomtest/pop_cdraw.c @@ -420,6 +420,25 @@ static void cd_bbox(uint8_t who, uint8_t p, int *x0, int *y0, int *x1, int *y1) } } +/* ЗАБЫТЬ, ЧТО НАРИСОВАНО, на ОБЕИХ страницах. + * + * Звать после всего, что переписало экран мимо кадрового конвейера: меню, + * QuickLoad, переворот картинки. Иначе снимок слота продолжает утверждать, + * что нужный кадр персонажа на странице уже есть, и cd_quiet честно + * пропускает отрисовку — персонаж исчезает до первого своего движения + * (поймано пользователем на выходе из меню; рядом с факелом бага не видно + * ровно потому, что анимация фона метит прямоугольник Кида и пропуск + * снимается сама собой). */ +void pop_cd_forget(void) __banked +{ + uint8_t i; + + for (i = 0; i < POP_CH_N; i++) { + pop_cd[i].valid[0] = pop_cd[i].valid[1] = 0; + pop_cd[i].ovalid[0] = pop_cd[i].ovalid[1] = 0; + } +} + /* «Тихий» слот: спрайт этой страницы цел (heal его не трогал), фон в его * прямоугольнике не трогали, и все входы отрисовки те же, что в момент, * когда этот кадр на эту страницу и рисовали. valid[p] здесь ключевой: он diff --git a/applications/PoP/roomtest/pop_cdraw.h b/applications/PoP/roomtest/pop_cdraw.h index a79c491..8049dc7 100644 --- a/applications/PoP/roomtest/pop_cdraw.h +++ b/applications/PoP/roomtest/pop_cdraw.h @@ -79,6 +79,11 @@ typedef struct { extern pop_cdraw_t pop_cd[POP_CH_N]; +/* Забыть, что нарисовано на обеих страницах: после меню, QuickLoad и любой + * другой перерисовки экрана мимо кадрового конвейера. Без этого пропуск + * перерисовки «тихого» персонажа оставляет его невидимым. */ +void pop_cd_forget(void) __banked; + /* Стереть прошлый кадр персонажа (спрайт + накладные) на ТЕКУЩЕЙ странице. */ void pop_char_heal(uint8_t who) __banked; diff --git a/applications/PoP/roomtest/pop_cheat.h b/applications/PoP/roomtest/pop_cheat.h index 345ce68..4b24108 100644 --- a/applications/PoP/roomtest/pop_cheat.h +++ b/applications/PoP/roomtest/pop_cheat.h @@ -27,6 +27,9 @@ extern uint8_t pop_cheats; /* 0 = выключены, иначе включ /* Ctrl+S (PS/2 set 2: S = 0x1B) — вкл/выкл звук; порт Ctrl+S из SDLPoP * (seg000:657). С Ctrl, а не голая S: у оригинала клавиша именно такая. */ #define KBD_SOUND_TOGGLE 0x1B +/* Ctrl+M — тумблер МУЗЫКИ, парный к Ctrl+S. Отдельный потому, что музыка + * у нас поток с диска: её выключают по другим причинам, чем эффекты. */ +#define KBD_MUSIC_TOGGLE 0x3A /* M (PS/2 set 2) */ #define KBD_CHEAT_NEXTLVL 0x4B /* Shift+L (PS/2 set 2) — следующий уровень */ #define KBD_CHEAT_XDEC 0x54 /* [ (PS/2 set 2) — сдвинуть Кида на 1 px влево */ #define KBD_CHEAT_XINC 0x5B /* ] (PS/2 set 2) — сдвинуть Кида на 1 px вправо */ diff --git a/applications/PoP/roomtest/pop_config.c b/applications/PoP/roomtest/pop_config.c index 4fe4b25..ba9d117 100644 --- a/applications/PoP/roomtest/pop_config.c +++ b/applications/PoP/roomtest/pop_config.c @@ -44,6 +44,7 @@ static uint8_t cfg_checksum(const uint8_t *data, uint16_t size) void pop_settings_defaults(void) __banked { pop_settings.sound_enabled = 1; + pop_settings.music_enabled = 1; pop_settings.speed_mode = POP_SPEED_NORMAL; pop_settings.gameplay_profile = POP_PROFILE_VANILLA; pop_settings.cheats_enabled = 1; /* пока roomtest — dev-сборка */ @@ -66,6 +67,9 @@ void pop_settings_apply(uint8_t sound_ready) __banked * Файл должен отражать фактически применимое состояние. */ pop_settings.sound_enabled = pop_snd_want; } + /* Музыке набор эффектов не нужен: она читается с диска отдельно, + * поэтому применяется всегда, а не по sound_ready. */ + pop_mus_want = pop_settings.music_enabled; } void pop_cfg_encode(uint8_t *buf) @@ -84,6 +88,7 @@ void pop_cfg_encode(uint8_t *buf) buf[13] = (uint8_t)(pop_settings.enhancement_flags >> 8); buf[14] = pop_settings.time_limit_enabled; buf[15] = pop_settings.show_debug_bar; + buf[16] = pop_settings.music_enabled; sum = cfg_checksum(buf + POP_CFG_HEADER_SIZE, POP_CFG_PAYLOAD_SIZE); buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE] = sum; buf[POP_CFG_HEADER_SIZE + POP_CFG_PAYLOAD_SIZE + 1] = (uint8_t)~sum; @@ -113,7 +118,8 @@ uint8_t pop_cfg_decode(const uint8_t *buf, uint16_t size) buf[9] > POP_PROFILE_ENHANCED || buf[10] > 1 || buf[11] > 1) return 0; if (payload_size >= POP_CFG_TIMELIMIT_PAYLOAD_SIZE && buf[14] > 1) return 0; - if (payload_size >= POP_CFG_PAYLOAD_SIZE && buf[15] > 1) return 0; + if (payload_size >= POP_CFG_DEBUGBAR_PAYLOAD_SIZE && buf[15] > 1) return 0; + if (payload_size >= POP_CFG_PAYLOAD_SIZE && buf[16] > 1) return 0; flags = (uint16_t)(buf[12] | ((uint16_t)buf[13] << 8)); pop_settings.sound_enabled = buf[7]; pop_settings.speed_mode = buf[8]; @@ -124,12 +130,14 @@ uint8_t pop_cfg_decode(const uint8_t *buf, uint16_t size) pop_settings.enhancement_flags = flags; /* Старый POP.CFG создавался до настройки таймера (и до тумблера * отладочной полосы): отсутствующее поле означает исторический дефолт — - * ограничение 60 минут и включённую полосу, а не случайное значение в - * памяти. */ + * ограничение 60 минут, включённую полосу и музыку, а не случайное + * значение в памяти. */ pop_settings.time_limit_enabled = (payload_size >= POP_CFG_TIMELIMIT_PAYLOAD_SIZE) ? buf[14] : 1; pop_settings.show_debug_bar = - (payload_size >= POP_CFG_PAYLOAD_SIZE) ? buf[15] : 1; + (payload_size >= POP_CFG_DEBUGBAR_PAYLOAD_SIZE) ? buf[15] : 1; + pop_settings.music_enabled = + (payload_size >= POP_CFG_PAYLOAD_SIZE) ? buf[16] : 1; return 1; } diff --git a/applications/PoP/roomtest/pop_intro.c b/applications/PoP/roomtest/pop_intro.c index c70502e..9aa4a7f 100644 --- a/applications/PoP/roomtest/pop_intro.c +++ b/applications/PoP/roomtest/pop_intro.c @@ -892,13 +892,19 @@ static uint8_t pre_hourglass_state(void) * Статична только поза Princess; draw_princess_room_bg оригинала * каждый логический кадр заново обновляет факелы, звёзды, корпус часов и * одну из трёх фаз сыплющегося песка. */ -static int pre_princess_animated(pop_pre_cutscene_t scene) +/* Живая комната принцессы БЕЗ актёрской механики: готовая композиция (или + * чистый фон, если picture == PV_ROOM_BARE), поверх неё пламя факелов, + * звёзды в окне и песочные часы заданного состояния. На этом держатся и + * сцены перед уровнями, и экран «время вышло» — разница только в картинке, + * состоянии часов и длине. + * + * hg_state: 0 — часов нет, 7 — время вышло (пустые, без струйки песка). */ +#define PV_ROOM_BARE 0xFF + +static int pre_room_animated(uint8_t picture, uint8_t state, uint8_t frames) { atlas_t hourglass, torch; gfx_rect_t full; - uint8_t picture = (uint8_t)(scene == POP_PRE_4 ? - INTRO_PV_LYING : INTRO_PV_STAND); - uint8_t state = pre_hourglass_state(); uint8_t torch_left = 1, torch_right = 6, sand = 0, frame; uint8_t star_phase[6] = { 1, 1, 1, 1, 1, 1 }; intro_skip_t skip; @@ -908,8 +914,12 @@ static int pre_princess_animated(pop_pre_cutscene_t scene) intro_commit_palette(); /* Обе shadow-страницы содержат чистую статическую сцену. Динамика ниже * рисуется GFX_BANK_SPRITE и потому не оседает в этом источнике. */ - if (intro_draw_page(picture, 0) != 0 || - intro_draw_page(picture, 1) != 0) return -1; + if (picture == PV_ROOM_BARE) { + if (intro_pv_draw_base() != 0) return -1; + } else if (intro_draw_page(picture, 0) != 0 || + intro_draw_page(picture, 1) != 0) { + return -1; + } { uint8_t tbl[ARC_PV_COUNT * POP_ARC_REC]; pop_arc_t arc; @@ -927,7 +937,7 @@ static int pre_princess_animated(pop_pre_cutscene_t scene) result = 0; full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT; intro_skip_begin(&skip); - for (frame = 0; frame < 26; frame++) { + for (frame = 0; frame < frames; frame++) { uint8_t back, hold, star; if (intro_skip_requested(&skip)) { result = 1; break; } @@ -938,8 +948,13 @@ static int pre_princess_animated(pop_pre_cutscene_t scene) pv_blit_floor(&torch, torch_left, 93, 116); pv_blit_floor(&torch, torch_right, 211, 116); pv_draw_stars(star_phase); - pv_blit_floor(&hourglass, state, 152, 168); - pv_blit_floor(&hourglass, (uint8_t)(8 + sand), 160, 164); + if (state) { + pv_blit_floor(&hourglass, state, 152, 168); + /* Струйка песка есть, пока часы идут: при state 7 время уже + * вышло (draw_hourglass, sandflow = -1). */ + if (state < 7) + pv_blit_floor(&hourglass, (uint8_t)(8 + sand), 160, 164); + } pv_blit_floor(&hourglass, 0, 240, 167); /* передняя колонна */ gfx_wait_vsync(); gfx_set_visible_page(back); @@ -1042,7 +1057,7 @@ static void intro_restore_game_palette(void) * 17..29 в a2. Номер кадра при этом остаётся исходным image-id. */ #define CUT_PV2_SPLIT 17 /* Один логический кадр катсцены = 5 vsync, как у остальных PV-сцен - * (cutscene_frame_time SDLPoP; см. pre_princess_animated). */ + * (cutscene_frame_time SDLPoP; см. pre_room_animated). */ #define CUT_FRAME_VSYNC 5 static atlas_t cut_pv1, cut_pv2a, cut_pv2b, cut_hg, cut_torch; @@ -1237,10 +1252,13 @@ static uint8_t cut_run(uint16_t frames, uint8_t kid_visible) pv_draw_stars(cut_star_phase); /* hourglass_state == 0 — часов в комнате нет вовсе (reset_cutscene * SDLPoP ставит state=0 и sandflow=-1). Так идёт финальная встреча - * с принцессой: время уже не считается, это победа. */ + * с принцессой: время уже не считается, это победа. + * state 7 — время ВЫШЛО: часы стоят пустыми, и струйки песка в них + * тоже нет (draw_hourglass: `if (hourglass_state >= 7) return;`). */ if (cut_hg_state) { pv_blit_floor(&cut_hg, cut_hg_state, 152, 168); - pv_blit_floor(&cut_hg, (uint8_t)(8 + cut_sand), 160, 164); + if (cut_hg_state < 7) + pv_blit_floor(&cut_hg, (uint8_t)(8 + cut_sand), 160, 164); } cut_blit_princess(&Guard, &pr_fr); if (kid_visible) cut_blit_kid(&Kid, &kid_fr); @@ -1342,6 +1360,27 @@ static int cut_scene_9(void) return 0; } +/* cutscene_12 (seg001:0525), ветка «времени мало»: принцесса стоит правее + * обычного, через два кадра оборачивается — и всё. Живой её делает та же + * машинка, что сцены 8/9: часы здесь обязаны показывать ОСТАВШЕЕСЯ время, + * а не запечённую в композицию картинку. */ +static int cut_scene_12_short(void) +{ + uint8_t aborted; + + if (cut_begin(pre_hourglass_state()) != 0) return -1; + /* init_princess_x156 (seg001:0230) = init_princess + x = 156. */ + cut_shad_init(156, 166, 94); + pop_pal_fade_in(INTRO_FADE); + aborted = cut_run(2, 0); + if (!aborted) { + cut_shad_seq(98); /* princess turn around [PV1] */ + aborted = cut_run(24, 0); + } + cut_end(aborted); + return 0; +} + /* end_sequence_anim (seg001:041C): Кид вбегает к принцессе, она * поворачивается и обнимает его; следом появляется мышь. */ static int cut_ending(void) @@ -1522,7 +1561,15 @@ void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked const pop_cs_cmd_t *script = 0; pop_sfx_pause(); - /* Музыка сцены — как в оригинале: 27 для 2/4/6/12 (seg001:401), + /* МУЗЫКА ПЕРЕЖИВАЕТ СЦЕНУ. Треки заставок между уровнями длиннее самих + * сцен (27 — 10,7 с против 2,6 с картинки), и оригинал их не обрывает: + * load_intro просто гасит экран и возвращает управление, а звук + * продолжает играть уже поверх загрузки уровня и первых секунд игры + * (seg001:690). Мы глушили CBL на выходе — музыка обрывалась на + * полуслове. Теперь не глушим: доиграет сама, а следующая загрузка + * трека всё равно освободит слот. + * + * Какой именно трек — как в оригинале: 27 для 2/4/6/12 (seg001:401), * 35 для 8/9 (seg001:298), 40 для короткой «времени мало» (seg001:388). * Экран в этот момент чёрный, поэтому читаем трек целиком: пауза на * диск здесь никому не мешает. */ @@ -1537,20 +1584,22 @@ void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked } } if (scene == POP_PRE_2_6 || scene == POP_PRE_4 || scene == POP_PRE_12) { - if (pre_princess_animated(scene) >= 0) { - pop_music_free(); - pop_sfx_pause(); + if (pre_room_animated((uint8_t)(scene == POP_PRE_4 ? INTRO_PV_LYING + : INTRO_PV_STAND), + pre_hourglass_state(), 26) >= 0) { intro_restore_game_palette(); return; } /* Неполный HDD: статический fallback сохраняет корректные 130 тиков. */ } + if (scene == POP_PRE_12_SHORT && cut_scene_12_short() == 0) { + intro_restore_game_palette(); + return; + } /* Сцены 8/9 — живая анимация мыши и принцессы (cutscene_8/cutscene_9). */ if (scene == POP_PRE_8 || scene == POP_PRE_9) { int done = (scene == POP_PRE_8) ? cut_scene_8() : cut_scene_9(); if (done == 0) { - pop_music_free(); - pop_sfx_pause(); intro_restore_game_palette(); return; } @@ -1560,27 +1609,35 @@ void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked else if (scene == POP_PRE_8) script = pre_8; else if (scene == POP_PRE_9) script = pre_9; else if (scene == POP_PRE_12_SHORT) script = pre_12_short; - if (!script) { - pop_music_free(); - pop_sfx_pause(); - return; - } + if (!script) return; intro_run(script); - pop_music_free(); - pop_sfx_pause(); intro_restore_game_palette(); } void pop_time_expired_show(void) __banked { pop_sfx_pause(); - /* time_expired (seg001:365) начинается с play_sound(sound_36). */ + /* time_expired (seg001:04D3) начинается с play_sound(sound_36). */ if (pop_music_load(36) == 0) { (void)pop_sfx_start(); pop_music_play(); } - intro_run(time_expired_script); + /* ПРИНЦЕССЫ ЗДЕСЬ НЕТ. Оригинал не зовёт ни одного init_*: экран — + * это пустая комната и ОСТАНОВИВШИЕСЯ часы (state 7, струйки песка + * нет). Мы показывали готовую композицию с принцессой и полными + * часами — то есть ровно противоположное по смыслу. + * Длина — 2 + 100 кадров cutscene, как в оригинале. */ + if (pre_room_animated(PV_ROOM_BARE, 7, 102) < 0) + intro_run(time_expired_script); /* нет ресурсов — старый путь */ + /* Оригинал ждёт КОНЦА ТРЕКА уже под чёрным экраном (seg001:374). */ + { + intro_skip_t tail; + + intro_skip_begin(&tail); + while (pop_music_busy() && !intro_skip_requested(&tail)) + pop_wait_edge(); + } pop_music_free(); pop_sfx_pause(); intro_restore_game_palette(); diff --git a/applications/PoP/roomtest/pop_menu.c b/applications/PoP/roomtest/pop_menu.c index 590e339..919182d 100644 --- a/applications/PoP/roomtest/pop_menu.c +++ b/applications/PoP/roomtest/pop_menu.c @@ -87,13 +87,13 @@ 9 * SET_STEP + SEL_BOT_DY + 1) #define SET_TITLE_BASE (UI_CENTER_TOP(SET_BLOCK_H) + POP_FONT_BIG_ASCENT) #define SET_HEAD1 (SET_TITLE_BASE + SET_HEAD_GAP) -#define SET_HEAD2 (SET_HEAD1 + 3 * SET_STEP + SET_HEAD_GAP) -#define SET_HEAD3 (SET_HEAD2 + 4 * SET_STEP + SET_HEAD_GAP) +#define SET_HEAD2 (SET_HEAD1 + 4 * SET_STEP + SET_HEAD_GAP) +#define SET_HEAD3 (SET_HEAD2 + 3 * SET_STEP + SET_HEAD_GAP) /* CONTROLS: число строк зависит от того, включены ли читы, поэтому верх * блока считается в рантайме. */ #define CTRL_TITLE_GAP 20 -#define CTRL_N_BASE 6 /* общие клавиши */ +#define CTRL_N_BASE 7 /* общие клавиши */ #define CTRL_N_CHEAT 5 /* дополнительные строки читов */ #define CTRL_COL_KEY 45 #define CTRL_COL_DESC 145 @@ -267,9 +267,9 @@ static void menu_draw(uint8_t sel) * значение помещается (на голом литерале 218 — молчит). */ static const uint8_t settings_y[SETTINGS_N_ITEMS] = { (uint8_t)(SET_HEAD1 + 1 * SET_STEP), (uint8_t)(SET_HEAD1 + 2 * SET_STEP), - (uint8_t)(SET_HEAD1 + 3 * SET_STEP), + (uint8_t)(SET_HEAD1 + 3 * SET_STEP), (uint8_t)(SET_HEAD1 + 4 * SET_STEP), (uint8_t)(SET_HEAD2 + 1 * SET_STEP), (uint8_t)(SET_HEAD2 + 2 * SET_STEP), - (uint8_t)(SET_HEAD2 + 3 * SET_STEP), (uint8_t)(SET_HEAD2 + 4 * SET_STEP), + (uint8_t)(SET_HEAD2 + 3 * SET_STEP), (uint8_t)(SET_HEAD3 + 1 * SET_STEP), (uint8_t)(SET_HEAD3 + 2 * SET_STEP) }; @@ -293,15 +293,15 @@ static void settings_draw(uint8_t sel) "SETTINGS"); pop_text_draw_mapped(POP_TEXT_SMALL, 60, SET_HEAD1, "GENERAL"); settings_line(0, "SOUND", pop_settings.sound_enabled ? "ON" : "OFF", sel == 0); - settings_line(1, "DEBUG BAR", - pop_settings.show_debug_bar ? "ON" : "OFF", sel == 1); - settings_line(2, "RESTORE DEFAULTS...", 0, sel == 2); + settings_line(1, "MUSIC", pop_settings.music_enabled ? "ON" : "OFF", sel == 1); + settings_line(2, "DEBUG BAR", + pop_settings.show_debug_bar ? "ON" : "OFF", sel == 2); + settings_line(3, "RESTORE DEFAULTS...", 0, sel == 3); pop_text_draw_mapped(POP_TEXT_SMALL, 60, SET_HEAD2, "GAMEPLAY"); - settings_line(3, "TIME LIMIT", + settings_line(4, "TIME LIMIT", pop_settings.time_limit_enabled ? "60 MIN" : "UNLIMITED", - sel == 3); - settings_line(4, "SPEED", speed_text[pop_settings.speed_mode], sel == 4); - settings_line(5, "GAMEPLAY PROFILE", "VANILLA", sel == 5); + sel == 4); + settings_line(5, "SPEED", speed_text[pop_settings.speed_mode], sel == 5); settings_line(6, "CHEATS", pop_settings.cheats_enabled ? "ON" : "OFF", sel == 6); pop_text_draw_mapped(POP_TEXT_SMALL, 60, SET_HEAD3, "CONTROLS"); settings_line(7, "SHOW KEY BINDINGS", 0, sel == 7); @@ -313,12 +313,13 @@ static void settings_draw(uint8_t sel) /* Две колонки «клавиша — что делает». Первые CTRL_N_BASE строк общие, за * ними идут читовые: их показываем, только когда читы включены. */ static const char * const ctrl_key[CTRL_N_BASE + CTRL_N_CHEAT] = { - "ARROWS", "SHIFT", "ESC", "F6 / F9", "CTRL+S", "P", + "ARROWS", "SHIFT", "ESC", "F6 / F9", "CTRL+S", "CTRL+M", "P", "K", "I", "SHIFT+L", "U", "F7 / F8" }; static const char * const ctrl_desc[CTRL_N_BASE + CTRL_N_CHEAT] = { - "MOVE / JUMP", "ACTION", "PAUSE MENU", "SAVE / LOAD", "SOUND", "SPEED", + "MOVE / JUMP", "ACTION", "PAUSE MENU", "SAVE / LOAD", "SOUND", "MUSIC", + "SPEED", "KILL GUARD", "IMMORTAL", "NEXT LEVEL", "FLIP SCREEN", "TIME - / +" }; @@ -521,25 +522,25 @@ static uint8_t settings_process(void) /* Left/right принадлежат только числовой настройке скорости; * остальные строки меняются Enter либо Space, чтобы стрелкой не * открыть случайно Controls и не сбросить параметры. */ - if ((ent && !ent_prev) || (sel == 4 && delta)) { + if ((ent && !ent_prev) || (sel == 5 && delta)) { if (sel == 0) { pop_settings.sound_enabled ^= 1; change = 1; } else if (sel == 1) { - pop_settings.show_debug_bar ^= 1; change = 1; + pop_settings.music_enabled ^= 1; change = 1; } else if (sel == 2) { + pop_settings.show_debug_bar ^= 1; change = 1; + } else if (sel == 3) { if (ui_confirm("RESTORE DEFAULTS?")) { pop_settings_defaults(); change = 1; } redraw = 1; sound = MENU_SND_SELECT; - } else if (sel == 3) { - pop_settings.time_limit_enabled ^= 1; change = 1; } else if (sel == 4) { + pop_settings.time_limit_enabled ^= 1; change = 1; + } else if (sel == 5) { uint8_t speed = pop_settings.speed_mode; if (delta < 0) speed = (uint8_t)(speed ? speed - 1 : POP_SPEED_FASTEST); else speed = (uint8_t)(speed == POP_SPEED_FASTEST ? POP_SPEED_NORMAL : speed + 1); pop_settings.speed_mode = speed; change = 1; - } else if (sel == 5) { - ui_notice("VANILLA ONLY"); redraw = 1; sound = MENU_SND_SELECT; } else if (sel == 6) { pop_settings.cheats_enabled ^= 1; change = 1; } else if (sel == 7) { diff --git a/applications/PoP/roomtest/pop_music.c b/applications/PoP/roomtest/pop_music.c index fd1b3d9..6459c66 100644 --- a/applications/PoP/roomtest/pop_music.c +++ b/applications/PoP/roomtest/pop_music.c @@ -219,6 +219,13 @@ void pop_music_service(void) __banked /* Один вызов на кадр из ЛЮБОГО цикла — игрового, интро, катсцены. * Заявку кладёт pop_sfx_play (звук без оцифровки = музыка оригинала), * а диск трогаем только здесь. */ + if (!pop_mus_want) { + /* Выключили на ходу: оборвать поток и замолчать. Эффекты при этом + * продолжают звучать — у них свой тумблер. */ + if (pop_mus_req) pop_mus_req = 0; + if (ld_busy || pop_mus_left) pop_music_free(); + return; + } if (pop_mus_req) { uint8_t id = pop_mus_req; diff --git a/applications/PoP/roomtest/pop_settings.h b/applications/PoP/roomtest/pop_settings.h index 56748e8..bd2889e 100644 --- a/applications/PoP/roomtest/pop_settings.h +++ b/applications/PoP/roomtest/pop_settings.h @@ -19,6 +19,7 @@ typedef enum { typedef struct { uint8_t sound_enabled; /* 0/1 — зеркало звука (Ctrl+S / pop_sfx) */ + uint8_t music_enabled; /* 0/1 — зеркало музыки (Ctrl+M / pop_mus_want) */ uint8_t speed_mode; /* POP_SPEED_* (pop_pace.h), 0..2 */ uint8_t gameplay_profile; /* pop_gameplay_profile_t */ uint8_t cheats_enabled; /* 0/1 — активны ли чит-клавиши */ diff --git a/applications/PoP/roomtest/pop_sfx.c b/applications/PoP/roomtest/pop_sfx.c index e6aecd2..032e0f5 100644 --- a/applications/PoP/roomtest/pop_sfx.c +++ b/applications/PoP/roomtest/pop_sfx.c @@ -59,6 +59,12 @@ volatile uint8_t pop_snd_tick; * (нулевого трека не бывает). Ставит pop_sfx_play, снимает главный цикл. */ uint8_t pop_mus_req; +/* ХОЧЕТ ЛИ ПОЛЬЗОВАТЕЛЬ МУЗЫКУ (Settings / Ctrl+M) — отдельно от звука. + * Оригинал такого разделения не знал (там один тумблер на всё), но у нас + * музыка это ПОТОК С ДИСКА: кому мешает подкачка или просто не нужен + * саундтрек, гасит её, не теряя эффектов. */ +uint8_t pop_mus_want; + static volatile uint8_t sfx_pg; /* индекс в pop_snd_page[] */ static volatile uint16_t sfx_ptr; /* адрес в окне W0: 0x0000..0x3FFF */ static volatile uint16_t sfx_left; /* сколько байт осталось (кратно 128) */ @@ -237,7 +243,7 @@ void pop_sfx_play(uint8_t id) * В конкурсе эффектов такой id не участвует: он не глушит живой звук * (порт проверки `if (NULL == sound_pointers[id]) return;`). */ if (pop_snd_tbl[id].len == 0) { - pop_mus_req = id; + if (pop_mus_want) pop_mus_req = id; return; } if (!snd_next1 || snd_prio[id] <= snd_prio[snd_next1 - 1]) diff --git a/applications/PoP/roomtest/pop_sfx.h b/applications/PoP/roomtest/pop_sfx.h index 0a7e86a..42adc10 100644 --- a/applications/PoP/roomtest/pop_sfx.h +++ b/applications/PoP/roomtest/pop_sfx.h @@ -65,6 +65,11 @@ extern volatile uint8_t pop_snd_tick; * недопустимо. 0 — заявки нет. Снимать обязан тот, кто обработал. */ extern uint8_t pop_mus_req; +/* Тумблер МУЗЫКИ (Settings / Ctrl+M), независимый от тумблера эффектов: + * музыка у нас — поток с диска, и выключают её по другим причинам. + * 0 — заявки не принимаются, играющий трек глушится (pop_music_service). */ +extern uint8_t pop_mus_want; + /* Заявить эффект с номером id (0..56 — нумерация оригинала, см. * sound_plan.md). НЕ запускает его немедленно и НЕ перебивает играющий: * это порт play_sound (seg000:12C5), который лишь НОМИНИРУЕТ кандидата на diff --git a/applications/PoP/roomtest/pop_status.c b/applications/PoP/roomtest/pop_status.c index 38704e6..1388a42 100644 --- a/applications/PoP/roomtest/pop_status.c +++ b/applications/PoP/roomtest/pop_status.c @@ -73,6 +73,8 @@ static const char * const status_text[POP_MSG_N] = { "NO QUICKLOAD", "SOUND ON", "SOUND OFF", + "MUSIC ON", + "MUSIC OFF", "", /* LEVEL N — собирается с номером */ "", /* N MINUTES LEFT */ "", /* N SECONDS LEFT */ @@ -113,10 +115,15 @@ static uint8_t st_arg; /* номер уровня для POP_MSG_LEVEL #define R_VAL_ROOM 84 /* зона 71..83 */ #define X_LBL_SPEED 84 /* ", Speed:" 84..116 */ #define R_VAL_SPEED 152 /* зона 117..151 */ -#define X_LBL_SOUND 152 /* ", Sound:" 152..184 */ -#define R_VAL_SOUND 202 /* зона 185..201 */ -#define X_LBL_IMM 202 /* ", Immortal" 202..243 */ -#define R_VAL_IMM 253 /* зона 244..252: цифре нужен зазор в пробел */ +/* Три тумблера подряд — звук, музыка, бессмертие. Подписи сокращены до + * буквы с двоеточием: полные слова («Sound», «Immortal») занимали столько, + * что на музыку места уже не оставалось, а смысл и так читается. */ +#define X_LBL_SOUND 152 /* ", S:" 152..168 */ +#define R_VAL_SOUND 185 /* зона 169..184 */ +#define X_LBL_MUS 185 /* ", M:" 185..201 */ +#define R_VAL_MUS 218 /* зона 202..217 */ +#define X_LBL_IMM 218 /* ", I:" 218..234 */ +#define R_VAL_IMM 245 /* зона 235..244: цифре нужен зазор в пробел */ /* Оставшееся время ##:## — отдельным полем у правого края, подписи не * требует: формат читается сам. */ #define R_VAL_TIME 310 /* зона 284..309 */ @@ -125,7 +132,7 @@ static uint8_t st_arg; /* номер уровня для POP_MSG_LEVEL /* Ширина зоны значения — под самый длинный вариант плюс воздух. */ #define W_VAL_NUM 13 /* две цифры */ #define W_VAL_SPEED 35 /* FASTEST */ -#define W_VAL_SOUND 17 /* OFF */ +#define W_VAL_SOUND 16 /* OFF */ #define W_VAL_IMM 9 /* одна цифра */ enum { /* биты маски «что перерисовать» */ @@ -135,12 +142,13 @@ enum { /* биты маски «что перерис DBG_F_SOUND = 0x08, DBG_F_IMM = 0x10, DBG_F_TIME = 0x20, - DBG_F_ALL = 0x3F /* + подписи: см. dbg_labels */ + DBG_F_MUS = 0x40, + DBG_F_ALL = 0x7F /* + подписи: см. dbg_labels */ }; static const char * const speed_text[] = { "NORMAL", "FAST", "FASTEST" }; -static uint8_t dbg_lvl, dbg_room, dbg_spd, dbg_snd, dbg_imm; +static uint8_t dbg_lvl, dbg_room, dbg_spd, dbg_snd, dbg_mus, dbg_imm; static uint8_t dbg_tmin, dbg_tsec; /* показанное время ##:## */ static uint8_t dbg_phase; /* кадры до пересчёта секунд */ static uint8_t dbg_dirty[2]; /* маска по странице; DBG_F_ALL => и подписи */ @@ -308,7 +316,11 @@ void pop_status_show_now(uint8_t msg) __banked saved_bank = gfx_get_bank(); gfx_set_draw_page(visible); status_draw(); - st_dirty = (uint8_t)(st_dirty & ~(1 << visible)); + /* Бит видимой страницы НЕ снимаем. За время дисковой операции ту же + * страницу успевает переписать восстановление комнаты, и снятый бит + * означал бы «здесь уже нарисовано» — надпись оставалась ровно на одной + * странице и мигала через кадр (поймано на QUICKLOAD). Лишняя + * перерисовка строки стоит дешевле мигания. */ gfx_set_draw_page(saved_page); gfx_set_bank(saved_bank); } @@ -334,8 +346,9 @@ static void dbg_labels(void) pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_LEVEL, DBG_BASE, "Level"); pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_ROOM, DBG_BASE, ", Room"); pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_SPEED, DBG_BASE, ", Speed:"); - pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_SOUND, DBG_BASE, ", Sound:"); - pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_IMM, DBG_BASE, ", Immortal"); + pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_SOUND, DBG_BASE, ", S:"); + pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_MUS, DBG_BASE, ", M:"); + pop_text_draw_mapped(POP_TEXT_SMALL, X_LBL_IMM, DBG_BASE, ", I:"); pop_text_unmap(); } @@ -359,6 +372,8 @@ static void dbg_draw(uint8_t mask) speed_text[dbg_spd < 3 ? dbg_spd : 0]); if (mask & DBG_F_SOUND) dbg_value(R_VAL_SOUND, W_VAL_SOUND, dbg_snd ? "ON" : "OFF"); + if (mask & DBG_F_MUS) + dbg_value(R_VAL_MUS, W_VAL_SOUND, dbg_mus ? "ON" : "OFF"); if (mask & DBG_F_IMM) { buf[0] = (char)('0' + (dbg_imm < 10 ? dbg_imm : 9)); buf[1] = 0; @@ -377,7 +392,7 @@ static void dbg_draw(uint8_t mask) static void dbg_scan(uint8_t room) { uint8_t prev_lvl = dbg_lvl, prev_room = dbg_room, prev_spd = dbg_spd; - uint8_t prev_snd = dbg_snd, prev_imm = dbg_imm; + uint8_t prev_snd = dbg_snd, prev_mus = dbg_mus, prev_imm = dbg_imm; uint8_t prev_tmin = dbg_tmin, prev_tsec = dbg_tsec; uint8_t mask = 0; @@ -402,11 +417,13 @@ static void dbg_scan(uint8_t room) dbg_room = room; dbg_spd = pop_speed_mode; dbg_snd = pop_snd_want; + dbg_mus = pop_mus_want; dbg_imm = pop_immortal; if (dbg_lvl != prev_lvl) mask |= DBG_F_LEVEL; if (dbg_room != prev_room) mask |= DBG_F_ROOM; if (dbg_spd != prev_spd) mask |= DBG_F_SPEED; if (dbg_snd != prev_snd) mask |= DBG_F_SOUND; + if (dbg_mus != prev_mus) mask |= DBG_F_MUS; if (dbg_imm != prev_imm) mask |= DBG_F_IMM; if (dbg_tmin != prev_tmin || dbg_tsec != prev_tsec) mask |= DBG_F_TIME; if (mask) { diff --git a/applications/PoP/roomtest/pop_status.h b/applications/PoP/roomtest/pop_status.h index eb1a61a..b977aa4 100644 --- a/applications/PoP/roomtest/pop_status.h +++ b/applications/PoP/roomtest/pop_status.h @@ -26,6 +26,8 @@ enum { POP_MSG_NO_QUICKLOAD, /* F9 нечего грузить / битый слот */ POP_MSG_SOUND_ON, /* Ctrl+S */ POP_MSG_SOUND_OFF, + POP_MSG_MUSIC_ON, /* Ctrl+M */ + POP_MSG_MUSIC_OFF, POP_MSG_LEVEL, /* «LEVEL N» — строка собирается с номером */ POP_MSG_MINUTES, /* «N MINUTES LEFT» */ POP_MSG_SECONDS, /* «N SECONDS LEFT» / «1 SECOND LEFT» */ diff --git a/applications/PoP/roomtest/roomtest.c b/applications/PoP/roomtest/roomtest.c index 264d3b8..0e38647 100644 --- a/applications/PoP/roomtest/roomtest.c +++ b/applications/PoP/roomtest/roomtest.c @@ -444,8 +444,16 @@ int main(void) quit = 1; break; } - if (menu_opened && !pop_app_dispatch(POP_APP_EV_RESUME)) { - failed = 1; quit = 1; break; + if (menu_opened) { + /* Меню переписало обе страницы целиком. Снимки слотов об + * этом не знают и продолжают считать, что персонаж на + * странице уже нарисован, — стоящий Кид оставался бы + * невидимым до первого своего движения. */ + pop_cd_forget(); + pop_status_invalidate(); + if (!pop_app_dispatch(POP_APP_EV_RESUME)) { + failed = 1; quit = 1; break; + } } } /* F10 — отладочный немедленный выход (раньше был на Esc, но Esc diff --git a/applications/PoP/roomtest/roomtest_cold.c b/applications/PoP/roomtest/roomtest_cold.c index d98b802..b4cda1d 100644 --- a/applications/PoP/roomtest/roomtest_cold.c +++ b/applications/PoP/roomtest/roomtest_cold.c @@ -344,7 +344,7 @@ uint8_t pop_dbg_roomnav(uint8_t dir) __banked * Флаги «фронта» стали статиками модуля — раньше они были локалями main. * Смысл прежний: одно нажатие = одно срабатывание. */ -static uint8_t cheat_prev, immo_prev, sound_prev, nlvl_prev; +static uint8_t cheat_prev, immo_prev, sound_prev, music_prev, nlvl_prev; static uint8_t upsd_prev, xdec_prev, xinc_prev, cfg_prev; static uint8_t time_sub_prev, time_add_prev; @@ -379,6 +379,8 @@ void pop_cheat_tick(void) __banked uint8_t i = kbd_raw_down(KBD_CHEAT_IMMO); uint8_t s = (uint8_t)(kbd_raw_down(KBD_SOUND_TOGGLE) && (kbd_raw_down(KBD_LCTRL) || kbd_raw_down(KBD_RCTRL))); + uint8_t m = (uint8_t)(kbd_raw_down(KBD_MUSIC_TOGGLE) && + (kbd_raw_down(KBD_LCTRL) || kbd_raw_down(KBD_RCTRL))); uint8_t nl = (uint8_t)(kbd_raw_down(KBD_CHEAT_NEXTLVL) && (kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT))); uint8_t up = kbd_raw_down(KBD_CHEAT_UPSIDE); @@ -401,6 +403,12 @@ void pop_cheat_tick(void) __banked pop_status_msg = (uint8_t)(pop_snd_want ? POP_MSG_SOUND_ON : POP_MSG_SOUND_OFF); } + if (m && !music_prev) { + pop_mus_want ^= 1; /* не под pop_cheats */ + pop_settings.music_enabled = pop_mus_want; + pop_status_msg = (uint8_t)(pop_mus_want ? POP_MSG_MUSIC_ON + : POP_MSG_MUSIC_OFF); + } if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL) pop_next_level = (uint8_t)(pop_current_level + 1); /* U — переворот экрана (seg000:0793 — чит и в оригинале). @@ -417,6 +425,7 @@ void pop_cheat_tick(void) __banked cheat_prev = k; immo_prev = i; sound_prev = s; + music_prev = m; nlvl_prev = nl; upsd_prev = up; xdec_prev = xd; @@ -617,12 +626,13 @@ void pop_qsave_restore_room(uint8_t room) __banked /* Обе страницы теперь содержат только свежий фон. Старые габариты * персонажей относятся к кадру до QuickLoad и не должны запускать heal * поверх восстановленной комнаты. */ - pop_cd[POP_CH_KID].valid[0] = pop_cd[POP_CH_KID].valid[1] = 0; - pop_cd[POP_CH_KID].ovalid[0] = pop_cd[POP_CH_KID].ovalid[1] = 0; - pop_cd[POP_CH_OPP].valid[0] = pop_cd[POP_CH_OPP].valid[1] = 0; - pop_cd[POP_CH_OPP].ovalid[0] = pop_cd[POP_CH_OPP].ovalid[1] = 0; + pop_cd_forget(); pop_cd_init(); pop_hp_invalidate(); + /* Полоса состояния тоже переписана заново — «QUICKLOAD» обязан лечь + * поверх свежего фона на ОБЕИХ страницах, иначе он виден через кадр и + * мигает. */ + pop_status_invalidate(); } /* start_level (seg003:0055) + do_startpos (seg003:01A3) + set_start_pos @@ -843,10 +853,7 @@ void pop_flip_screen(void) __banked /* Всё, что рисуется поверх фона, надо положить заново: прошлые * прямоугольники персонажей относятся к СТАРОЙ (неперевёрнутой) * картинке, и heal по ним стёр бы не то место. */ - pop_cd[POP_CH_KID].valid[0] = pop_cd[POP_CH_KID].valid[1] = 0; - pop_cd[POP_CH_KID].ovalid[0] = pop_cd[POP_CH_KID].ovalid[1] = 0; - pop_cd[POP_CH_OPP].valid[0] = pop_cd[POP_CH_OPP].valid[1] = 0; - pop_cd[POP_CH_OPP].ovalid[0] = pop_cd[POP_CH_OPP].ovalid[1] = 0; + pop_cd_forget(); POP_CD_TOUCH_ALL(); /* фон трогали целиком */ pop_hp_invalidate(); /* полосу HP перерисовать */ } diff --git a/applications/PoP/roomtest/tests-host/t_cfg.c b/applications/PoP/roomtest/tests-host/t_cfg.c index 066b6aa..761648b 100644 --- a/applications/PoP/roomtest/tests-host/t_cfg.c +++ b/applications/PoP/roomtest/tests-host/t_cfg.c @@ -14,7 +14,7 @@ /* Runtime-владельцы нужны только для линковки pop_settings_apply(); codec * их не вызывает. */ -uint8_t pop_speed_mode, pop_cheats, pop_snd_want; +uint8_t pop_speed_mode, pop_cheats, pop_snd_want, pop_mus_want; void pop_sfx_toggle(void) __banked { pop_snd_want = (uint8_t)!pop_snd_want; } static void set_all(uint8_t snd, uint8_t spd, uint8_t prof, uint8_t cheats,