Музыка: озвучена вся заставка, тайминги сведены со шкалой оригинала
Добавлены треки 54 (тема титров), 50/53/52 (три реплики сцены с принцессой и Джафаром) — заставка звучит целиком, как в оригинале. pop_music: два слота EMM. Реплики идут встык, паузы под загрузку нет, поэтому следующая читается в НЕ играющий слот, а play подменяет резидентную таблицу страниц. Своей копии таблицы слот не держит — её хранит блок EMM. Плюс постраничная загрузка (load_begin/load_step): страница стоит 33 мс, четверть логического кадра сцены, и подкачка посреди анимации не видна. Тайминги: все длительности сцен — в тиках оригинала (60 Гц), перевод в кадры луча делает POP_T60. Пока сцены были немыми, разбег в 20 % был незаметен; с музыкой реплика кончалась раньше картинки. Переведены титры, история, хвост после PV и пейсинг самой PV-сцены. Паузы, которые оригинал отмеряет концом сэмпла, теперь равны реальной длине наших записей: конец m50 на тике 846, вход Джафара 1046, уход 2073, конец сцены 2500 (было 1959). Fade перед PV сдвинут так, чтобы полная темнота наступала в тот же момент трека, что у SDLPoP: их fade длиннее нашего на 123 тика, наш полосовой переход длиннее на 16. Финальная сцена: убраны песочные часы — end_sequence_anim не трогает hourglass_state, а reset_cutscene его обнулил. Уровень пройден, время больше не идёт. Проверено в MAME: цепочка 54 -> 55 -> 50 -> 53 -> 52 отыгрывается целиком, интро доходит до демо-режима. Host-тесты: 15 наборов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -180,12 +180,13 @@ SND_PARTS := $(foreach n,0 1 2 3 4 5 6 7 8 9,$(SND_DIR)/s$(n).bin)
|
||||
SND_ARC := $(SND_DIR)/snd.arc
|
||||
SND_DATA := $(SND_ARC)
|
||||
|
||||
# МУЗЫКА: записи DOS-версии, приведённые к формату CBL и порезанные на
|
||||
# страницы EMM (toolchain/pop_pack_music.py). Пока в игре звучит один
|
||||
# трек — story_1_absence на первом экране intro; список расширяется
|
||||
# добавлением номеров в MUS_IDS, имена кусков задаёт упаковщик.
|
||||
# МУЗЫКА: записи DOS-версии, приведённые к формату CBL
|
||||
# (toolchain/pop_pack_music.py). Сейчас озвучена вся заставка:
|
||||
# 54 — тема титров, 55 — «In the absence», 50/53/52 — три реплики сцены
|
||||
# с принцессой и Джафаром. Игровые джинглы (смерть, зелья) пойдут
|
||||
# отдельной задачей: там музыка спорит с эффектами за единственный CBL.
|
||||
MUS_DIR := $(CURDIR)/../poc/res/music
|
||||
MUS_IDS := 55
|
||||
MUS_IDS := 50 52 53 54 55
|
||||
MUS_DATA := $(foreach i,$(MUS_IDS),$(MUS_DIR)/m$(i).bin)
|
||||
|
||||
# UI: два исходных шрифта SDLPoP в одном EMM-атласе. Файл короче страницы:
|
||||
|
||||
@@ -1206,6 +1206,28 @@ N навык M» — отдельная надобность, если пона
|
||||
|
||||
Фазы З1..З5 и список «проверить артефактом до кодинга» — в доке.
|
||||
|
||||
### <a id="arc-atlases"></a>ARC-ATLASES. Атласы — в архивы PBA1, как звук и музыка
|
||||
|
||||
**Что сделано.** Формат PBA1 и читатель готовы и работают на звуке:
|
||||
`toolchain/pop_pack_arc.py` собирает архив (заголовок 512 Б, записи по 4
|
||||
байта — offset в секторах, size в байтах), `pop_arc.c` открывает его ОДИН
|
||||
раз на группу и читает элементы прямо в EMM-страницы. Набор эффектов
|
||||
уехал в `SND\snd.arc` целиком.
|
||||
|
||||
**Что осталось.** Перевести остальные группы: `BG\` (25 файлов), `KID\`
|
||||
(60), `GUARD/SKEL/VIZIER/SHADOW` (~50), `TITLE\` (21), `PV\` (75),
|
||||
`LEVELS\` (15). Каждая группа — свой архив, потому что таблицу держит
|
||||
вызывающий на стеке (4 байта на запись, у PV это 300 Б).
|
||||
|
||||
**Зачем.** Замер 2026-08-25: `open` 51,4 мс против 32,6 мс на чтение 16 КБ.
|
||||
Стартовая пачка ресурсов — около 142 файлов, то есть **7,3 с чистого
|
||||
`open`**, который архивы убирают почти целиком. Второй мотив назван
|
||||
пользователем прямо: «не нравится огромное кол-во файлов» в корне диска.
|
||||
|
||||
**Чем подтверждать.** Время от запуска до первого кадра уровня (сейчас
|
||||
меряется по MAME), число файлов на образе, и что каждая сцена по-прежнему
|
||||
собирается при неполном диске (пути ошибок в `atlas_load`).
|
||||
|
||||
### <a id="mem-next"></a>MEM. Следующий шаг разгрузки W1/W2
|
||||
|
||||
`pop_ctrl.c` уехал в банк 5 ([MEM-BANK5](TASKS_CLOSED.md#mem-bank5), куча
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ТИК ОРИГИНАЛА -> НАШ КАДР. Все длительности сцен взяты из SDLPoP в его
|
||||
* тиках (BASE_FPS = 60), а ждём мы кадрами луча (50 Гц). Без пересчёта
|
||||
* сцена идёт на 20 % дольше — незаметно, пока она немая, и сразу слышно,
|
||||
* когда под неё играет музыка: реплика кончается раньше картинки.
|
||||
* Аргумент — константа сценария, до 2500, так что 32-бит не возникает. */
|
||||
#define POP_T60(t) ((uint16_t)((uint16_t)(t) * 5u / 6u))
|
||||
|
||||
typedef enum {
|
||||
POP_CS_SHOW = 1, /* arg: номер готового кадра */
|
||||
POP_CS_WAIT, /* arg: число логических кадров */
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "pop_music.h"
|
||||
#include "pop_cutscene.h"
|
||||
#include "pop_intro.h"
|
||||
#include "pop_pace.h" /* pop_wait_edge */
|
||||
#include "pop_pal.h"
|
||||
#include "pop_timer.h"
|
||||
#include "pop_ui.h"
|
||||
@@ -25,33 +26,42 @@
|
||||
|
||||
#define INTRO_PARTS 5
|
||||
#define INTRO_FADE 4
|
||||
#define PV_ANIM_TICKS 1959
|
||||
|
||||
/* Оцифрованные эффекты pv_scene() (SDLPoP seg001:422). Музыка сюжета
|
||||
* (50/52/53) в наборе пустая — её у нас нет; эти два ЕСТЬ и в оригинале
|
||||
* звучат именно здесь: створка ворот закрывается, затем открывается дверь
|
||||
* покоев, из которой входит Джафар. */
|
||||
/* Оцифрованные эффекты pv_scene() (SDLPoP seg001:422): створка ворот
|
||||
* закрывается, затем открывается дверь покоев, из которой входит Джафар. */
|
||||
#define PV_SND_GATE_CLOSING 4
|
||||
#define PV_SND_DOOR_OPENING 51
|
||||
|
||||
/* Шкала ниже повторяет порядок pv_scene() SDLPoP. Обычный кадр cutscene
|
||||
* длится 8 тиков, после начала заклинания — 7. Длинные речевые паузы здесь
|
||||
* заданы явно: в DOS они заканчиваются по окончании CBL-сэмпла. */
|
||||
/* Шкала ниже повторяет порядок pv_scene() SDLPoP и живёт в ЕГО тиках
|
||||
* (60 Гц): обычный кадр cutscene длится 8 тиков, до первой смены темпа 6,
|
||||
* после начала заклинания 7. В кадры луча переводит POP_T60.
|
||||
*
|
||||
* ДЛИННЫЕ ПАУЗЫ ЗДЕСЬ — ЭТО РЕПЛИКИ. Оригинал их не отмеряет, а ждёт
|
||||
* окончания сэмпла (`do { frame(1) } while (check_sound_playing())`), и
|
||||
* пока музыки у нас не было, паузы стояли укороченными «на глаз». Теперь
|
||||
* они равны реальной длине наших записей (pop_music_tbl.h, тики = блоки *
|
||||
* 128 / 10937,5 * 60): m50 — 831, m53 — 985 тиков. */
|
||||
enum {
|
||||
PV_WAIT_END = 360,
|
||||
PV_GATE_END = 408,
|
||||
PV_DOOR_END = 432,
|
||||
PV_MUS_2_START = 12, /* frame(2), затем «принцесса ждёт» (m50) */
|
||||
PV_MUS_3_LOAD = 60, /* подкачка следующей реплики, m50 звучит */
|
||||
PV_M50_END = 846, /* 12 + 834: конец m50, кадр становится 8 */
|
||||
PV_WAIT_END = 886, /* +frame(5) -> створка ворот */
|
||||
PV_GATE_END = 934,
|
||||
PV_DOOR_END = 958,
|
||||
PV_TURN_START = PV_DOOR_END,
|
||||
PV_WALK1_START = 472,
|
||||
PV_DIALOG1_START = 520,
|
||||
PV_WALK2_START = 696,
|
||||
PV_DIALOG2_START = 936,
|
||||
PV_RAISE_START = 1216,
|
||||
PV_STEPBACK_START = 1223,
|
||||
PV_MAGIC_START = 1342,
|
||||
PV_EXIT_START = 1490,
|
||||
PV_GLASS_DONE = 1700,
|
||||
PV_SLUMP_START = 1763
|
||||
PV_WALK1_START = 998,
|
||||
PV_DIALOG1_START = 1046, /* Джафар входит — реплика m53 */
|
||||
PV_MUS_4_LOAD = 1100, /* подкачка m52, Джафар стоит на месте */
|
||||
PV_WALK2_START = 1222,
|
||||
PV_DIALOG2_START = 1462,
|
||||
PV_RAISE_START = 1742,
|
||||
PV_STEPBACK_START = 1749,
|
||||
PV_MAGIC_START = 1868,
|
||||
PV_EXIT_START = 2031, /* 1046 + 985: конец m53 */
|
||||
PV_MUS_4_START = 2073, /* +frame(6) -> «Джафар уходит» (m52) */
|
||||
PV_GLASS_DONE = 2241,
|
||||
PV_SLUMP_START = 2304,
|
||||
PV_ANIM_TICKS = 2500
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -103,10 +113,18 @@ static const char * const pv_base_part[INTRO_PARTS] = {
|
||||
|
||||
/* Две текстовые половины show_title(). Между ними PV теперь идёт отдельным
|
||||
* покадровым renderer-ом, а не четырьмя полными статичными композициями. */
|
||||
/* ПОЧЕМУ НЕ 0x258, КАК В ОРИГИНАЛЕ. Точка отсчёта у обоих — пуск m55;
|
||||
* совпасть должен МОМЕНТ ПОЛНОЙ ТЕМНОТЫ, а дороги к нему разные:
|
||||
* SDLPoP: transition_ltr 80 тиков + pop_wait 0x258 + fade_out_2, а тот
|
||||
* гасит палитру за 0x40 шагов по 2 тика = 128 тиков;
|
||||
* у нас: полосовой переход 80 кадров луча (= 96 тиков) + WAIT + fade
|
||||
* всего в четыре ступени палитры (= 5 тиков).
|
||||
* Отсюда 80 + 600 + 128 - 96 - 5 = 707. Дальше и там и там экран уже
|
||||
* чёрный, а трек доигрывает — этой паузой сцена и стыкуется с PV. */
|
||||
static const pop_cs_cmd_t intro_before_pv[] = {
|
||||
/* absence собирается прямо поверх последнего title-кадра полосовым
|
||||
* transition_ltr(), как в SDLPoP; оба экрана используют title.pal. */
|
||||
{ POP_CS_WAIT, 0x258 }, /* story 1: In the absence */
|
||||
{ POP_CS_WAIT, POP_T60(707) }, /* story 1: In the absence */
|
||||
{ POP_CS_FADE_OUT, 0 },
|
||||
{ POP_CS_END, 0 }
|
||||
};
|
||||
@@ -130,13 +148,20 @@ static const pop_cs_cmd_t intro_pv_fallback[] = {
|
||||
{ POP_CS_END, 0 }
|
||||
};
|
||||
|
||||
static const pop_cs_cmd_t intro_after_pv[] = {
|
||||
/* Хвост show_title() разрезан надвое: между «свадьбой» и титрами оригинал
|
||||
* ЖДЁТ окончания последней реплики Джафара (seg000:2051), которая начала
|
||||
* звучать ещё в PV и продолжается поверх смены картинки. */
|
||||
static const pop_cs_cmd_t intro_marry[] = {
|
||||
{ POP_CS_PALETTE, INTRO_PAL_STORY },
|
||||
{ POP_CS_SHOW, INTRO_STORY_MARRY },
|
||||
{ POP_CS_FADE_IN, 0 },
|
||||
{ POP_CS_WAIT, 0x78 },
|
||||
{ POP_CS_END, 0 }
|
||||
};
|
||||
|
||||
static const pop_cs_cmd_t intro_credits[] = {
|
||||
{ POP_CS_WAIT, POP_T60(0x78) },
|
||||
{ POP_CS_SHOW, INTRO_STORY_CREDITS },
|
||||
{ POP_CS_WAIT, 0x168 },
|
||||
{ POP_CS_WAIT, POP_T60(0x168) },
|
||||
{ POP_CS_FADE_OUT, 0 },
|
||||
{ POP_CS_END, 0 }
|
||||
};
|
||||
@@ -592,17 +617,18 @@ static void pv_blit_princess(const atlas_t *princess, const pv_actor_t *actor)
|
||||
166, (uint8_t)!actor->left);
|
||||
}
|
||||
|
||||
/* Внутренняя шкала сценария пока остаётся в исходных тиках 60 Гц: так
|
||||
* сохраняются точки начала реплик и последовательностей. Внешне каждый
|
||||
* такой логический кадр показывается четыре физических кадра Sprinter. */
|
||||
/* Длина логического кадра в тиках 60 Гц — cutscene_frame_time оригинала.
|
||||
* Сколько это кадров луча, решает пейсинг в intro_pv_animated. */
|
||||
static uint8_t pv_seq_period(uint16_t tick)
|
||||
{
|
||||
if (tick < PV_WAIT_END) return 6;
|
||||
if (tick < PV_M50_END) return 6;
|
||||
if (tick < PV_RAISE_START) return 8;
|
||||
return 7;
|
||||
}
|
||||
|
||||
static void intro_pv_draw_frame(const atlas_t *princess, const atlas_t *j0,
|
||||
/* Рисует кадр и меняет страницу; возвращает, сколько кадров луча на это
|
||||
* ушло (вспышка живёт на физической шкале и стоит одиннадцать). */
|
||||
static uint8_t intro_pv_draw_frame(const atlas_t *princess, const atlas_t *j0,
|
||||
const atlas_t *j1, const atlas_t *j2,
|
||||
const atlas_t *hourglass, const atlas_t *torch,
|
||||
const pv_actor_t *p, const pv_actor_t *j,
|
||||
@@ -653,14 +679,9 @@ static void intro_pv_draw_frame(const atlas_t *princess, const atlas_t *j0,
|
||||
pop_pal_flash_white(0);
|
||||
gfx_wait_vsync();
|
||||
}
|
||||
} else {
|
||||
uint8_t hold;
|
||||
|
||||
/* Номинальные 50/4 = 12,5 FPS. Если сама сборка кадра пересекла
|
||||
* лишний фронт, фактическая частота естественно станет ниже; точный
|
||||
* anchor-based pacing оставлен отдельным TODO. */
|
||||
for (hold = 1; hold < 4; hold++) gfx_wait_vsync();
|
||||
return 11;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Сцена pv_scene() SDLPoP: ожидание Princess с факелами/звёздами, поворот,
|
||||
@@ -674,6 +695,7 @@ static int intro_pv_animated(void)
|
||||
uint16_t tick;
|
||||
uint16_t frame_end;
|
||||
uint16_t render_tick;
|
||||
uint16_t beam = 0; /* потрачено кадров луча с начала сцены */
|
||||
uint8_t anim_step = 0;
|
||||
uint8_t torch_left = 1;
|
||||
uint8_t torch_right = 6;
|
||||
@@ -713,10 +735,10 @@ static int intro_pv_animated(void)
|
||||
}
|
||||
|
||||
pop_pal_fade_in(INTRO_FADE);
|
||||
/* Все атласы сцены уже в EMM: дальше файлового ввода-вывода нет, и
|
||||
* насос CBL можно открывать (иначе ESTEX не даёт долить блок — см.
|
||||
* контракт pop_sfx_start). Полноэкранная accel-копия кадра насосу не
|
||||
* мешает: leaf режет DI бандами по 16 строк. */
|
||||
/* Актёрские атласы в EMM; дальше по сцене диск трогает только подкачка
|
||||
* следующей реплики — по одной странице за кадр (pop_music_load_step).
|
||||
* Полноэкранная accel-копия кадра насосу не мешает: leaf режет DI
|
||||
* бандами по 16 строк. */
|
||||
(void)pop_sfx_start();
|
||||
result = 0;
|
||||
princess_actor.x = 120;
|
||||
@@ -754,6 +776,16 @@ static int intro_pv_animated(void)
|
||||
* очередного сэмпла, — заявляем эффект ровно на них. */
|
||||
if (tick == PV_WAIT_END) pop_sfx_play(PV_SND_GATE_CLOSING);
|
||||
else if (tick == PV_GATE_END) pop_sfx_play(PV_SND_DOOR_OPENING);
|
||||
/* Три реплики сцены. Каждая следующая читается с диска, пока
|
||||
* звучит предыдущая (второй слот pop_music), поэтому здесь
|
||||
* остаётся только пуск — мгновенный. */
|
||||
else if (tick == PV_MUS_2_START) pop_music_play();
|
||||
else if (tick == PV_MUS_3_LOAD)
|
||||
(void)pop_music_load_begin(POP_MUS_STORY_3);
|
||||
else if (tick == PV_DIALOG1_START) pop_music_play();
|
||||
else if (tick == PV_MUS_4_LOAD)
|
||||
(void)pop_music_load_begin(POP_MUS_STORY_4);
|
||||
else if (tick == PV_MUS_4_START) pop_music_play();
|
||||
++tick;
|
||||
} while (tick < frame_end);
|
||||
|
||||
@@ -768,13 +800,30 @@ static int intro_pv_animated(void)
|
||||
++anim_step;
|
||||
}
|
||||
render_tick = (uint16_t)(tick - 1);
|
||||
intro_pv_draw_frame(&princess, &jaffar0, &jaffar1, &jaffar2,
|
||||
&hourglass, &torch, &princess_actor,
|
||||
&jaffar_actor, star_phase, torch_left,
|
||||
torch_right, sand_frame, flash, render_tick);
|
||||
beam += intro_pv_draw_frame(&princess, &jaffar0, &jaffar1, &jaffar2,
|
||||
&hourglass, &torch, &princess_actor,
|
||||
&jaffar_actor, star_phase, torch_left,
|
||||
torch_right, sand_frame, flash,
|
||||
render_tick);
|
||||
pop_sfx_tick(); /* как в оригинале — в конце отрисовки кадра */
|
||||
/* Страницу следующей реплики читаем ЗДЕСЬ, до досыпания кадра:
|
||||
* 33 мс диска укладываются в паузу логического кадра (133 мс) и
|
||||
* анимации не стоят. */
|
||||
if (pop_music_loading()) (void)pop_music_load_step();
|
||||
/* ПЕЙСИНГ ПО ОРИГИНАЛЬНОЙ ШКАЛЕ. Кадр cutscene — 6/8/7 тиков по
|
||||
* 1/60 с, наш экран идёт на 1/50: без пересчёта сцена растянулась
|
||||
* бы на пятую часть, и реплики разъехались бы с картинкой. */
|
||||
{
|
||||
uint16_t target = POP_T60(tick);
|
||||
|
||||
while (beam < target) { gfx_wait_vsync(); beam++; }
|
||||
}
|
||||
}
|
||||
pop_sfx_pause(); /* дальше снова только загрузка файлов */
|
||||
/* CBL НЕ ГЛУШИМ: последняя реплика Джафара звучит поверх смены сцены —
|
||||
* ровно как в оригинале, где show_title ждёт её окончания уже на
|
||||
* экране «свадьбы» (seg000:2051). Глушит вызывающий, когда музыка
|
||||
* действительно доиграла. */
|
||||
if (result) { pop_music_free(); pop_sfx_pause(); }
|
||||
if (!result) pop_pal_fade_out(INTRO_FADE);
|
||||
|
||||
free_torch:
|
||||
@@ -872,14 +921,24 @@ free_hourglass_pre:
|
||||
* видимый title узкими вертикальными полосами слева направо. У оригинала
|
||||
* 2px/120 FPS; здесь 4px/60 FPS даёт ту же длительность (~1.3 с), не
|
||||
* удваивая число полных копий 320x256 в ограниченном bank 9. */
|
||||
/* Собрать «In the absence» в СКРЫТУЮ страницу. Отделено от показа: между
|
||||
* ними успевает загрузиться трек, а на экране всё это время держится
|
||||
* последний кадр титров — ровно как у оригинала, который рисует историю в
|
||||
* offscreen и лишь потом пускает transition_ltr. */
|
||||
static int intro_story_prepare(void)
|
||||
{
|
||||
uint8_t target = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||||
|
||||
return intro_draw_page(INTRO_STORY_ABSENCE, target);
|
||||
}
|
||||
|
||||
/* Полосовой показ подготовленной страницы (transition_ltr оригинала). */
|
||||
static int intro_story_transition(void)
|
||||
{
|
||||
gfx_rect_t stripe;
|
||||
uint8_t visible = gfx_get_visible_page();
|
||||
uint8_t target = (uint8_t)(visible ^ 1);
|
||||
int x;
|
||||
|
||||
if (intro_draw_page(INTRO_STORY_ABSENCE, target) != 0) return -1;
|
||||
gfx_set_draw_page(visible); /* source gfx_copy_page = target */
|
||||
stripe.y = 0;
|
||||
stripe.h = GFX_HEIGHT;
|
||||
@@ -1119,8 +1178,13 @@ static uint8_t cut_run(uint16_t frames, uint8_t kid_visible)
|
||||
pv_blit_floor(&cut_torch, cut_torch_l, 93, 116);
|
||||
pv_blit_floor(&cut_torch, cut_torch_r, 211, 116);
|
||||
pv_draw_stars(cut_star_phase);
|
||||
pv_blit_floor(&cut_hg, cut_hg_state, 152, 168);
|
||||
pv_blit_floor(&cut_hg, (uint8_t)(8 + cut_sand), 160, 164);
|
||||
/* hourglass_state == 0 — часов в комнате нет вовсе (reset_cutscene
|
||||
* SDLPoP ставит state=0 и sandflow=-1). Так идёт финальная встреча
|
||||
* с принцессой: время уже не считается, это победа. */
|
||||
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);
|
||||
}
|
||||
cut_blit_princess(&Guard, &pr_fr);
|
||||
if (kid_visible) cut_blit_kid(&Kid, &kid_fr);
|
||||
/* ПОСЛЕ персонажей: передняя колонна — передний слой комнаты, она
|
||||
@@ -1144,16 +1208,17 @@ static uint8_t cut_run(uint16_t frames, uint8_t kid_visible)
|
||||
}
|
||||
|
||||
/* Общая обвязка всех трёх сцен: чистый фон комнаты принцессы в обе
|
||||
* страницы, PV-палитра, актёрские атласы. Возврат -1 — ресурсов нет
|
||||
* страницы, PV-палитра, актёрские атласы. hg_state — кадр песочных часов
|
||||
* (0 = часов нет). Возврат -1 — ресурсов нет
|
||||
* (неполный HDD), и caller откатывается на статический сценарий. */
|
||||
static int cut_begin(void)
|
||||
static int cut_begin(uint8_t hg_state)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
intro_skip_begin(&cut_skip);
|
||||
cut_torch_l = 1; cut_torch_r = 6; cut_sand = 0;
|
||||
for (i = 0; i < 6; i++) cut_star_phase[i] = 1;
|
||||
cut_hg_state = pre_hourglass_state();
|
||||
cut_hg_state = hg_state;
|
||||
intro_prepare_palette(INTRO_PAL_PV);
|
||||
intro_commit_palette();
|
||||
if (intro_pv_draw_base() != 0) return -1;
|
||||
@@ -1173,7 +1238,7 @@ static int cut_scene_8(void)
|
||||
{
|
||||
uint8_t aborted;
|
||||
|
||||
if (cut_begin() != 0) return -1;
|
||||
if (cut_begin(pre_hourglass_state()) != 0) return -1;
|
||||
/* init_mouse_cu8 (seg001:0212) = init_mouse_go + x=144 + seq_106. */
|
||||
cut_kid_init(CHARID_24_MOUSE, 144, 167, 106);
|
||||
/* princess_crouching (seg001:024D): сидит и гладит мышь. */
|
||||
@@ -1198,7 +1263,7 @@ static int cut_scene_9(void)
|
||||
{
|
||||
uint8_t aborted;
|
||||
|
||||
if (cut_begin() != 0) return -1;
|
||||
if (cut_begin(pre_hourglass_state()) != 0) return -1;
|
||||
/* princess_stand (seg001:026A) — поза PV1, лицом вправо. */
|
||||
cut_shad_init(144, 169, 94);
|
||||
Guard.direction = 0;
|
||||
@@ -1224,7 +1289,10 @@ static int cut_ending(void)
|
||||
{
|
||||
uint8_t aborted;
|
||||
|
||||
if (cut_begin() != 0) return -1;
|
||||
/* end_sequence_anim (seg001:041C) НЕ трогает hourglass_state, а
|
||||
* reset_cutscene перед сценой обнулил его: песочных часов в финале нет.
|
||||
* Это и логично — уровень пройден, время больше не идёт. */
|
||||
if (cut_begin(0) != 0) return -1;
|
||||
/* init_ending_princess (seg001:0251) + init_ending_kid (seg001:0287). */
|
||||
cut_shad_init(136, 164, 109);
|
||||
cut_kid_init(CHARID_0_KID, 198, 164, 1);
|
||||
@@ -1296,31 +1364,42 @@ uint8_t pop_intro_show(void) __banked
|
||||
{
|
||||
int pv_result;
|
||||
|
||||
/* Title уже заглушил CBL; оставляем это явным lifecycle-правилом, если
|
||||
* intro позднее будет вызвано отдельно (например, из debug-маршрута). */
|
||||
pop_sfx_pause();
|
||||
if (intro_story_transition() != 0) {
|
||||
/* МУЗЫКА ПРИХОДИТ СЮДА ЗВУЧАЩЕЙ. На титрах играет тема (m54), и
|
||||
* обрывать её нельзя: у оригинала заставка и история — одна звуковая
|
||||
* дорожка, где следующая реплика начинается там, где кончилась
|
||||
* предыдущая. Поэтому здесь ни pop_sfx_pause, ни pop_sfx_start:
|
||||
* CBL уже открыт титрами. */
|
||||
if (intro_story_prepare() != 0) {
|
||||
/* Ошибка файла не должна оставить старый title без выхода: менее
|
||||
* эффектный, но корректный fallback остаётся обычным fade-in. */
|
||||
pop_music_free();
|
||||
pop_sfx_pause();
|
||||
intro_prepare_palette(INTRO_PAL_STORY);
|
||||
if (intro_draw_new_palette(INTRO_STORY_ABSENCE) == 0)
|
||||
pop_pal_fade_in(INTRO_FADE);
|
||||
}
|
||||
/* Первый экран истории — единственное место, где музыка уже звучит.
|
||||
* Грузим ПОСЛЕ того, как картинка собрана: чтение кусков идёт через
|
||||
* ESTEX, и при открытом CBL насос не успел бы долить блок (скрежет,
|
||||
* см. pop_sfx_start). Порядок обязателен: load -> start -> play. */
|
||||
if (pop_music_load(POP_MUS_STORY_1) == 0) {
|
||||
(void)pop_sfx_start();
|
||||
} else {
|
||||
/* Картинка собрана и ждёт в скрытой странице; читаем следующую
|
||||
* реплику во ВТОРОЙ слот — тема титров при этом продолжает
|
||||
* звучать из первого. */
|
||||
(void)pop_music_load(POP_MUS_STORY_1);
|
||||
/* Тему дослушиваем до конца, как seg000:2027, и только потом
|
||||
* пускаем историю: пуск и полосовой переход идут встык. */
|
||||
{
|
||||
intro_skip_t head;
|
||||
intro_skip_begin(&head);
|
||||
while (pop_music_busy() && !intro_skip_requested(&head))
|
||||
pop_wait_edge();
|
||||
}
|
||||
(void)pop_sfx_start(); /* идемпотентно: титры его уже открыли */
|
||||
pop_music_play();
|
||||
(void)intro_story_transition();
|
||||
}
|
||||
if (intro_run(intro_before_pv)) {
|
||||
/* Пропуск по клавише. ГЛУШИМ ВЫВОД: сразу за возвратом пойдёт
|
||||
* загрузка уровня, а при открытом CBL ESTEX уходит в диск дольше,
|
||||
* чем играет буфер, и железо начинает крутить свои 256 байт по
|
||||
* кругу — на слух ровно тот скрежет, что ловится нажатием клавиши
|
||||
* на первом экране истории. В обычной ветке ниже pause уже есть,
|
||||
* а здесь его не было. */
|
||||
* на первом экране истории. */
|
||||
pop_music_free();
|
||||
pop_sfx_pause();
|
||||
intro_restore_game_palette();
|
||||
@@ -1329,26 +1408,47 @@ uint8_t pop_intro_show(void) __banked
|
||||
/* ДОИГРАТЬ ПОД ЧЁРНЫМ ЭКРАНОМ. Оригинал не обрывает story-трек на
|
||||
* переходе к PV: fade уводит картинку, а музыка продолжается, и лишь
|
||||
* когда она смолкла, начинается сцена с принцессой (у SDLPoP это
|
||||
* общий приём — `while (check_sound_playing())`, seg000:2027).
|
||||
* Мы освобождали страницы сразу после fade, и трек обрывался на
|
||||
* полуслове — на слух это и был скрежет в конце затемнения. */
|
||||
* общий приём — `while (check_sound_playing())`, seg000:2027). */
|
||||
{
|
||||
intro_skip_t tail;
|
||||
intro_skip_begin(&tail);
|
||||
while (pop_music_busy() && !intro_skip_requested(&tail)) pop_wait_edge();
|
||||
}
|
||||
pop_music_free();
|
||||
/* Первая реплика PV читается ЗДЕСЬ, под чёрным экраном и в тишине:
|
||||
* дальше внутри сцены остаётся только пуск, а следующие две реплики
|
||||
* подкачиваются по странице за кадр. */
|
||||
pop_sfx_pause();
|
||||
(void)pop_music_load(POP_MUS_STORY_2);
|
||||
pv_result = intro_pv_animated();
|
||||
if (pv_result > 0) {
|
||||
pop_music_free();
|
||||
pop_sfx_pause();
|
||||
intro_restore_game_palette();
|
||||
return 1;
|
||||
}
|
||||
if (pv_result < 0 && intro_run(intro_pv_fallback)) {
|
||||
pop_music_free();
|
||||
pop_sfx_pause();
|
||||
intro_restore_game_palette();
|
||||
return 1;
|
||||
}
|
||||
if (intro_run(intro_after_pv)) {
|
||||
/* «Свадьба» показывается ПОВЕРХ звучащей реплики Джафара: она началась
|
||||
* в PV и переживает и fade-out сцены, и fade-in этого экрана. */
|
||||
if (intro_run(intro_marry)) {
|
||||
pop_music_free();
|
||||
pop_sfx_pause();
|
||||
intro_restore_game_palette();
|
||||
return 1;
|
||||
}
|
||||
{
|
||||
intro_skip_t marry;
|
||||
intro_skip_begin(&marry);
|
||||
while (pop_music_busy() && !intro_skip_requested(&marry))
|
||||
pop_wait_edge();
|
||||
}
|
||||
pop_music_free();
|
||||
pop_sfx_pause();
|
||||
if (intro_run(intro_credits)) {
|
||||
intro_restore_game_palette();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,18 @@
|
||||
#include "pop_sfx.h"
|
||||
|
||||
uint8_t pop_mus_page[POP_MUS_PAGES];
|
||||
static uint8_t mus_blk; /* блок EMM под трек */
|
||||
static uint8_t mus_pages; /* сколько страниц реально занято */
|
||||
static uint16_t mus_blocks; /* длина трека в порциях по 128 Б */
|
||||
static uint8_t mus_id; /* что загружено — для менеджера звука */
|
||||
|
||||
/* Два слота: играющий и подгружаемый. Своей таблицы страниц слот НЕ
|
||||
* держит — её и так хранит блок EMM, и mem_get_page отдаёт номер по
|
||||
* индексу. Резидентный pop_mus_page[] заполняется на pop_music_play, для
|
||||
* одного слота: 40 байт W2 у игры лишних нет. */
|
||||
#define MUS_SLOTS 2
|
||||
static uint8_t slot_blk[MUS_SLOTS]; /* блок EMM, 0 — слот пуст */
|
||||
static uint8_t slot_pages[MUS_SLOTS];
|
||||
static uint16_t slot_blocks[MUS_SLOTS];
|
||||
static uint8_t slot_id[MUS_SLOTS];
|
||||
static uint8_t mus_cur; /* слот, что играет/играл */
|
||||
static uint8_t mus_ready; /* слот+1, готовый к запуску */
|
||||
|
||||
/* "MUS\mNN.bin" — ровно 8.3, как требует DSS. */
|
||||
static void mus_name(char *buf, uint8_t id)
|
||||
@@ -34,6 +42,25 @@ static void mus_name(char *buf, uint8_t id)
|
||||
buf[11] = 0;
|
||||
}
|
||||
|
||||
/* ld_busy отдельно от ld_fd: инициализаторов у статиков в банке нет
|
||||
* (crt0 зануляет _DATA), а «нет открытого файла» — это не ноль. */
|
||||
static uint8_t ld_busy;
|
||||
static int ld_fd;
|
||||
static uint8_t ld_slot;
|
||||
static uint8_t ld_next; /* следующая страница к чтению */
|
||||
static uint8_t ld_pages;
|
||||
static uint8_t ld_id;
|
||||
|
||||
static void slot_release(uint8_t n)
|
||||
{
|
||||
if (slot_blk[n]) {
|
||||
mem_free_block(slot_blk[n]);
|
||||
slot_blk[n] = 0;
|
||||
}
|
||||
slot_pages[n] = 0;
|
||||
slot_blocks[n] = 0;
|
||||
}
|
||||
|
||||
void pop_music_stop(void) __banked
|
||||
{
|
||||
pop_mus_left = 0;
|
||||
@@ -41,63 +68,118 @@ void pop_music_stop(void) __banked
|
||||
|
||||
void pop_music_free(void) __banked
|
||||
{
|
||||
if (ld_busy) { close(ld_fd); ld_busy = 0; }
|
||||
pop_mus_left = 0;
|
||||
if (mus_blk) {
|
||||
mem_free_block(mus_blk);
|
||||
mus_blk = 0;
|
||||
}
|
||||
mus_pages = 0;
|
||||
mus_blocks = 0;
|
||||
slot_release(0);
|
||||
slot_release(1);
|
||||
mus_ready = 0;
|
||||
}
|
||||
|
||||
int8_t pop_music_load(uint8_t id) __banked
|
||||
/* --- загрузка ---------------------------------------------------------
|
||||
*
|
||||
* ПОСТРАНИЧНО, А НЕ ЦЕЛИКОМ. Реплики сцены с принцессой идут встык, и
|
||||
* следующую надо читать, пока звучит текущая. Трек — до 320 КБ, целиком
|
||||
* это 0,7 с диска: на 7,5 кадрах в секунду сцена встала бы на пять кадров.
|
||||
* Одна страница стоит 33 мс — меньше четверти кадра, и подкачка растворя-
|
||||
* ется в анимации. Кто может позволить себе паузу (титры, чёрный экран
|
||||
* между сценами), зовёт pop_music_load — тот же цикл, но целиком. */
|
||||
static void load_abort(void)
|
||||
{
|
||||
if (ld_busy) close(ld_fd);
|
||||
ld_busy = 0;
|
||||
slot_release(ld_slot);
|
||||
}
|
||||
|
||||
int8_t pop_music_load_begin(uint8_t id) __banked
|
||||
{
|
||||
char name[12];
|
||||
uint8_t i, pages;
|
||||
int fd;
|
||||
uint8_t pages, n;
|
||||
|
||||
if (ld_busy) load_abort();
|
||||
if (id >= POP_MUS_COUNT) return -1;
|
||||
pages = pop_mus_tbl[id].pages;
|
||||
if (!pages || pages > POP_MUS_PAGES) return -1;
|
||||
|
||||
pop_music_free();
|
||||
/* Грузим ВСЕГДА в не текущий слот: играющий трек читает свои страницы
|
||||
* из прерывания, и отобрать их у него нельзя. */
|
||||
n = (uint8_t)(mus_cur ^ 1);
|
||||
slot_release(n);
|
||||
mus_name(name, id);
|
||||
/* Трек лежит ОДНИМ файлом и читается порциями по странице: `open` стоит
|
||||
* дороже самого чтения (51,4 мс против 32,6 мс на 16 КБ), и платить его
|
||||
* на каждую страницу — чистые потери. Тот же путь понадобится финалу
|
||||
* `won`, который в память целиком не влезает. */
|
||||
fd = open(name, O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
mus_blk = mem_alloc_pages(pages);
|
||||
if (!mus_blk) { close(fd); return -1; }
|
||||
for (i = 0; i < pages; i++) {
|
||||
pop_mus_page[i] = mem_get_page(mus_blk, i);
|
||||
/* Последняя страница короче — читаем сколько есть; ошибку ловим по
|
||||
* отрицательному коду, а не по «не 16384». */
|
||||
if (!pop_mus_page[i] ||
|
||||
bank_read_page(fd, pop_mus_page[i], 0, 16384) < 0) {
|
||||
close(fd);
|
||||
pop_music_free();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
mus_pages = pages;
|
||||
mus_blocks = pop_mus_tbl[id].blocks;
|
||||
mus_id = id;
|
||||
/* Трек лежит ОДНИМ файлом, и fd остаётся открытым до последней
|
||||
* страницы: `open` стоит дороже самого чтения (51,4 мс против 32,6 мс
|
||||
* на 16 КБ), и платить его на каждую страницу — чистые потери. */
|
||||
ld_fd = open(name, O_RDONLY);
|
||||
if (ld_fd < 0) return -1;
|
||||
slot_blk[n] = mem_alloc_pages(pages);
|
||||
if (!slot_blk[n]) { close(ld_fd); return -1; }
|
||||
ld_busy = 1;
|
||||
ld_slot = n;
|
||||
ld_next = 0;
|
||||
ld_pages = pages;
|
||||
ld_id = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t pop_music_load_step(void) __banked
|
||||
{
|
||||
uint8_t p;
|
||||
|
||||
if (!ld_busy) return 1; /* нечего грузить */
|
||||
p = mem_get_page(slot_blk[ld_slot], ld_next);
|
||||
/* Последняя страница короче — читаем сколько есть; ошибку ловим по
|
||||
* отрицательному коду, а не по «не 16384». */
|
||||
if (!p || bank_read_page(ld_fd, p, 0, 16384) < 0) {
|
||||
load_abort();
|
||||
return -1;
|
||||
}
|
||||
if (++ld_next < ld_pages) return 0;
|
||||
|
||||
close(ld_fd);
|
||||
ld_busy = 0;
|
||||
slot_pages[ld_slot] = ld_pages;
|
||||
slot_blocks[ld_slot] = pop_mus_tbl[ld_id].blocks;
|
||||
slot_id[ld_slot] = ld_id;
|
||||
mus_ready = (uint8_t)(ld_slot + 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t pop_music_loading(void) __banked
|
||||
{
|
||||
return ld_busy;
|
||||
}
|
||||
|
||||
int8_t pop_music_load(uint8_t id) __banked
|
||||
{
|
||||
int8_t r;
|
||||
|
||||
if (pop_music_load_begin(id) != 0) return -1;
|
||||
do {
|
||||
r = pop_music_load_step();
|
||||
} while (r == 0);
|
||||
return (r < 0) ? -1 : 0;
|
||||
}
|
||||
|
||||
void pop_music_play(void) __banked
|
||||
{
|
||||
if (!mus_pages) return;
|
||||
uint8_t n, i;
|
||||
|
||||
if (!mus_ready) return;
|
||||
n = (uint8_t)(mus_ready - 1);
|
||||
/* Насос обязан замолчать ДО подмены таблицы страниц: иначе он успеет
|
||||
* взять порцию по новому индексу из старого курсора. */
|
||||
pop_mus_left = 0;
|
||||
for (i = 0; i < slot_pages[n]; i++)
|
||||
pop_mus_page[i] = mem_get_page(slot_blk[n], i);
|
||||
if (n != mus_cur) slot_release(mus_cur);
|
||||
mus_cur = n;
|
||||
mus_ready = 0;
|
||||
/* Порядок важен: сначала позиция, потом длина. Насос стартует ровно
|
||||
* с той порции, где left стал ненулевым, и промежуточное состояние
|
||||
* «играем, но курсор от прошлого трека» не возникает. */
|
||||
pop_mus_id = mus_id; /* до left: менеджер спросит уже готовое */
|
||||
pop_mus_id = slot_id[n]; /* до left: менеджер спросит готовое */
|
||||
pop_mus_pg = 0;
|
||||
pop_mus_ptr = 0;
|
||||
pop_mus_left = mus_blocks;
|
||||
pop_mus_left = slot_blocks[n];
|
||||
}
|
||||
|
||||
uint8_t pop_music_busy(void) __banked
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* Музыка ПО ХОДУ игры (джинглы смерти, зелья) — отдельный вопрос, её
|
||||
* придётся либо гасить эффектами, либо уводить на AY.
|
||||
*
|
||||
* ФОРМАТ на диске — пачка кусков m<id>_<nn>.bin по 16 КБ (bank_load_file
|
||||
* читает файл только целиком и только в одну страницу). Каталог с
|
||||
* длинами генерирует toolchain/pop_pack_music.py в pop_music_tbl.h.
|
||||
* ФОРМАТ на диске — один файл MUS\m<id>.bin на трек, читается порциями по
|
||||
* 16 КБ из одного открытого fd (bank_read_page). Каталог с длинами
|
||||
* генерирует toolchain/pop_pack_music.py в pop_music_tbl.h.
|
||||
*/
|
||||
#ifndef POP_MUSIC_H
|
||||
#define POP_MUSIC_H
|
||||
@@ -29,7 +29,11 @@
|
||||
#define POP_MUS_PAGES 20
|
||||
|
||||
/* Номера треков — нумерация оригинала (SDLPoP data/music/names.txt). */
|
||||
#define POP_MUS_STORY_1 55 /* «In the absence of the Sultan...» */
|
||||
#define POP_MUS_INTRO 54 /* тема заставки, звучит на титрах */
|
||||
#define POP_MUS_STORY_1 55 /* «In the absence of the Sultan...» */
|
||||
#define POP_MUS_STORY_2 50 /* принцесса ждёт */
|
||||
#define POP_MUS_STORY_3 53 /* входит Джафар */
|
||||
#define POP_MUS_STORY_4 52 /* Джафар уходит */
|
||||
|
||||
/* Курсор проигрывания — читает НАСОС (pop_sfx.c, резидент), пишет
|
||||
* загрузчик. Отдельный от эффектов: они звучат поверх музыки, а не
|
||||
@@ -42,16 +46,34 @@ extern volatile uint16_t pop_mus_left; /* порций до конца, 0
|
||||
* музыка участвует в таблицах приоритета наравне с эффектами. */
|
||||
extern uint8_t pop_mus_id;
|
||||
|
||||
/* Загрузить трек в EMM (НЕ запускает). Звать там, где допустима пауза на
|
||||
* диск: CBL в это время обязан быть закрыт, иначе насос не успевает долить
|
||||
* блок и железо повторяет старый — на слух скрежет (pop_sfx.h).
|
||||
/* ДВА СЛОТА. Заставочные сцены идут цепочкой треков (титры -> история ->
|
||||
* PV из трёх реплик), и загрузка каждого — это полсекунды-секунда диска.
|
||||
* Прятать её в паузу между репликами негде: пауз в оригинале нет, там
|
||||
* следующая реплика начинается сразу. Поэтому слотов два: пока звучит
|
||||
* один, следующий читается во второй, и переключение стоит копирования
|
||||
* двадцати байт таблицы страниц.
|
||||
*
|
||||
* Загрузить трек в свободный слот (НЕ запускает; играющий трек не трогает).
|
||||
* 0 — готово, -1 — нет трека/памяти/файла. */
|
||||
int8_t pop_music_load(uint8_t id) __banked;
|
||||
|
||||
/* Отпустить страницы. Останавливает проигрывание, если оно шло. */
|
||||
/* ТА ЖЕ ЗАГРУЗКА ПО ОДНОЙ СТРАНИЦЕ ЗА ВЫЗОВ — для тех, кому нельзя
|
||||
* замереть на секунду: страница стоит 33 мс, то есть четверть кадра
|
||||
* заставочной сцены, и подкачка следующей реплики не видна.
|
||||
* begin — открыть файл и занять страницы (0 / -1);
|
||||
* step — прочитать очередную: 0 — ещё есть, 1 — трек готов к play,
|
||||
* -1 — ошибка (слот освобождён);
|
||||
* loading — идёт ли чтение (звать step имеет смысл, пока 1).
|
||||
* Оборвать начатое чтение можно pop_music_free. */
|
||||
int8_t pop_music_load_begin(uint8_t id) __banked;
|
||||
int8_t pop_music_load_step(void) __banked;
|
||||
uint8_t pop_music_loading(void) __banked;
|
||||
|
||||
/* Отпустить ОБА слота. Останавливает проигрывание, если оно шло. */
|
||||
void pop_music_free(void) __banked;
|
||||
|
||||
/* Пустить загруженный трек с начала / оборвать. */
|
||||
/* Пустить последний загруженный трек с начала; слот предыдущего при этом
|
||||
* освобождается. Без парного load ничего не делает. */
|
||||
void pop_music_play(void) __banked;
|
||||
void pop_music_stop(void) __banked;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#define POP_MUS_COUNT 56
|
||||
#define POP_MUS_BLOCK 128
|
||||
#define POP_MUS_MAX_PAGES 11
|
||||
#define POP_MUS_MAX_PAGES 20
|
||||
|
||||
typedef struct {
|
||||
uint8_t pages; /* 0 — трека нет */
|
||||
@@ -72,11 +72,11 @@ static const pop_mus_ent_t pop_mus_tbl[POP_MUS_COUNT] = {
|
||||
{ 0, 0 }, /* 47 */
|
||||
{ 0, 0 }, /* 48 */
|
||||
{ 0, 0 }, /* 49 */
|
||||
{ 0, 0 }, /* 50 */
|
||||
{ 10, 1184 }, /* 50 story_2_princess */
|
||||
{ 0, 0 }, /* 51 */
|
||||
{ 0, 0 }, /* 52 */
|
||||
{ 0, 0 }, /* 53 */
|
||||
{ 0, 0 }, /* 54 */
|
||||
{ 20, 2443 }, /* 52 story_4_Jaffar_leaves */
|
||||
{ 11, 1403 }, /* 53 story_3_Jaffar_enters */
|
||||
{ 17, 2064 }, /* 54 intro_theme */
|
||||
{ 11, 1350 }, /* 55 story_1_absence */
|
||||
};
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <sprite.h>
|
||||
#include "pop_bg.h" /* POP_YOFF: title занимает 200px по центру 256px */
|
||||
#include "pop_sfx.h"
|
||||
#include "pop_music.h"
|
||||
#include "pop_cutscene.h" /* POP_T60 */
|
||||
#include "pop_settings.h"
|
||||
#include "pop_pal.h"
|
||||
#include "pop_title.h"
|
||||
@@ -42,12 +44,19 @@ static const char * const title_part[4][TITLE_PARTS] = {
|
||||
/* Последовательность и интервалы буквально из show_title():
|
||||
* main --0x82--> presents --0xCD--> main --0x41--> game
|
||||
* --0x10E--> main --0xEB--> logo+Mechner.
|
||||
* SDLPoP BASE_FPS = 60, поэтому один счёт = один Sprinter vsync. */
|
||||
* Счёт — в тиках 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[] = {
|
||||
0x82, 0xCD, 0x41, 0x10E, 0xEB, 0xEB
|
||||
POP_T60(0x82), POP_T60(0xCD), POP_T60(0x41),
|
||||
POP_T60(0x10E), POP_T60(0xEB), POP_T60(0xEB)
|
||||
};
|
||||
|
||||
static uint8_t title_shown;
|
||||
@@ -100,8 +109,8 @@ uint8_t pop_title_show(void) __banked
|
||||
uint8_t cold_start = (uint8_t)(!title_shown &&
|
||||
!pop_settings.show_build_info);
|
||||
|
||||
/* До музыки title дойдём в FG8; игровые SFX не должны звучать поверх
|
||||
* немой заставки и не должны обслуживаться во время дисковых чтений. */
|
||||
/* Игровые SFX не должны тянуться сюда из прерванной партии: заставка
|
||||
* начинается с тишины, а через несколько строк её займёт тема титров. */
|
||||
pop_sfx_pause();
|
||||
if (!cold_start) pop_pal_fade_out(TITLE_FADE);
|
||||
|
||||
@@ -117,6 +126,14 @@ uint8_t pop_title_show(void) __banked
|
||||
pop_screen_present((uint8_t)prepared);
|
||||
title_shown = 1;
|
||||
|
||||
/* Тема заставки. Порядок как на первом экране истории: сначала диск,
|
||||
* потом открытие CBL, потом пуск — иначе первый же блок насос отдал бы
|
||||
* из ещё не дочитанной страницы. */
|
||||
if (pop_music_load(POP_MUS_INTRO) == 0) {
|
||||
(void)pop_sfx_start();
|
||||
pop_music_play();
|
||||
}
|
||||
|
||||
for (step = 0; step < sizeof(title_scene); step++) {
|
||||
if (step && title_draw(title_scene[step], 1) < 0)
|
||||
break;
|
||||
@@ -125,6 +142,21 @@ uint8_t pop_title_show(void) __banked
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user