SprPoP: заставку можно прервать в любой её точке, F10 — выход

Три места, где нажатие раньше не работало или работало наполовину.

FADE.  fade_run() был замкнутым циклом на 2,13 с без опроса клавиш, а на
стыке экранов их два: пропуск работал внутри сценария, но не между его
шагами, и заставка ощущалась невыключаемой.  Добавлены прерываемые
варианты (pop_ui_fade_*_skip, обёртки в pop_pal) — отдельными функциями,
а не флагом в прежних: в меню паузы и на переходе уровня прерывать
нечего, и менять там поведение молча не следует.  Прерванный fade всё
равно доводит палитру до конца, экран не остаётся на промежуточной
ступени.  Подключено в интерпретаторе сценария, сцене с принцессой,
четырёх катсценах cut_*, титрах и таблице рекордов.

ПРОЯВЛЕНИЕ ПОЛОСАМИ.  pop_screen_present_ltr() была void и нажатие
ГЛОТАЛА: полосы схлопывались, картинка появлялась целиком — и всё,
вызывающий о нажатии не узнавал.  На заставке это выглядело как
«клавиша срабатывает наполовину».  Теперь возвращает признак прерывания,
и он проброшен по маршруту: первый экран истории, титры финала, логотип
между «свадьбой» и титрами.

Везде считается КРОМКА нажатия от входа, как в сценах: клавиша, которой
закончили предыдущий экран, ещё зажата, и принимать её за новое нажатие
нельзя — иначе весь маршрут заставки схлопывался бы сам собой.

F10 — НЕМЕДЛЕННЫЙ ВЫХОД, откуда угодно.  Проверка стоит в kbd_idle(), а
этот хук висит на gfx_wait_vsync, то есть вызывается везде, где программа
ждёт кадр: заставка, титры, fade, проявление полосами, меню, игра.  Одна
точка вместо десятка по циклам ожидания.  Флаг pop_quit_req резидентный —
взводится и читается без трамплина из любого банка.  Проверка в НАЧАЛЕ
витка автомата обязательна: заставку прерывает любая клавиша, и без неё
F10 успевал уронить программу в загрузку уровня перед закрытием.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 12:49:01 +03:00
parent 31b82661eb
commit 808c2a5349
9 changed files with 96 additions and 35 deletions
+3 -3
View File
@@ -25,11 +25,11 @@
* Ctrl+S: Sound on/off. * Ctrl+S: Sound on/off.
* Ctrl+M: Music on/off. * Ctrl+M: Music on/off.
* Ctrl+V: Show version of SprPoP. * Ctrl+V: Show version of SprPoP.
* Ctrl+Q: Quit game. * Ctrl+Q / F10: Quit game.
* F6: Quicksave: Save the exact state of the game. * F6: Quicksave: Save the exact state of the game.
* F9: Quickload: Load what the last quicksave saved. * F9: Quickload: Load what the last quicksave saved.
* F12: Save a screenshot to the screenshots folder. * F12: Save a screenshot to the screenshots folder (optional - можно пока не делать).
* Backspace: Display the in-game menu. (Esc will also display the menu by default, but you can turn that off.) * Backspace: Display the in-game menu. (Esc will also display the menu by default, but you can turn that off.)
* Shift+L: Go to next level. * Shift+L: Go to next level.
@@ -54,5 +54,5 @@
* [: Shift kid 1 pixel to the left. * [: Shift kid 1 pixel to the left.
* ]: Shift kid 1 pixel to the right. * ]: Shift kid 1 pixel to the right.
* T: Toggle display of timer (remaining minutes:seconds:ticks). Also shows the total elapsed ticks during playback. * T: Toggle display of timer (remaining minutes:seconds).
+3 -3
View File
@@ -491,11 +491,11 @@ void pop_hof_show(void) __banked
/* fade_out_2 гасит титульную картинку перед таблицей (seg001:601). /* fade_out_2 гасит титульную картинку перед таблицей (seg001:601).
* Палитру не меняем: story.pal с красным фоном рамки уже стоит от * Палитру не меняем: story.pal с красным фоном рамки уже стоит от
* финального HAIL, и снимок для fade сделан ею же. */ * финального HAIL, и снимок для fade сделан ею же. */
pop_pal_fade_out(HOF_FADE); (void)pop_pal_fade_out_skip(HOF_FADE);
page = pop_screen_begin(); page = pop_screen_begin();
if (hof_page_build(page, row) == 0) { if (hof_page_build(page, row) == 0) {
pop_screen_present(page); pop_screen_present(page);
pop_pal_fade_in(HOF_FADE); (void)pop_pal_fade_in_skip(HOF_FADE);
hof_edit(row); hof_edit(row);
hof_save(); hof_save();
/* Имя вписано: строку показываем уже обычным золотым текстом, /* Имя вписано: строку показываем уже обычным золотым текстом,
@@ -508,7 +508,7 @@ void pop_hof_show(void) __banked
} }
} }
hof_wait_music(); hof_wait_music();
pop_pal_fade_out(HOF_FADE); (void)pop_pal_fade_out_skip(HOF_FADE);
pop_music_free(); pop_music_free();
pop_sfx_pause(); pop_sfx_pause();
} }
+40 -17
View File
@@ -910,7 +910,10 @@ static int intro_pv_animated(void)
if (pop_music_loading()) (void)pop_music_load_step(); if (pop_music_loading()) (void)pop_music_load_step();
/* Первый кадр собран под чёрной палитрой — теперь проявляем его /* Первый кадр собран под чёрной палитрой — теперь проявляем его
* целиком, вместе с принцессой и факелами. */ * целиком, вместе с принцессой и факелами. */
if (!faded_in) { faded_in = 1; pop_pal_fade_in(INTRO_FADE); } if (!faded_in) {
faded_in = 1;
if (pop_pal_fade_in_skip(INTRO_FADE)) { result = 1; break; }
}
/* ПЕЙСИНГ ПО ОРИГИНАЛЬНОЙ ШКАЛЕ. Кадр cutscene — 6/8/7 тиков по /* ПЕЙСИНГ ПО ОРИГИНАЛЬНОЙ ШКАЛЕ. Кадр cutscene — 6/8/7 тиков по
* 1/60 с; один тик = 85,4/60 = 1,424 тика насоса, и 57/40 = 1,425 * 1/60 с; один тик = 85,4/60 = 1,424 тика насоса, и 57/40 = 1,425
* повторяет это с точностью 0,07 % (30 мс на всю сцену) без * повторяет это с точностью 0,07 % (30 мс на всю сцену) без
@@ -952,7 +955,7 @@ static int intro_pv_animated(void)
* экране «свадьбы» (seg000:2051). Глушит вызывающий, когда музыка * экране «свадьбы» (seg000:2051). Глушит вызывающий, когда музыка
* действительно доиграла. */ * действительно доиграла. */
if (result) { pop_music_free(); pop_sfx_pause(); } if (result) { pop_music_free(); pop_sfx_pause(); }
if (!result) pop_pal_fade_out(INTRO_FADE); if (!result) (void)pop_pal_fade_out_skip(INTRO_FADE);
atlas_free(&torch); atlas_free(&torch);
atlas_free(&hourglass); atlas_free(&hourglass);
@@ -1050,7 +1053,7 @@ static int pre_room_animated(uint8_t picture, uint8_t state, uint8_t frames)
/* Проявляем ПОСЛЕ первого кадра: иначе две секунды fade идут по /* Проявляем ПОСЛЕ первого кадра: иначе две секунды fade идут по
* пустой комнате, а часы и пламя возникают уже на свету. */ * пустой комнате, а часы и пламя возникают уже на свету. */
if (frame == 0) pop_pal_fade_in(INTRO_FADE); if (frame == 0 && pop_pal_fade_in_skip(INTRO_FADE)) { result = 1; break; }
torch_left = (uint8_t)((torch_left + 1) % 9); torch_left = (uint8_t)((torch_left + 1) % 9);
torch_right = (uint8_t)((torch_right + 1) % 9); torch_right = (uint8_t)((torch_right + 1) % 9);
sand = (uint8_t)((sand + 1) % 3); sand = (uint8_t)((sand + 1) % 3);
@@ -1383,7 +1386,7 @@ static uint8_t cut_aborted;
static void cut_end(uint8_t aborted) static void cut_end(uint8_t aborted)
{ {
cut_aborted = aborted; cut_aborted = aborted;
if (!aborted) pop_pal_fade_out(INTRO_FADE); if (!aborted && pop_pal_fade_out_skip(INTRO_FADE)) cut_aborted = 1;
cut_free(); cut_free();
} }
@@ -1403,7 +1406,7 @@ static int cut_scene_8(void)
* первым кадром цикла. Рисуем его при ещё чёрной палитре — тогда * первым кадром цикла. Рисуем его при ещё чёрной палитре — тогда
* картинка проявляется целиком, вместе с актёрами. */ * картинка проявляется целиком, вместе с актёрами. */
aborted = cut_run(1, 1); aborted = cut_run(1, 1);
if (!aborted) pop_pal_fade_in(INTRO_FADE); if (!aborted) aborted = pop_pal_fade_in_skip(INTRO_FADE);
if (!aborted) aborted = cut_run(19, 1); if (!aborted) aborted = cut_run(19, 1);
if (!aborted) { if (!aborted) {
cut_kid_seq(107); /* мышь встаёт и уходит */ cut_kid_seq(107); /* мышь встаёт и уходит */
@@ -1432,7 +1435,7 @@ static int cut_scene_9(void)
* первым кадром цикла. Рисуем его при ещё чёрной палитре — тогда * первым кадром цикла. Рисуем его при ещё чёрной палитре — тогда
* картинка проявляется целиком, вместе с актёрами. */ * картинка проявляется целиком, вместе с актёрами. */
aborted = cut_run(1, 0); /* мышь ещё за кадром */ aborted = cut_run(1, 0); /* мышь ещё за кадром */
if (!aborted) pop_pal_fade_in(INTRO_FADE); if (!aborted) aborted = pop_pal_fade_in_skip(INTRO_FADE);
/* init_mouse_go (seg001:022A): мышь вбегает справа. */ /* init_mouse_go (seg001:022A): мышь вбегает справа. */
cut_kid_init(CHARID_24_MOUSE, 199, 167, 105); cut_kid_init(CHARID_24_MOUSE, 199, 167, 105);
if (!aborted) aborted = cut_run(5, 1); if (!aborted) aborted = cut_run(5, 1);
@@ -1464,7 +1467,7 @@ static int cut_scene_12_short(void)
* первым кадром цикла. Рисуем его при ещё чёрной палитре — тогда * первым кадром цикла. Рисуем его при ещё чёрной палитре — тогда
* картинка проявляется целиком, вместе с актёрами. */ * картинка проявляется целиком, вместе с актёрами. */
aborted = cut_run(1, 0); aborted = cut_run(1, 0);
if (!aborted) pop_pal_fade_in(INTRO_FADE); if (!aborted) aborted = pop_pal_fade_in_skip(INTRO_FADE);
if (!aborted) aborted = cut_run(1, 0); if (!aborted) aborted = cut_run(1, 0);
if (!aborted) { if (!aborted) {
cut_shad_seq(98); /* princess turn around [PV1] */ cut_shad_seq(98); /* princess turn around [PV1] */
@@ -1492,7 +1495,7 @@ static int cut_ending(void)
* первым кадром цикла. Рисуем его при ещё чёрной палитре — тогда * первым кадром цикла. Рисуем его при ещё чёрной палитре — тогда
* картинка проявляется целиком, вместе с актёрами. */ * картинка проявляется целиком, вместе с актёрами. */
aborted = cut_run(1, 1); aborted = cut_run(1, 1);
if (!aborted) pop_pal_fade_in(INTRO_FADE); if (!aborted) aborted = pop_pal_fade_in_skip(INTRO_FADE);
pop_sfx_play(26); /* «arrived to princess» */ pop_sfx_play(26); /* «arrived to princess» */
if (!aborted) aborted = cut_run(7, 1); if (!aborted) aborted = cut_run(7, 1);
if (!aborted) { if (!aborted) {
@@ -1547,8 +1550,17 @@ static uint8_t intro_run(const pop_cs_cmd_t *script)
aborted = 1; aborted = 1;
break; break;
} }
} else if (ev == POP_CS_EVENT_FADE_IN) pop_pal_fade_in(INTRO_FADE); } else if (ev == POP_CS_EVENT_FADE_IN || ev == POP_CS_EVENT_FADE_OUT) {
else if (ev == POP_CS_EVENT_FADE_OUT) pop_pal_fade_out(INTRO_FADE); /* Fade — 2,13 с, и на стыке экранов их два. Пока он был глухим,
* эти секунды не пропускались ничем: пропуск работал внутри
* сценария, но не между его шагами. Прерывание здесь означает
* то же, что и в самом сценарии — уйти из заставки. */
uint8_t cut = (ev == POP_CS_EVENT_FADE_IN)
? pop_pal_fade_in_skip(INTRO_FADE)
: pop_pal_fade_out_skip(INTRO_FADE);
if (cut) { pop_cutscene_abort(&cs); aborted = 1; break; }
}
else if (ev == POP_CS_EVENT_FLASH) intro_flash(arg); else if (ev == POP_CS_EVENT_FLASH) intro_flash(arg);
pop_music_service(); /* подкачка потокового трека (финал) */ pop_music_service(); /* подкачка потокового трека (финал) */
gfx_wait_vsync(); gfx_wait_vsync();
@@ -1581,8 +1593,9 @@ static uint8_t intro_credits_ltr(void)
{ {
uint8_t page = pop_screen_begin(); uint8_t page = pop_screen_begin();
if (intro_draw_page(INTRO_STORY_CREDITS, page) == 0) if (intro_draw_page(INTRO_STORY_CREDITS, page) == 0 &&
pop_screen_present_ltr(); pop_screen_present_ltr())
return 1;
return intro_wait_frames(POP_T60(0x168)); return intro_wait_frames(POP_T60(0x168));
} }
@@ -1602,7 +1615,7 @@ uint8_t pop_intro_show(void) __banked
pop_sfx_pause(); pop_sfx_pause();
intro_prepare_palette(INTRO_PAL_STORY); intro_prepare_palette(INTRO_PAL_STORY);
if (intro_draw_new_palette(INTRO_STORY_ABSENCE) == 0) if (intro_draw_new_palette(INTRO_STORY_ABSENCE) == 0)
pop_pal_fade_in(INTRO_FADE); (void)pop_pal_fade_in_skip(INTRO_FADE);
} else { } else {
/* Картинка собрана и ждёт в скрытой странице; читаем следующую /* Картинка собрана и ждёт в скрытой странице; читаем следующую
* реплику во ВТОРОЙ слот — тема титров при этом продолжает * реплику во ВТОРОЙ слот — тема титров при этом продолжает
@@ -1618,7 +1631,13 @@ uint8_t pop_intro_show(void) __banked
} }
(void)pop_sfx_start(); /* идемпотентно: титры его уже открыли */ (void)pop_sfx_start(); /* идемпотентно: титры его уже открыли */
pop_music_play(); pop_music_play();
pop_screen_present_ltr(); /* Прервали проявление — это не «показать картинку разом», а «хватит
* заставки»: маршрут тот же, что и у пропуска внутри сценария. */
if (pop_screen_present_ltr()) {
pop_music_free();
pop_sfx_pause();
return 1;
}
} }
if (intro_run(intro_before_pv)) { if (intro_run(intro_before_pv)) {
/* Пропуск по клавише. ГЛУШИМ ВЫВОД: сразу за возвратом пойдёт /* Пропуск по клавише. ГЛУШИМ ВЫВОД: сразу за возвратом пойдёт
@@ -1678,9 +1697,13 @@ uint8_t pop_intro_show(void) __banked
* TITLE_MAIN + логотип + Jordan Mechner и проявляет их полосами * TITLE_MAIN + логотип + Jordan Mechner и проявляет их полосами
* (seg000:2045), и только через 0x78 тиков показывает credits — тоже * (seg000:2045), и только через 0x78 тиков показывает credits — тоже
* полосами. Раньше обе картинки возникали мгновенно. */ * полосами. Раньше обе картинки возникали мгновенно. */
if (pop_title_logo_ltr() == 0 && intro_wait_frames(POP_T60(0x78))) { {
intro_restore_game_palette(); int shown = pop_title_logo_ltr(); /* 1 — прервано, -1 — нет архива */
return 1;
if (shown == 1 || (shown == 0 && intro_wait_frames(POP_T60(0x78)))) {
intro_restore_game_palette();
return 1;
}
} }
if (intro_credits_ltr()) { if (intro_credits_ltr()) {
intro_restore_game_palette(); intro_restore_game_palette();
+6
View File
@@ -13,6 +13,12 @@
#include "pop_state.h" #include "pop_state.h"
#include "pop_ctrl.h" /* объявления шины control_* (живёт здесь) */ #include "pop_ctrl.h" /* объявления шины control_* (живёт здесь) */
/* F10 — НЕМЕДЛЕННЫЙ ВЫХОД, откуда угодно. Флаг взводит kbd_idle (sprpop.c),
* то есть он ловится везде, где программа ждёт кадр: в заставке, в титрах,
* в fade, в проявлении полосами, в меню и в игре. Держим его резидентным,
* чтобы взводить и читать без трамплина из любого банка. */
uint8_t pop_quit_req;
uint8_t pop_mob_busy; uint8_t pop_mob_busy;
uint8_t pop_loose_landed; uint8_t pop_loose_landed;
/* Кусок loose УШЁЛ ВНИЗ из комнаты (порт хвоста move_loose, seg007:1126: /* Кусок loose УШЁЛ ВНИЗ из комнаты (порт хвоста move_loose, seg007:1126:
+2
View File
@@ -17,6 +17,8 @@
* Живёт в резиденте, а не в pop_room.c, потому что читает его pop_map * Живёт в резиденте, а не в pop_room.c, потому что читает его pop_map
* (банк 3), а писучие статики банкового модуля наружу не видны. Ставит * (банк 3), а писучие статики банкового модуля наружу не видны. Ставит
* отрыв куска, снимает сам обход по факту пустой таблицы. */ * отрыв куска, снимает сам обход по факту пустой таблицы. */
/* F10 нажата — выйти из программы; см. pop_state.c. */
extern uint8_t pop_quit_req;
extern uint8_t pop_mob_busy; extern uint8_t pop_mob_busy;
/* Кусок loose приземлился (loose_land, seg007:11E8): 0 = нет, иначе /* Кусок loose приземлился (loose_land, seg007:11E8): 0 = нет, иначе
+6 -6
View File
@@ -99,8 +99,7 @@ static int title_draw(uint8_t scene, uint8_t show)
int pop_title_main_ltr(void) __banked int pop_title_main_ltr(void) __banked
{ {
if (title_draw(TITLE_MAIN, 0) < 0) return -1; if (title_draw(TITLE_MAIN, 0) < 0) return -1;
pop_screen_present_ltr(); return pop_screen_present_ltr() ? 1 : 0;
return 0;
} }
/* Тот же приём для композиции с логотипом и подписью Jordan Mechner: её /* Тот же приём для композиции с логотипом и подписью Jordan Mechner: её
@@ -108,8 +107,7 @@ int pop_title_main_ltr(void) __banked
int pop_title_logo_ltr(void) __banked int pop_title_logo_ltr(void) __banked
{ {
if (title_draw(TITLE_LOGO, 0) < 0) return -1; if (title_draw(TITLE_LOGO, 0) < 0) return -1;
pop_screen_present_ltr(); return pop_screen_present_ltr() ? 1 : 0;
return 0;
} }
static uint8_t title_wait(uint16_t ticks) static uint8_t title_wait(uint16_t ticks)
@@ -140,7 +138,9 @@ uint8_t pop_title_show(void) __banked
/* Игровые SFX не должны тянуться сюда из прерванной партии: заставка /* Игровые SFX не должны тянуться сюда из прерванной партии: заставка
* начинается с тишины, а через несколько строк её займёт тема титров. */ * начинается с тишины, а через несколько строк её займёт тема титров. */
pop_sfx_pause(); pop_sfx_pause();
if (!cold_start) pop_pal_fade_out(TITLE_FADE); /* Только мгновенность перехода: сам титул ещё не показан,
* и прерывать тут нечего. */
if (!cold_start) (void)pop_pal_fade_out_skip(TITLE_FADE);
/* Видимая VRAM могла сохраниться от предыдущей программы. Обнулить её /* Видимая VRAM могла сохраниться от предыдущей программы. Обнулить её
* при чёрной палитре, затем собрать t0 СКРЫТО: загрузка title.pal тогда * при чёрной палитре, затем собрать t0 СКРЫТО: загрузка title.pal тогда
@@ -163,7 +163,7 @@ uint8_t pop_title_show(void) __banked
if (step && title_draw(title_scene[step], 1) < 0) if (step && title_draw(title_scene[step], 1) < 0)
break; break;
if (step == 0) { if (step == 0) {
pop_pal_fade_in(TITLE_FADE); if (pop_pal_fade_in_skip(TITLE_FADE)) { skipped = 1; break; }
if (music_ok) { if (music_ok) {
(void)pop_sfx_start(); (void)pop_sfx_start();
pop_music_play(); pop_music_play();
+19 -3
View File
@@ -395,27 +395,43 @@ void pop_screen_present(uint8_t page) __banked
* Страницы при этом НЕ меняются местами: копируем в видимую, как оригинал * Страницы при этом НЕ меняются местами: копируем в видимую, как оригинал
* копирует offscreen в onscreen. Поэтому у вызывающего после перехода на * копирует offscreen в onscreen. Поэтому у вызывающего после перехода на
* ЭКРАНЕ и в скрытой странице лежит одно и то же. */ * ЭКРАНЕ и в скрытой странице лежит одно и то же. */
void pop_screen_present_ltr(void) __banked /* Возвращает 1, если проявление прервано НОВЫМ нажатием.
*
* Раньше функция была void и нажатие ГЛОТАЛА: полосы схлопывались, картинка
* появлялась целиком — и на этом всё, вызывающий о нажатии не узнавал. На
* заставке это выглядело как «клавиша срабатывает наполовину»: проявление
* прервалось, а в игру не ушли.
*
* Кромка считается от входа, как в сценах и в fade: клавиша, которой
* закончили предыдущий экран, ещё зажата, и принимать её за новое нажатие
* нельзя — иначе весь маршрут заставки схлопнулся бы сам собой. */
uint8_t pop_screen_present_ltr(void) __banked
{ {
gfx_rect_t stripe; gfx_rect_t stripe;
uint8_t visible = gfx_get_visible_page(); uint8_t visible = gfx_get_visible_page();
uint8_t armed;
int x; int x;
kbd_raw_sync();
armed = (uint8_t)!kbd_raw_any_down();
gfx_set_draw_page(visible); /* source gfx_copy_page = target */ gfx_set_draw_page(visible); /* source gfx_copy_page = target */
stripe.y = 0; stripe.y = 0;
stripe.h = GFX_HEIGHT; stripe.h = GFX_HEIGHT;
for (x = 0; x < GFX_WIDTH; x += 4) { for (x = 0; x < GFX_WIDTH; x += 4) {
if (kbd_raw_any_down()) { kbd_raw_sync();
if (!kbd_raw_any_down()) armed = 1;
else if (armed) {
/* Прервали — дорисовать остаток разом, а не бросить полукадр. */ /* Прервали — дорисовать остаток разом, а не бросить полукадр. */
stripe.x = 0; stripe.w = GFX_WIDTH; stripe.x = 0; stripe.w = GFX_WIDTH;
gfx_copy_page(&stripe, GFX_COPY_DIRECT); gfx_copy_page(&stripe, GFX_COPY_DIRECT);
return; return 1;
} }
stripe.x = x; stripe.x = x;
stripe.w = (uint16_t)((GFX_WIDTH - x < 4) ? GFX_WIDTH - x : 4); stripe.w = (uint16_t)((GFX_WIDTH - x < 4) ? GFX_WIDTH - x : 4);
gfx_copy_page(&stripe, GFX_COPY_DIRECT); gfx_copy_page(&stripe, GFX_COPY_DIRECT);
gfx_wait_vsync(); gfx_wait_vsync();
} }
return 0;
} }
/* Нарисовать одну полосу и отпустить её страницу. Общая часть обоих /* Нарисовать одну полосу и отпустить её страницу. Общая часть обоих
+2 -1
View File
@@ -99,7 +99,8 @@ void pop_screen_present(uint8_t page) __banked;
* (transition_ltr оригинала). Любая зажатая клавиша достраивает переход * (transition_ltr оригинала). Любая зажатая клавиша достраивает переход
* мгновенно. Страницы местами не меняются: содержимое копируется в * мгновенно. Страницы местами не меняются: содержимое копируется в
* видимую, поэтому после возврата обе страницы одинаковы. */ * видимую, поэтому после возврата обе страницы одинаковы. */
void pop_screen_present_ltr(void) __banked; /* 1 — проявление прервано новым нажатием (остаток дорисован разом). */
uint8_t pop_screen_present_ltr(void) __banked;
/* Нарисовать в уже выбранную draw-страницу набор вертикальных полос SPA1. /* Нарисовать в уже выбранную draw-страницу набор вертикальных полос SPA1.
* Полоса загружается, блитится и тут же освобождается; функция не вызывает * Полоса загружается, блитится и тут же освобождается; функция не вызывает
+15 -2
View File
@@ -119,9 +119,16 @@ uint8_t pop_menu_request;
/* Обёртка ради типа: хук — void(*)(void), а kbd_raw_poll возвращает /* Обёртка ради типа: хук — void(*)(void), а kbd_raw_poll возвращает
* «было ли что вычерпано». Приводить указатель кастом не хочется. */ * «было ли что вычерпано». Приводить указатель кастом не хочется. */
/* F10 (PS/2 set 2 — 0x09): немедленный выход. Проверяем ЗДЕСЬ, а не в
* каждом цикле ожидания: idle-хук висит на gfx_wait_vsync, то есть
* вызывается везде, где программа ждёт кадр — заставка, титры, fade,
* проявление полосами, меню, игра. Одна точка вместо десятка. */
#define KBD_KEY_F10 0x09
static void kbd_idle(void) static void kbd_idle(void)
{ {
kbd_raw_poll(); kbd_raw_poll();
if (kbd_raw_down(KBD_KEY_F10)) pop_quit_req = 1;
} }
/* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */ /* seq_2_stand (types.h seqids) — Kid стоит, ждёт ввода. */
@@ -282,6 +289,12 @@ int main(void)
for (;;) { for (;;) {
uint8_t is_demo; uint8_t is_demo;
/* F10 в любой точке — выходим, не начиная следующий маршрут. Проверка
* стоит В НАЧАЛЕ витка: заставку и титры прерывает ЛЮБАЯ клавиша (это
* штатный «пропустить в игру»), и без этой строки F10 успел бы уронить
* нас в загрузку уровня перед тем, как программа закроется. */
if (pop_quit_req) break;
/* Title -> intro -> demo — три самостоятельных состояния. Сцены не /* Title -> intro -> demo — три самостоятельных состояния. Сцены не
* вызывают gameplay рекурсивно: после каждого return следующий маршрут * вызывают gameplay рекурсивно: после каждого return следующий маршрут
* выбирается только pop_app_dispatch(). */ * выбирается только pop_app_dispatch(). */
@@ -364,7 +377,7 @@ int main(void)
demo_new_game = 0; demo_new_game = 0;
demo_finished = 0; demo_finished = 0;
while (!quit && (pop_app_state == POP_APP_PLAYING || while (!quit && !pop_quit_req && (pop_app_state == POP_APP_PLAYING ||
pop_app_state == POP_APP_DEMO)) { pop_app_state == POP_APP_DEMO)) {
PROF(2); /* ввод + heal */ PROF(2); /* ввод + heal */
@@ -926,7 +939,7 @@ int main(void)
if (is_demo && demo_finished) break; if (is_demo && demo_finished) break;
} }
if (quit) break; if (quit || pop_quit_req) break;
/* Restart Game завершает только игровой цикл. Следующая итерация /* Restart Game завершает только игровой цикл. Следующая итерация
* верхнего автомата начинает title/intro с самого первого экрана. */ * верхнего автомата начинает title/intro с самого первого экрана. */
if (pop_app_state == POP_APP_TITLE) continue; if (pop_app_state == POP_APP_TITLE) continue;