/* * pop_title.c — оригинальная графическая часть show_title(), банк 9 (FG6). * * Готовые композиции создаёт toolchain/pop_pack_title.py непосредственно из * TITLE/res51..55.png SDLPoP. Каждый 320x200 экран режется на пять лент: * загружаем одну в EMM, блитим непрозрачно в normal bank, освобождаем. Это * сознательно не держит title-ресурсы рядом с игровыми атласами. */ #include #include #include #include #include #include "pop_bg.h" /* POP_YOFF: title занимает 200px по центру 256px */ #include "pop_sfx.h" #include "pop_music.h" #include "pop_arc.h" #include "title_arc.h" #include "pop_cutscene.h" /* POP_T60 */ #include "pop_settings.h" #include "pop_pal.h" #include "pop_title.h" #include "pop_ui.h" #define TITLE_W 320 #define TITLE_H 200 #define TITLE_Y POP_YOFF #define TITLE_PARTS 5 #define TITLE_FADE POP_FADE_FRAMES /* 2,13 с, как fade_*_1 оригинала */ enum { TITLE_MAIN = 0, TITLE_PRESENTS, TITLE_GAME, TITLE_LOGO }; /* Композиции лежат в одном архиве подряд по пять полос 49/49/49/49/4, * поэтому имён здесь больше нет: индекс = сцена * TITLE_PARTS. */ #if (ARC_TITLE_T1_0 - ARC_TITLE_T0_0) != TITLE_PARTS #error "полосы титров в архиве лежат не по пять подряд" #endif /* Последовательность и интервалы буквально из show_title(): * main --0x82--> presents --0xCD--> main --0x41--> game * --0x10E--> main --0xEB--> logo+Mechner. * Счёт — в тиках SDLPoP (60 Гц), в кадры луча переводит POP_T60: под * заставку играет тема, и разбегание на 20 % было бы слышно. * * Последний интервал у оригинала не фиксирован: показав логотип, он ждёт * ОКОНЧАНИЯ ТЕМЫ (`while (check_sound_playing())`, seg000:2027) и лишь * потом уходит в историю. 0xEB здесь — нижняя граница на случай, когда * музыки нет (нет файла, выключен звук). */ static const uint8_t title_scene[] = { TITLE_MAIN, TITLE_PRESENTS, TITLE_MAIN, TITLE_GAME, TITLE_MAIN, TITLE_LOGO }; static const uint16_t title_wait_ticks[] = { POP_T60(0x82), POP_T60(0xCD), POP_T60(0x41), POP_T60(0x10E), POP_T60(0xEB), POP_T60(0xEB) }; static uint8_t title_shown; static uint8_t title_skip_pressed(void) { /* Как и в attract-demo, любой удерживаемый raw-код означает «не ждать». * Это заодно не дублирует список клавиш у title, intro и cutscene. */ return kbd_raw_any_down(); } /* Нарисовать одну из готовых 320x200 композиций в скрытую страницу. * Сопоставление W0 охватывает ТОЛЬКО atlas_image/blit: BIOS/vsync сюда * не попадает, значит не конфликтует с EMM и CBL. Возвращает номер * подготовленной страницы; show=0 оставляет её скрытой для palette commit. */ static int title_draw(uint8_t scene, uint8_t show) { uint8_t tbl[ARC_TITLE_COUNT * POP_ARC_REC]; pop_arc_t arc; uint8_t page = pop_screen_begin(); int res; pop_screen_fill(BLACK); if (pop_arc_open_id(&arc, POP_ARC_TITLE, tbl, ARC_TITLE_COUNT) < 0) return -1; res = pop_screen_blit_atlas_arc(&arc, tbl, (uint8_t)(ARC_TITLE_T0_0 + scene * TITLE_PARTS), TITLE_PARTS, 0, TITLE_Y); pop_arc_close(&arc); if (res != 0) return -1; if (show) pop_screen_present(page); return page; } /* draw_full_image(TITLE_MAIN) + transition_ltr() финала (seg001:590 и * :629): титульный рисунок проявляется полосами поверх текущего экрана. * Палитру не трогаем — story.pal и title.pal это одни и те же цвета * (обе сделаны из res51). -1 — архив не прочитан. */ int pop_title_main_ltr(void) __banked { if (title_draw(TITLE_MAIN, 0) < 0) return -1; pop_screen_present_ltr(); return 0; } /* Тот же приём для композиции с логотипом и подписью Jordan Mechner: её * show_title() показывает МЕЖДУ «свадьбой» и титрами (seg000:2045). */ int pop_title_logo_ltr(void) __banked { if (title_draw(TITLE_LOGO, 0) < 0) return -1; pop_screen_present_ltr(); return 0; } static uint8_t title_wait(uint16_t ticks) { while (ticks--) { if (title_skip_pressed()) return 1; gfx_wait_vsync(); } return 0; } /* Новый источник не становится видимым сам: cold boot уже чёрный, а * последующий fade_in выбирает яркость только после сборки t0. */ static void title_load_palette_dimmed(void) { pop_pal_title_load(); pop_pal_apply(POP_PAL_DIM_BLACK); } uint8_t pop_title_show(void) __banked { uint8_t step; uint8_t skipped = 0, music_ok = 0; int prepared; uint8_t cold_start = (uint8_t)(!title_shown && !pop_settings.show_build_info); /* Игровые SFX не должны тянуться сюда из прерванной партии: заставка * начинается с тишины, а через несколько строк её займёт тема титров. */ pop_sfx_pause(); if (!cold_start) pop_pal_fade_out(TITLE_FADE); /* Видимая VRAM могла сохраниться от предыдущей программы. Обнулить её * при чёрной палитре, затем собрать t0 СКРЫТО: загрузка title.pal тогда * освещает только индекс 0, а не старый экран или недорисованный title. */ gfx_set_draw_page(gfx_get_visible_page()); gfx_set_bank(GFX_BANK_NORMAL); pop_screen_fill(BLACK); prepared = title_draw(TITLE_MAIN, 0); if (prepared < 0) return 0; title_load_palette_dimmed(); pop_screen_present((uint8_t)prepared); title_shown = 1; /* Тему читаем с диска ЗАРАНЕЕ (пока экран ещё тёмный), а пускаем ниже — * после fade_in, ровно как show_title: fade_in_2, затем * play_sound(sound_54). */ music_ok = (uint8_t)(pop_music_load(POP_MUS_INTRO) == 0); for (step = 0; step < sizeof(title_scene); step++) { if (step && title_draw(title_scene[step], 1) < 0) break; if (step == 0) { pop_pal_fade_in(TITLE_FADE); if (music_ok) { (void)pop_sfx_start(); pop_music_play(); } } if (title_wait(title_wait_ticks[step])) { skipped = 1; break; } /* Логотип держим до конца темы — так оригинал стыкует заставку с * историей: музыка не обрывается, а сменяется следующей репликой. */ if (step + 1 == sizeof(title_scene)) { while (pop_music_busy()) { if (title_skip_pressed()) { skipped = 1; break; } gfx_wait_vsync(); } } } /* Уходим В ИГРУ — заставочная музыка не должна тянуться в уровень. * Дальше по сценарию (intro) трек, наоборот, ОСТАЁТСЯ звучать: там * следующая реплика подгружается во второй слот, пока играет эта. */ if (skipped) { pop_music_free(); pop_sfx_pause(); } return skipped; }