4b74478d19
Строки дорисовывались уже после полосового перехода: он копирует страницу акселератором, а тот читает ОЗУ-копию, куда GFX_BANK_SPRITE (NOSHADOW + TRANSPARENT) не пишет. Банк GFX_BANK_TRANSPARENT даёт ту же прозрачность 0xFF, но обновляет и копию, поэтому страницу можно собрать целиком до показа — как offscreen оригинала (draw_full_image + show_hof, и только потом transition_ltr). Проверено в MAME: на промежуточных кадрах перехода строка рекорда видна в уже проявившейся части экрана вместе с логотипом. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
531 lines
20 KiB
C
531 lines
20 KiB
C
/*
|
||
* pop_hof.c — persistent Hall of Fame, банк 9 (FG12).
|
||
*
|
||
* Порт двух показов оригинала: между credits и attract-demo (seg000:2060)
|
||
* и после победы, с вводом имени (end_sequence, seg001:5F1). Экран тот
|
||
* же самый — рамка story с логотипом PRINCE OF PERSIA (STORY_FRAME +
|
||
* HOF_POP), поэтому фон приходит готовой композицией из архива PV, а
|
||
* палитру выбирает вызывающая сцена: обе фазы идут на story.pal, и
|
||
* отличаются только цветом фона рамки (см. pop_pal_story_load).
|
||
*
|
||
* POP.HOF — небольшой самодостаточный формат: magic, version, fixed records
|
||
* и XOR+инверсия. Поэтому обрыв/мусор на HDD не может поломать старт игры:
|
||
* decode просто начинает с пустой таблицы. Ввод — raw-клавиатура, как весь
|
||
* PoP; он не открывает DSS-console и не конфликтует с активным raw hook.
|
||
*/
|
||
#include <stdint.h>
|
||
#include <string.h>
|
||
#include <kbd_raw.h>
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include <sprinter_mem.h>
|
||
#include <sprite.h>
|
||
#include "pop_bg.h"
|
||
#include "pop_settings.h"
|
||
#include "pop_sfx.h"
|
||
#include "pop_timer.h"
|
||
#include "pop_pal.h"
|
||
#include "pop_ui.h"
|
||
#include "pop_cutscene.h" /* POP_T60 — тики оригинала в кадры луча */
|
||
#include "pop_intro.h" /* фон экрана: рамка story + логотип */
|
||
#include "pop_title.h" /* титульная картинка после ввода имени */
|
||
#include "pop_music.h" /* подкачка потоковой темы победы */
|
||
#include "pop_hof.h"
|
||
|
||
/* MAX_HOF_COUNT оригинала — шесть строк. */
|
||
#define HOF_COUNT 6
|
||
#define HOF_NAME_SIZE 16 /* пятнадцать букв + '\0' */
|
||
#define HOF_REC_SIZE 20
|
||
#define HOF_HEADER_SIZE 6
|
||
#define HOF_SIZE (HOF_HEADER_SIZE + HOF_COUNT * HOF_REC_SIZE + 2)
|
||
#define HOF_BASE 0x100
|
||
#define HOF_VERSION 2
|
||
#define HOF_FADE POP_FADE_FRAMES
|
||
#define HOF_NO_ROW 0xFF
|
||
|
||
/* Геометрия hof_rects[] (seg001:0D92) в координатах 320x200: строка i
|
||
* занимает y = 84 + 14*i .. +12, имя от x=72, время по правому краю 248.
|
||
* Полоса ввода — та же строка, сдвинутая offset4_rect_add(-4,-1,-40,-1). */
|
||
#define HOF_ROW_TOP 84
|
||
#define HOF_ROW_STEP 14
|
||
#define HOF_ROW_H 12
|
||
#define HOF_LEFT 72
|
||
#define HOF_RIGHT 248
|
||
#define HOF_EDIT_LEFT (HOF_LEFT - 4)
|
||
#define HOF_EDIT_RIGHT (HOF_RIGHT - 40)
|
||
/* Крупный шрифт: глиф 7 строк, строка 12 — текст центрируется по вертикали.
|
||
* baseline = верх строки + 2 + POP_FONT_BIG_ASCENT. */
|
||
#define HOF_BASELINE(i) (POP_YOFF + HOF_ROW_TOP + (int)(i) * HOF_ROW_STEP + 9)
|
||
/* Золотой цвет полосы ввода: 0xB7 оригинала = седьмой цвет палитры title,
|
||
* а у нас story.pal сделана из неё же. */
|
||
#define HOF_GOLD 7
|
||
/* Паузы оригинала в тиках 60 Гц: показ таблицы на титрах и пауза после
|
||
* ввода имени (seg000:2065, seg001:628). */
|
||
#define HOF_TITLE_TICKS POP_T60(0xF0)
|
||
#define HOF_AFTER_TICKS POP_T60(120)
|
||
|
||
typedef struct {
|
||
char name[HOF_NAME_SIZE];
|
||
uint8_t minutes;
|
||
uint16_t ticks;
|
||
uint8_t limited;
|
||
} hof_entry_t;
|
||
|
||
static hof_entry_t hof_table[HOF_COUNT];
|
||
static uint8_t hof_buf[HOF_SIZE];
|
||
|
||
static uint8_t hof_xor(const uint8_t *p, uint8_t n)
|
||
{
|
||
uint8_t sum = 0;
|
||
while (n--) sum ^= *p++;
|
||
return sum;
|
||
}
|
||
|
||
static void hof_clear_entry(hof_entry_t *entry)
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < HOF_NAME_SIZE; i++) entry->name[i] = 0;
|
||
entry->minutes = 0;
|
||
entry->ticks = 0;
|
||
entry->limited = 0;
|
||
}
|
||
|
||
static void hof_clear(void)
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < HOF_COUNT; i++) hof_clear_entry(&hof_table[i]);
|
||
}
|
||
|
||
static uint8_t hof_decode(const uint8_t *src, uint8_t size)
|
||
{
|
||
uint8_t i, j, off, sum;
|
||
|
||
if (size != HOF_SIZE || src[0] != 'P' || src[1] != 'H' ||
|
||
src[2] != 'O' || src[3] != 'F' || src[4] != HOF_VERSION ||
|
||
src[5] != HOF_COUNT)
|
||
return 0;
|
||
sum = hof_xor(src, (uint8_t)(HOF_SIZE - 2));
|
||
if (src[HOF_SIZE - 2] != sum || src[HOF_SIZE - 1] != (uint8_t)~sum)
|
||
return 0;
|
||
|
||
off = HOF_HEADER_SIZE;
|
||
for (i = 0; i < HOF_COUNT; i++) {
|
||
hof_entry_t *entry = &hof_table[i];
|
||
for (j = 0; j < HOF_NAME_SIZE - 1; j++) {
|
||
uint8_t ch = src[off++];
|
||
/* Имя намеренно ASCII: это также отбрасывает мусор из чужого
|
||
* формата, не позволяя строке уйти за свою границу. */
|
||
if (ch && (ch < ' ' || ch > 'Z')) return 0;
|
||
entry->name[j] = (char)ch;
|
||
}
|
||
entry->name[HOF_NAME_SIZE - 1] = 0;
|
||
entry->minutes = src[off++];
|
||
entry->ticks = (uint16_t)(src[off] | ((uint16_t)src[off + 1] << 8));
|
||
off += 2;
|
||
entry->limited = src[off++];
|
||
if (entry->limited > 1 ||
|
||
(entry->limited && (entry->ticks == 0 ||
|
||
entry->ticks > POP_TIMER_START_TICKS)))
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
static void hof_encode(uint8_t *dst)
|
||
{
|
||
uint8_t i, j, off, sum;
|
||
|
||
dst[0] = 'P'; dst[1] = 'H'; dst[2] = 'O'; dst[3] = 'F';
|
||
dst[4] = HOF_VERSION;
|
||
dst[5] = HOF_COUNT;
|
||
off = HOF_HEADER_SIZE;
|
||
for (i = 0; i < HOF_COUNT; i++) {
|
||
const hof_entry_t *entry = &hof_table[i];
|
||
for (j = 0; j < HOF_NAME_SIZE - 1; j++) dst[off++] = entry->name[j];
|
||
dst[off++] = entry->minutes;
|
||
dst[off++] = (uint8_t)entry->ticks;
|
||
dst[off++] = (uint8_t)(entry->ticks >> 8);
|
||
dst[off++] = entry->limited;
|
||
}
|
||
sum = hof_xor(dst, (uint8_t)(HOF_SIZE - 2));
|
||
dst[HOF_SIZE - 2] = sum;
|
||
dst[HOF_SIZE - 1] = (uint8_t)~sum;
|
||
}
|
||
|
||
static void hof_load(void)
|
||
{
|
||
uint8_t blk, page, i;
|
||
int got;
|
||
|
||
hof_clear();
|
||
blk = mem_alloc_pages(1);
|
||
if (!blk) return;
|
||
page = mem_get_page(blk, 0);
|
||
gfx_w0_page_prepare(page);
|
||
got = bank_load_file(page, HOF_BASE, "POP.HOF", HOF_SIZE);
|
||
if (got == HOF_SIZE) {
|
||
gfx_w0_map(page);
|
||
for (i = 0; i < HOF_SIZE; i++)
|
||
hof_buf[i] = ((const uint8_t *)HOF_BASE)[i];
|
||
gfx_w0_unmap();
|
||
if (!hof_decode(hof_buf, (uint8_t)HOF_SIZE)) hof_clear();
|
||
}
|
||
mem_free_block(blk);
|
||
}
|
||
|
||
static void hof_save(void)
|
||
{
|
||
uint8_t blk, page, i;
|
||
|
||
blk = mem_alloc_pages(1);
|
||
if (!blk) return;
|
||
page = mem_get_page(blk, 0);
|
||
gfx_w0_page_prepare(page);
|
||
hof_encode(hof_buf);
|
||
gfx_w0_map(page);
|
||
for (i = 0; i < HOF_SIZE; i++) ((uint8_t *)HOF_BASE)[i] = hof_buf[i];
|
||
gfx_w0_unmap();
|
||
/* Неудача записи не меняет только что показанную таблицу: следующее
|
||
* открытие разумно начнёт с прежнего файла или с пустого списка. */
|
||
bank_save_file(page, HOF_BASE, "POP.HOF", HOF_SIZE);
|
||
mem_free_block(blk);
|
||
}
|
||
|
||
static uint8_t hof_better(const hof_entry_t *entry)
|
||
{
|
||
uint8_t limited = pop_settings.time_limit_enabled;
|
||
|
||
if (!entry->name[0]) return 1;
|
||
/* Unlimited сохраняется и выводится, но не может вытеснить ограниченный
|
||
* таймером результат: у него нет сравнимого времени. */
|
||
if (limited != entry->limited) return limited;
|
||
if (pop_timer_minutes != entry->minutes)
|
||
return (uint8_t)(pop_timer_minutes > entry->minutes);
|
||
return (uint8_t)(pop_timer_ticks > entry->ticks);
|
||
}
|
||
|
||
static uint8_t hof_insert_current(void)
|
||
{
|
||
uint8_t i, j;
|
||
|
||
for (i = 0; i < HOF_COUNT; i++) {
|
||
if (!hof_better(&hof_table[i])) continue;
|
||
for (j = HOF_COUNT - 1; j > i; j--) hof_table[j] = hof_table[j - 1];
|
||
hof_clear_entry(&hof_table[i]);
|
||
hof_table[i].minutes = pop_timer_minutes;
|
||
hof_table[i].ticks = pop_timer_ticks;
|
||
hof_table[i].limited = pop_settings.time_limit_enabled;
|
||
return i;
|
||
}
|
||
return HOF_NO_ROW;
|
||
}
|
||
|
||
static uint8_t hof_has_entries(void)
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < HOF_COUNT; i++)
|
||
if (hof_table[i].name[0]) return 1;
|
||
return 0;
|
||
}
|
||
|
||
static void hof_dec(uint8_t value, char *out)
|
||
{
|
||
if (value >= 100) {
|
||
*out++ = (char)('0' + value / 100);
|
||
value %= 100;
|
||
*out++ = (char)('0' + value / 10);
|
||
} else if (value >= 10) {
|
||
*out++ = (char)('0' + value / 10);
|
||
}
|
||
*out++ = (char)('0' + value % 10);
|
||
*out = 0;
|
||
}
|
||
|
||
/* «59:43» — те же min-1 : tick/12, что печатает show_hof (seg001:0E94). */
|
||
static void hof_result_text(const hof_entry_t *entry, char *out)
|
||
{
|
||
uint8_t seconds;
|
||
|
||
if (!entry->limited) {
|
||
strcpy(out, "UNLIMITED");
|
||
return;
|
||
}
|
||
/* min == 0 у оригинала означает истёкшее время и печатается как 0:00. */
|
||
hof_dec(entry->minutes ? (uint8_t)(entry->minutes - 1) : 0, out);
|
||
while (*out) out++;
|
||
seconds = entry->minutes ? (uint8_t)(entry->ticks / 12) : 0;
|
||
*out++ = ':';
|
||
*out++ = (char)('0' + seconds / 10);
|
||
*out++ = (char)('0' + seconds % 10);
|
||
*out = 0;
|
||
}
|
||
|
||
/* show_hof_text(): сначала тень со сдвигом (1,1), затем сам текст. У нас
|
||
* цвет запечён в глифы, поэтому «другой цвет» — это второй набор
|
||
* (POP_TEXT_BIG_DARK), а не параметр рисования. */
|
||
static void hof_text_left(int x, int baseline, const char *s)
|
||
{
|
||
pop_text_draw_mapped(POP_TEXT_BIG_DARK, x + 1, baseline + 1, s);
|
||
pop_text_draw_mapped(POP_TEXT_BIG, x, baseline, s);
|
||
}
|
||
|
||
static void hof_text_right(int right, int baseline, const char *s)
|
||
{
|
||
pop_text_right_mapped(POP_TEXT_BIG_DARK, right + 1, baseline + 1, s);
|
||
pop_text_right_mapped(POP_TEXT_BIG, right, baseline, s);
|
||
}
|
||
|
||
/* Золотая полоса под ввод — draw_rect(&rect, bgcolor) оригинала. */
|
||
static void hof_edit_bar(uint8_t row)
|
||
{
|
||
int top = HOF_BASELINE(row) - 10;
|
||
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
setfillstyle(SOLID_FILL, HOF_GOLD);
|
||
bar(HOF_EDIT_LEFT, top, HOF_EDIT_RIGHT, top + HOF_ROW_H - 1);
|
||
}
|
||
|
||
/* Строки таблицы в ТЕКУЩУЮ draw-страницу.
|
||
*
|
||
* БАНК ЗДЕСЬ TRANSPARENT, А НЕ SPRITE. Полосовой переход копирует страницу
|
||
* акселератором, а тот читает ОЗУ-копию (gfx_copy_page); SPRITE — это
|
||
* NOSHADOW + TRANSPARENT, то есть в копию он не пишет, и на титрах
|
||
* переезжал бы один фон без строк. TRANSPARENT даёт ту же прозрачность
|
||
* 0xFF, но обновляет и копию, поэтому таблица проявляется уже собранной. */
|
||
static void hof_draw_rows(uint8_t edit_row)
|
||
{
|
||
uint8_t i;
|
||
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
pop_text_map();
|
||
for (i = 0; i < HOF_COUNT; i++) {
|
||
char value[12];
|
||
const hof_entry_t *entry = &hof_table[i];
|
||
int baseline = HOF_BASELINE(i);
|
||
|
||
/* Строка, которую сейчас вписывают, уже содержит результат: имени
|
||
* ещё нет, а время справа оригинал показывает сразу (show_hof
|
||
* рисует все hof_count строк, включая только что вставленную). */
|
||
if (!entry->name[0] && i != edit_row) continue;
|
||
if (entry->name[0]) hof_text_left(HOF_LEFT, baseline, entry->name);
|
||
hof_result_text(entry, value);
|
||
hof_text_right(HOF_RIGHT, baseline, value);
|
||
}
|
||
pop_text_unmap();
|
||
}
|
||
|
||
/* Готовая страница: фон из архива, полоса ввода и строки. -1 — фон не
|
||
* прочитан, и тогда таблица просто не показывается: ни игра, ни title от
|
||
* этого не зависят. */
|
||
static int hof_page_build(uint8_t page, uint8_t edit_row)
|
||
{
|
||
if (pop_intro_hof_bg(page) != 0) return -1;
|
||
gfx_set_draw_page(page);
|
||
if (edit_row != HOF_NO_ROW) hof_edit_bar(edit_row);
|
||
hof_draw_rows(edit_row);
|
||
return 0;
|
||
}
|
||
|
||
/* Имя пишется ПРЯМО НА ЭКРАН, как input_str оригинала
|
||
* (current_target_surface = onscreen_surface_): страницы при вводе не
|
||
* меняются местами, поэтому дорисовывать фон второй страницы не нужно. */
|
||
static void hof_edit_draw(uint8_t row, const char *name, uint8_t cursor)
|
||
{
|
||
int x = HOF_LEFT, baseline = HOF_BASELINE(row);
|
||
|
||
gfx_set_draw_page(gfx_get_visible_page());
|
||
hof_edit_bar(row);
|
||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||
pop_text_map();
|
||
/* Тёмный набор: на золотой полосе оригинал пишет цветом 0xBE. */
|
||
if (name[0]) {
|
||
pop_text_draw_mapped(POP_TEXT_BIG_DARK, x, baseline, name);
|
||
x += pop_text_width_mapped(POP_TEXT_BIG_DARK, name) + 1;
|
||
}
|
||
/* draw_text_cursor(): подчерк той же гарнитуры сразу за текстом,
|
||
* мигающий сменой цвета на фоновый — у нас проще перерисовать. */
|
||
if (cursor) pop_text_draw_mapped(POP_TEXT_BIG_DARK, x, baseline, "_");
|
||
pop_text_unmap();
|
||
}
|
||
|
||
/* ---- ожидания -------------------------------------------------------- *
|
||
*
|
||
* Любое ожидание этого экрана держит ПОТОКОВУЮ музыку: победная тема
|
||
* (1,2 МБ) играет кольцом страниц, и дочитывать её надо всё время, пока
|
||
* игрок смотрит таблицу и печатает имя — иначе насос догонит загрузчика и
|
||
* пойдёт по кругу старыми данными.
|
||
*
|
||
* Прерывание — по НОВОМУ нажатию (детектор фронта, как в сценах intro):
|
||
* клавиша, которой закончили предыдущий экран, в момент входа ещё зажата,
|
||
* и считать её за «пропустить» нельзя. */
|
||
typedef struct { uint8_t armed; } hof_skip_t;
|
||
|
||
static void hof_skip_begin(hof_skip_t *sk)
|
||
{
|
||
kbd_raw_sync();
|
||
sk->armed = (uint8_t)!kbd_raw_any_down();
|
||
}
|
||
|
||
static uint8_t hof_skip_requested(hof_skip_t *sk)
|
||
{
|
||
kbd_raw_sync();
|
||
if (!kbd_raw_any_down()) { sk->armed = 1; return 0; }
|
||
return sk->armed;
|
||
}
|
||
|
||
/* Возврат 1 — ожидание прервано нажатием. */
|
||
static uint8_t hof_wait_frames(uint16_t frames)
|
||
{
|
||
hof_skip_t skip;
|
||
|
||
hof_skip_begin(&skip);
|
||
while (frames--) {
|
||
if (hof_skip_requested(&skip)) return 1;
|
||
pop_music_service();
|
||
gfx_wait_vsync();
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* `while (check_sound_playing() && !key_test_quit())` (seg001:637): финал
|
||
* дослушивает победную тему и прерывается любой клавишей. */
|
||
static void hof_wait_music(void)
|
||
{
|
||
hof_skip_t skip;
|
||
|
||
hof_skip_begin(&skip);
|
||
while (pop_music_busy() && !hof_skip_requested(&skip)) {
|
||
pop_music_service();
|
||
gfx_wait_vsync();
|
||
}
|
||
}
|
||
|
||
/* Set 2 make-коды A..Z, затем space/backspace/enter/esc. Храним edge для
|
||
* каждой клавиши, поэтому зажатая буква не заполняет имя автоповтором. */
|
||
static const uint8_t hof_key_code[30] = {
|
||
0x1C, 0x32, 0x21, 0x23, 0x24, 0x2B, 0x34, 0x33, 0x43,
|
||
0x3B, 0x42, 0x4B, 0x3A, 0x31, 0x44, 0x4D, 0x15, 0x2D,
|
||
0x1B, 0x2C, 0x3C, 0x2A, 0x1D, 0x22, 0x35, 0x1A,
|
||
KBD_SPACE, 0x66, KBD_ENTER, KBD_ESC
|
||
};
|
||
|
||
/* Возвращает 1, когда ввод закончен (Enter/Esc); *changed — надо ли
|
||
* перерисовать строку. */
|
||
static uint8_t hof_name_tick(hof_entry_t *entry, uint8_t *length,
|
||
uint8_t *previous, uint8_t *changed)
|
||
{
|
||
uint8_t i;
|
||
|
||
for (i = 0; i < 30; i++) {
|
||
uint8_t down = kbd_raw_down(hof_key_code[i]);
|
||
if (down && !previous[i]) {
|
||
if (i < 26 && *length < HOF_NAME_SIZE - 1) {
|
||
entry->name[(*length)++] = (char)('A' + i);
|
||
entry->name[*length] = 0;
|
||
*changed = 1;
|
||
} else if (i == 26 && *length && *length < HOF_NAME_SIZE - 1) {
|
||
entry->name[(*length)++] = ' ';
|
||
entry->name[*length] = 0;
|
||
*changed = 1;
|
||
} else if (i == 27 && *length) {
|
||
entry->name[--(*length)] = 0;
|
||
*changed = 1;
|
||
} else if (i == 28 || i == 29) {
|
||
previous[i] = down;
|
||
return 1;
|
||
}
|
||
}
|
||
previous[i] = down;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Ввод имени в строку row. Пустое имя оригинал не отвергает, но пустая
|
||
* строка в таблице выглядела бы как свободное место — подставляем PLAYER. */
|
||
/* Курсор мигает каждые шесть тиков 60 Гц (start_timer(timer_0, 6) в
|
||
* input_str) — в кадрах луча это HOF_BLINK. */
|
||
#define HOF_BLINK POP_T60(6)
|
||
|
||
static void hof_edit(uint8_t row)
|
||
{
|
||
uint8_t length = 0, previous[30], i, changed = 1;
|
||
uint8_t cursor = 1, phase = 0;
|
||
hof_entry_t *entry = &hof_table[row];
|
||
|
||
for (i = 0; i < sizeof(previous); i++) previous[i] = 0;
|
||
/* Стартовое состояние клавиш: зажатое с прошлого экрана не считается
|
||
* нажатием, поэтому первый sync только заполняет previous[]. */
|
||
kbd_raw_sync();
|
||
for (i = 0; i < sizeof(previous); i++)
|
||
previous[i] = kbd_raw_down(hof_key_code[i]);
|
||
for (;;) {
|
||
kbd_raw_sync();
|
||
if (hof_name_tick(entry, &length, previous, &changed)) {
|
||
if (!length) strcpy(entry->name, "PLAYER");
|
||
return;
|
||
}
|
||
if (changed) { cursor = 1; phase = 0; }
|
||
if (++phase >= HOF_BLINK) {
|
||
phase = 0;
|
||
cursor = (uint8_t)!cursor;
|
||
changed = 1;
|
||
}
|
||
if (changed) {
|
||
hof_edit_draw(row, entry->name, cursor);
|
||
changed = 0;
|
||
}
|
||
pop_music_service();
|
||
gfx_wait_vsync();
|
||
}
|
||
}
|
||
|
||
void pop_hof_show(void) __banked
|
||
{
|
||
uint8_t row;
|
||
|
||
hof_load();
|
||
row = hof_insert_current();
|
||
if (row != HOF_NO_ROW) {
|
||
uint8_t page;
|
||
|
||
/* fade_out_2 гасит титульную картинку перед таблицей (seg001:601).
|
||
* Палитру не меняем: story.pal с красным фоном рамки уже стоит от
|
||
* финального HAIL, и снимок для fade сделан ею же. */
|
||
pop_pal_fade_out(HOF_FADE);
|
||
page = pop_screen_begin();
|
||
if (hof_page_build(page, row) == 0) {
|
||
pop_screen_present(page);
|
||
pop_pal_fade_in(HOF_FADE);
|
||
hof_edit(row);
|
||
hof_save();
|
||
/* Имя вписано: строку показываем уже обычным золотым текстом,
|
||
* без полосы (restore_peel + show_hof_text оригинала). */
|
||
page = pop_screen_begin();
|
||
if (hof_page_build(page, HOF_NO_ROW) == 0)
|
||
pop_screen_present(page);
|
||
hof_wait_frames(HOF_AFTER_TICKS);
|
||
(void)pop_title_main_ltr();
|
||
}
|
||
}
|
||
hof_wait_music();
|
||
pop_pal_fade_out(HOF_FADE);
|
||
pop_music_free();
|
||
pop_sfx_pause();
|
||
}
|
||
|
||
uint8_t pop_hof_show_title(void) __banked
|
||
{
|
||
uint8_t page;
|
||
|
||
hof_load();
|
||
if (!hof_has_entries()) return 0;
|
||
page = pop_screen_begin();
|
||
/* Страница собирается ЦЕЛИКОМ до показа — как offscreen оригинала:
|
||
* draw_full_image + show_hof, и только потом transition_ltr
|
||
* (seg000:2063). Ни fade, ни смены палитры: экран проявляется прямо
|
||
* поверх credits, на той же story.pal. */
|
||
if (hof_page_build(page, HOF_NO_ROW) != 0) return 0;
|
||
pop_screen_present_ltr();
|
||
return hof_wait_frames(HOF_TITLE_TICKS);
|
||
}
|