47a4b084c4
Эта сессия (меню + текст в служебных полосах):
* Меню: рамка выделения считается от силуэта текста (SEL_PAD сверху и
снизу), а не «на глаз»; все экраны центрируются в игровых 200 строках
(UI_CENTER_TOP/UI_CENTER_FIELD) и в них помещаются; Enter и Space —
равноправные клавиши действия (ui_action_down).
* Settings: убраны SHOW SPRINTER SCREEN и BACK, добавлены DEBUG BAR и
ABOUT. About показывает тот же текст, что стартовый Sprinter screen,
минус строка про клавиши — общий about_text(), чтобы экраны не
разъехались. CONTROLS собирается таблицей и центрируется по
фактическому числу строк.
* GAME PAUSED переехала в нижнюю статус-строку, как в оригинале
(SDLPoP rect_bottom_text = {193,70,202,250}): это состояние программы,
а не пункт меню. POP_HP_Y вынесен в pop_cdraw.h — полосу делят два
модуля.
* pop_status.c (банк 9) — текст в обеих служебных полосах. Нижняя:
порт display_text_bottom + таймера (QUICKSAVE/QUICKLOAD/SOUND ON/OFF,
24 тика). Верхняя отладочная переведена с палочек на текст
«Level ##, Room ##, Speed: …, Sound: …, Immortal #» малым шрифтом, с
своим форматированием чисел (без printf и без деления).
Заявка сообщения — запись одного байта pop_status_msg: резидент W1/W2
не растёт, весь рендер в банке. Бюджет после правок не изменился
(_CODE 23981, куча 267 Б).
* Цена вывода: блит глифа ~4,6 тыс. тактов независимо от размера, поэтому
всё change-driven, отладочная строка перерисовывается ПО ПОЛЯМ, пробелы
не блитятся вовсе, а вход в комнату заливает только игровое поле
(pop_screen_fill_field) — борта от комнаты к комнате не меняются.
* Интро: в PV-сцене зазвучали пропавшие эффекты оригинала — закрытие
ворот (4) и открытие двери покоев (51), из которой входит Джафар.
* docs/status_line_text.md — полная инвентаризация ВСЕХ текстов SDLPoP в
статус-строке: геометрия, семантика text_time_total как идентификатора
сообщения, мигание, рестарт по истечении 36/288.
Вместе с этим выкладывается накопленная работа по оболочке полной игры:
автомат состояний (pop_app), title, intro/PV и cutscene, attract-demo,
Hall of Fame, глобальный таймер, настройки и POP.CFG, модуль палитр и
fade, звуковой набор, host-тесты на новые швы.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1384 lines
58 KiB
C
1384 lines
58 KiB
C
/*
|
||
* pop_intro.c — вторая половина show_title(): story + Princess/Jaffar (FG8).
|
||
*
|
||
* Готовые сцены создаёт pop_pack_intro.py из TITLE/PV SDLPoP. Каждая сцена
|
||
* хранится пятью полосами <=49 строк, поэтому EMM удерживает лишь текущую
|
||
* полосу. pop_cutscene_t остаётся независимым от графики: этот модуль только
|
||
* исполняет его события и позднее будет образцом для pre-level сцен FG9.
|
||
*/
|
||
#include <stdint.h>
|
||
#include <kbd_raw.h>
|
||
#include <graphics.h>
|
||
#include <gfx.h>
|
||
#include <sprite.h>
|
||
#include "pop_bg.h"
|
||
#include "pop_shadow.h"
|
||
#include "pop_sfx.h"
|
||
#include "pop_cutscene.h"
|
||
#include "pop_intro.h"
|
||
#include "pop_pal.h"
|
||
#include "pop_timer.h"
|
||
#include "pop_ui.h"
|
||
#include "pop_kid.h" /* Char/окна, play_seq — движок персонажей катсцен */
|
||
#include "pop_guard.h" /* Guard, CHARID_*, pop_gframe */
|
||
|
||
#define INTRO_PARTS 5
|
||
#define INTRO_FADE 4
|
||
#define PV_ANIM_TICKS 1959
|
||
|
||
/* Оцифрованные эффекты pv_scene() (SDLPoP seg001:422). Музыка сюжета
|
||
* (50/52/53) в наборе пустая — её у нас нет; эти два ЕСТЬ и в оригинале
|
||
* звучат именно здесь: створка ворот закрывается, затем открывается дверь
|
||
* покоев, из которой входит Джафар. */
|
||
#define PV_SND_GATE_CLOSING 4
|
||
#define PV_SND_DOOR_OPENING 51
|
||
|
||
/* Шкала ниже повторяет порядок pv_scene() SDLPoP. Обычный кадр cutscene
|
||
* длится 8 тиков, после начала заклинания — 7. Длинные речевые паузы здесь
|
||
* заданы явно: в DOS они заканчиваются по окончании CBL-сэмпла. */
|
||
enum {
|
||
PV_WAIT_END = 360,
|
||
PV_GATE_END = 408,
|
||
PV_DOOR_END = 432,
|
||
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
|
||
};
|
||
|
||
enum {
|
||
INTRO_STORY_ABSENCE = 0,
|
||
INTRO_STORY_MARRY,
|
||
INTRO_STORY_CREDITS,
|
||
INTRO_STORY_HAIL,
|
||
INTRO_STORY_FRAME,
|
||
INTRO_PV_WAITING,
|
||
INTRO_PV_JAFFAR,
|
||
INTRO_PV_MAGIC,
|
||
INTRO_PV_ALONE,
|
||
INTRO_PV_STAND,
|
||
INTRO_PV_LYING,
|
||
INTRO_PV_MOUSE,
|
||
INTRO_PV_SHORT
|
||
};
|
||
|
||
enum {
|
||
INTRO_PAL_STORY = 0,
|
||
INTRO_PAL_PV = 1
|
||
};
|
||
|
||
/* 0..4 — TITLE/res41 + тексты 42/43/45/44 и чистая рамка; 5..12 —
|
||
* PV/res951 + персонажи.
|
||
* 8.3-имена намеренно короткие: DSS не получает длинные path-компоненты. */
|
||
static const char * const intro_part[13][INTRO_PARTS] = {
|
||
{ "PV\\s0_0.atl", "PV\\s0_1.atl", "PV\\s0_2.atl", "PV\\s0_3.atl", "PV\\s0_4.atl" },
|
||
{ "PV\\s1_0.atl", "PV\\s1_1.atl", "PV\\s1_2.atl", "PV\\s1_3.atl", "PV\\s1_4.atl" },
|
||
{ "PV\\s2_0.atl", "PV\\s2_1.atl", "PV\\s2_2.atl", "PV\\s2_3.atl", "PV\\s2_4.atl" },
|
||
{ "PV\\s3_0.atl", "PV\\s3_1.atl", "PV\\s3_2.atl", "PV\\s3_3.atl", "PV\\s3_4.atl" },
|
||
{ "PV\\s4_0.atl", "PV\\s4_1.atl", "PV\\s4_2.atl", "PV\\s4_3.atl", "PV\\s4_4.atl" },
|
||
{ "PV\\p0_0.atl", "PV\\p0_1.atl", "PV\\p0_2.atl", "PV\\p0_3.atl", "PV\\p0_4.atl" },
|
||
{ "PV\\p1_0.atl", "PV\\p1_1.atl", "PV\\p1_2.atl", "PV\\p1_3.atl", "PV\\p1_4.atl" },
|
||
{ "PV\\p2_0.atl", "PV\\p2_1.atl", "PV\\p2_2.atl", "PV\\p2_3.atl", "PV\\p2_4.atl" },
|
||
{ "PV\\p3_0.atl", "PV\\p3_1.atl", "PV\\p3_2.atl", "PV\\p3_3.atl", "PV\\p3_4.atl" },
|
||
{ "PV\\p4_0.atl", "PV\\p4_1.atl", "PV\\p4_2.atl", "PV\\p4_3.atl", "PV\\p4_4.atl" },
|
||
{ "PV\\p5_0.atl", "PV\\p5_1.atl", "PV\\p5_2.atl", "PV\\p5_3.atl", "PV\\p5_4.atl" },
|
||
{ "PV\\p6_0.atl", "PV\\p6_1.atl", "PV\\p6_2.atl", "PV\\p6_3.atl", "PV\\p6_4.atl" },
|
||
{ "PV\\p7_0.atl", "PV\\p7_1.atl", "PV\\p7_2.atl", "PV\\p7_3.atl", "PV\\p7_4.atl" }
|
||
};
|
||
|
||
/* Чистая Princess-room без персонажей. Shadow-копии обеих страниц содержат
|
||
* именно этот фон: каждый анимационный кадр получает его accelerator-копией,
|
||
* а полупрозрачные actor-спрайты не оставляют следа. */
|
||
static const char * const pv_base_part[INTRO_PARTS] = {
|
||
"PV\\b0_0.atl", "PV\\b0_1.atl", "PV\\b0_2.atl", "PV\\b0_3.atl", "PV\\b0_4.atl"
|
||
};
|
||
|
||
/* Две текстовые половины show_title(). Между ними PV теперь идёт отдельным
|
||
* покадровым renderer-ом, а не четырьмя полными статичными композициями. */
|
||
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_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
|
||
/* Файловый fallback сохраняет возможность пройти title на неполном HDD.
|
||
* Нормальная ветка его не вызывает: p0..p3 оставлены также для диагностики
|
||
* точек сюжета и не являются анимационным ресурсом. */
|
||
static const pop_cs_cmd_t intro_pv_fallback[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_PV },
|
||
{ POP_CS_SHOW, INTRO_PV_WAITING },
|
||
{ POP_CS_FADE_IN, 0 },
|
||
{ POP_CS_WAIT, 0x1A }, /* princess, gate and door */
|
||
{ POP_CS_SHOW, INTRO_PV_JAFFAR },
|
||
{ POP_CS_WAIT, 0x6D }, /* Jaffar comes/walks */
|
||
{ POP_CS_SHOW, INTRO_PV_MAGIC },
|
||
{ POP_CS_FLASH, 5 }, /* hourglass appears */
|
||
{ POP_CS_WAIT, 0x2F },
|
||
{ POP_CS_SHOW, INTRO_PV_ALONE },
|
||
{ POP_CS_WAIT, 0x3D }, /* Jaffar leaves, princess looks */
|
||
{ POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
|
||
static const pop_cs_cmd_t intro_after_pv[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_STORY },
|
||
{ POP_CS_SHOW, INTRO_STORY_MARRY },
|
||
{ POP_CS_FADE_IN, 0 },
|
||
{ POP_CS_WAIT, 0x78 },
|
||
{ POP_CS_SHOW, INTRO_STORY_CREDITS },
|
||
{ POP_CS_WAIT, 0x168 },
|
||
{ POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
|
||
/* Статические fallback-сценарии хранят уже физические кадры Sprinter.
|
||
* Один логический кадр cutscene SDLPoP длится 6/60 с; на 50 Гц это ровно
|
||
* 5/50 с. Поэтому суммы proc_cutscene_frame() здесь умножены на пять. */
|
||
static const pop_cs_cmd_t pre_2_6[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_STAND },
|
||
{ POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 130 }, { POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
static const pop_cs_cmd_t pre_4[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_LYING },
|
||
{ POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 130 }, { POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
static const pop_cs_cmd_t pre_8[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_MOUSE },
|
||
/* 20 + 20 + 20 логических кадров = 6,0 с. */
|
||
{ POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 300 }, { POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
static const pop_cs_cmd_t pre_9[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_MOUSE },
|
||
/* 5 + 9 + 58 логических кадров = 7,2 с. */
|
||
{ POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 360 }, { POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
static const pop_cs_cmd_t pre_12_short[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_SHORT },
|
||
/* 2 + 24 логических кадра = те же 2,6 с, что и длинная ветка. */
|
||
{ POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 130 }, { POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
/* time_expired() SDLPoP: hourglass_state=7, fade in, 2+100 кадров, fade
|
||
* out. P7 — готовая стадия комнаты с часами; звук намеренно не стартует,
|
||
* так как CBL на lifecycle screen погашен до дисковых чтений. */
|
||
static const pop_cs_cmd_t time_expired_script[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_SHORT },
|
||
{ POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 102 }, { POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
/* end_sequence_anim() — объятие/мышь — здесь сведён к отдельной PV-стадии;
|
||
* затем end_sequence() SDLPoP показывает HAIL ровно 900 тиков. */
|
||
/* Хвост финала после живой сцены встречи: только исходный story-экран. */
|
||
static const pop_cs_cmd_t ending_hail_script[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_STORY }, { POP_CS_SHOW, INTRO_STORY_HAIL },
|
||
{ POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 900 }, { POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
static const pop_cs_cmd_t ending_script[] = {
|
||
{ POP_CS_PALETTE, INTRO_PAL_PV }, { POP_CS_SHOW, INTRO_PV_MOUSE },
|
||
{ POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 54 }, { POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_PALETTE, INTRO_PAL_STORY }, { POP_CS_SHOW, INTRO_STORY_HAIL },
|
||
{ POP_CS_FADE_IN, 0 }, { POP_CS_WAIT, 900 }, { POP_CS_FADE_OUT, 0 },
|
||
{ POP_CS_END, 0 }
|
||
};
|
||
|
||
static uint8_t intro_scene;
|
||
static uint8_t intro_pending_pal;
|
||
static uint8_t intro_pal_pending;
|
||
|
||
/* Таблицы intro_part/pv_base_part расположены в этом же банке 11. Общий
|
||
* pop_screen_blit_atlas_strips находится в банке 9, и передавать ему
|
||
* указатель на W3-данные нельзя: после bcall тот же адрес уже видит другой
|
||
* физический банк. Держим короткий полосовой loader рядом с его таблицами.
|
||
* atlas_load сам временно занимает W3 только ПОСЛЕ open(path). */
|
||
static int intro_blit_atlas_strips(const char * const *parts,
|
||
uint8_t count, int x, int y)
|
||
{
|
||
uint8_t part;
|
||
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
for (part = 0; part < count; part++) {
|
||
atlas_t at;
|
||
const uint8_t *img;
|
||
|
||
if (atlas_load(&at, parts[part]) != 0) return -1;
|
||
gfx_w0_map(at.page);
|
||
img = (const uint8_t *)atlas_image(&at, 0);
|
||
gfx_blit(x, y, img);
|
||
y += img[2];
|
||
gfx_w0_unmap();
|
||
atlas_free(&at);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ---- «Пропустить сцену» — по НОВОМУ нажатию, а не по удержанию -------- *
|
||
*
|
||
* Клавиша, которой закончили уровень (движение Кида в дверь, чит Shift+L,
|
||
* skip предыдущего экрана), в момент старта сцены ЕЩЁ ЗАЖАТА. Считать это
|
||
* за skip нельзя: сцена схлопывается в один кадр, и её никто не видит.
|
||
* Ждать отпускания безусловно тоже нельзя — потерянный break-код вешает
|
||
* игру намертво (BUGS_OPEN.md#kbd-stuck-wait).
|
||
*
|
||
* Поэтому детектор ФРОНТА: пока хоть что-то зажато со старта, skip не
|
||
* взводится; как только всё отпущено, детектор «взводится» и следующее
|
||
* нажатие сцену прерывает. Залипший бит в худшем случае делает сцену
|
||
* непропускаемой — она доиграет сама, а игра не встанет. */
|
||
typedef struct { uint8_t armed; } intro_skip_t;
|
||
|
||
static void intro_skip_begin(intro_skip_t *sk)
|
||
{
|
||
kbd_raw_sync();
|
||
sk->armed = (uint8_t)!kbd_raw_any_down();
|
||
}
|
||
|
||
static uint8_t intro_skip_requested(intro_skip_t *sk)
|
||
{
|
||
kbd_raw_sync();
|
||
if (!kbd_raw_any_down()) { sk->armed = 1; return 0; }
|
||
return sk->armed;
|
||
}
|
||
|
||
static int intro_draw_page(uint8_t scene, uint8_t page)
|
||
{
|
||
if (scene >= sizeof(intro_part) / sizeof(intro_part[0])) return -1;
|
||
gfx_set_draw_page(page);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
pop_screen_fill(BLACK);
|
||
if (intro_blit_atlas_strips(intro_part[scene], INTRO_PARTS,
|
||
0, POP_YOFF) != 0)
|
||
return -1;
|
||
return 0;
|
||
}
|
||
|
||
static int intro_draw(uint8_t scene)
|
||
{
|
||
uint8_t page = pop_screen_begin();
|
||
|
||
if (intro_draw_page(scene, page) != 0) return -1;
|
||
pop_screen_present(page);
|
||
return 0;
|
||
}
|
||
|
||
/* У gfx_pal_fload/sync палитра меняется по 64 цвета за вызов BIOS. Если в
|
||
* этот момент хоть одна страница содержит следующую сцену, один VSync может
|
||
* показать её уже яркой, ещё до штатного fade-in. Поэтому перед сменой
|
||
* источника палитры уничтожаем растр на ОБЕИХ страницах, а не только гасим
|
||
* их палитры. */
|
||
static void intro_black_pages(void)
|
||
{
|
||
gfx_set_draw_page(0);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
pop_screen_fill(BLACK);
|
||
gfx_set_draw_page(1);
|
||
pop_screen_fill(BLACK);
|
||
pop_screen_present(1);
|
||
}
|
||
|
||
/* Для смены полного источника палитры сначала гасим старую сцену и
|
||
* оставляем на видимой странице настоящий чёрный растр. */
|
||
static void intro_prepare_palette(uint8_t pal)
|
||
{
|
||
intro_pending_pal = pal;
|
||
intro_pal_pending = 1;
|
||
pop_pal_apply(4);
|
||
intro_black_pages();
|
||
}
|
||
|
||
static void intro_commit_palette(void)
|
||
{
|
||
if (intro_pending_pal == INTRO_PAL_PV) pop_pal_pv_load();
|
||
else pop_pal_story_load();
|
||
pop_pal_apply(4);
|
||
intro_pal_pending = 0;
|
||
}
|
||
|
||
static int intro_draw_new_palette(uint8_t scene)
|
||
{
|
||
uint8_t page;
|
||
|
||
/* Палитра загружается, пока обе страницы содержат только чёрный растр.
|
||
* После повторного затемнения можно собрать новую сцену в скрытую
|
||
* страницу и показать её исключительно чёрной перед fade-in. */
|
||
intro_commit_palette();
|
||
page = pop_screen_begin();
|
||
if (intro_draw_page(scene, page) != 0) return -1;
|
||
pop_screen_present(page);
|
||
return 0;
|
||
}
|
||
|
||
/* В отличие от intro_draw_page() здесь base собирается в shadow ОБЕИХ
|
||
* страниц. После show page 1 palette 0 не видна и в неё безопасно загрузить
|
||
* PV\pv.pal; первый animated frame соберётся в page 0. */
|
||
static int intro_pv_draw_base(void)
|
||
{
|
||
gfx_rect_t full;
|
||
|
||
gfx_set_draw_page(0);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
pop_screen_fill(BLACK);
|
||
if (intro_blit_atlas_strips(pv_base_part, INTRO_PARTS,
|
||
0, POP_YOFF) != 0)
|
||
return -1;
|
||
gfx_set_draw_page(1);
|
||
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
|
||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||
pop_screen_present(1);
|
||
return 0;
|
||
}
|
||
|
||
static void pv_blit_floor(const atlas_t *at, uint8_t frame, int x, int floor)
|
||
{
|
||
const uint8_t *img;
|
||
|
||
gfx_w0_map(at->page);
|
||
img = (const uint8_t *)atlas_image(at, frame);
|
||
/* add_*table() оригинала задаёт нижнюю строку спрайта включительно. */
|
||
gfx_blit(x, POP_YOFF + floor - img[2] + 1, img);
|
||
gfx_w0_unmap();
|
||
}
|
||
|
||
static void pv_blit_actor(const atlas_t *at, uint8_t frame, int anchor_x,
|
||
int floor, uint8_t flip)
|
||
{
|
||
const uint8_t *img;
|
||
int x = anchor_x;
|
||
|
||
gfx_w0_map(at->page);
|
||
img = (const uint8_t *)atlas_image(at, frame);
|
||
/* draw_mid SDLPoP зеркалит правое направление относительно hot-point:
|
||
* экранный x сначала масштабирован, затем из него вычитается width. */
|
||
if (flip) x -= img[0];
|
||
gfx_blit_cols(x, POP_YOFF + floor - img[2] + 1, img, flip);
|
||
gfx_w0_unmap();
|
||
}
|
||
|
||
static void pv_draw_stars(const uint8_t *phase)
|
||
{
|
||
static const uint8_t star_x[] = { 20, 16, 23, 17, 24, 18 };
|
||
static const uint8_t star_y[] = { 97, 104, 110, 116, 120, 128 };
|
||
static const uint8_t star_color[] = { 8, 7, 15, 15, 7 };
|
||
uint8_t i;
|
||
|
||
for (i = 0; i < sizeof(star_x); i++) {
|
||
/* draw_rect SDLPoP получает полуинтервал [x,x+1)×[y,y+1).
|
||
* BGI bar() включает правую/нижнюю границу и давал звезду 2x2. */
|
||
putpixel(star_x[i], POP_YOFF + star_y[i], star_color[phase[i]]);
|
||
}
|
||
}
|
||
|
||
/* У Princess/Jaffar номер frame — это индекс frame_tbl_cuts, а картинка
|
||
* лежит в отдельном chtab. Эти таблицы — компактная нужная часть таблицы
|
||
* SDLPoP: не приходится тащить в банк весь interpreter SEQTBL. */
|
||
typedef struct {
|
||
int16_t x; /* Char.x оригинала: полу-пиксели */
|
||
uint8_t frame;
|
||
uint8_t left;
|
||
uint8_t visible;
|
||
uint8_t step;
|
||
} pv_actor_t;
|
||
|
||
/* frame_tbl_cuts[1..18].image; 10 — служебная пустая запись. */
|
||
static const uint8_t pv_princess_image[19] = {
|
||
0, 15, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 9, 10, 11, 12, 13, 14, 16
|
||
};
|
||
static const int8_t pv_princess_dx[19] = {
|
||
0, 0, 0, 0, 0, -1, 2, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||
};
|
||
static const uint8_t pv_princess_parity[19] = {
|
||
0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0
|
||
};
|
||
|
||
/* frame_tbl_cuts[48..85].dx. image равен frame - 48. */
|
||
static const int8_t pv_jaffar_dx[38] = {
|
||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
3, 3, 3, 2, 3, 5, 5, 1, 2, 2, 1, 1, 2, 3, 3, 0, 2, 2, 1
|
||
};
|
||
static const uint8_t pv_jaffar_parity[38] = {
|
||
1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0,
|
||
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0
|
||
};
|
||
static const uint8_t pv_walk_frame[6] = { 48, 49, 50, 51, 52, 53 };
|
||
static const int8_t pv_walk_dx[6] = { 1, 2, 6, 1, -1, 1 };
|
||
static const uint8_t pv_raise_frame[] = {
|
||
85, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
|
||
68, 69, 70, 71, 72, 73, 74, 75, 83, 84, 76
|
||
};
|
||
|
||
static int pv_actor_x(const pv_actor_t *actor, int frame_dx, uint8_t parity)
|
||
{
|
||
int x = actor->x;
|
||
int logical;
|
||
|
||
if (actor->left) x -= frame_dx;
|
||
else x += frame_dx;
|
||
logical = x * 2 - 116;
|
||
/* load_frame_to_obj: нечётная фаза зависит от flags кадра и direction.
|
||
* chtab Princess/Jaffar затем проходит calc_screen_x_coord(320/280). */
|
||
if (actor->left == parity) ++logical;
|
||
return logical * 8 / 7;
|
||
}
|
||
|
||
static void pv_walk_step(pv_actor_t *jaffar)
|
||
{
|
||
uint8_t step = (uint8_t)(jaffar->step % 6);
|
||
int dx = pv_walk_dx[step];
|
||
|
||
if (jaffar->left) jaffar->x -= dx;
|
||
else jaffar->x += dx;
|
||
jaffar->frame = pv_walk_frame[step];
|
||
++jaffar->step;
|
||
}
|
||
|
||
static void pv_princess_step(pv_actor_t *princess, uint16_t tick)
|
||
{
|
||
uint16_t pos;
|
||
|
||
/* Palert: 2..9, flip, dx(8), 11. Она продолжается и во время входа
|
||
* Jaffar, как последовательность Palert оригинала. */
|
||
if (tick >= PV_TURN_START && tick < PV_TURN_START + 72 &&
|
||
((tick - PV_TURN_START) & 7) == 0) {
|
||
pos = (uint16_t)((tick - PV_TURN_START) >> 3);
|
||
if (pos < 8) princess->frame = (uint8_t)(2 + pos);
|
||
else {
|
||
princess->left = 0;
|
||
princess->x += 8;
|
||
princess->frame = 11;
|
||
}
|
||
}
|
||
|
||
/* Pstepback: flip, dx(11), 12; затем 13..16 и вечный кадр 17. */
|
||
if (tick >= PV_STEPBACK_START && tick < PV_STEPBACK_START + 42 &&
|
||
((tick - PV_STEPBACK_START) % 7) == 0) {
|
||
pos = (uint16_t)((tick - PV_STEPBACK_START) / 7);
|
||
if (pos == 0) {
|
||
princess->left = 1;
|
||
princess->x -= 11;
|
||
princess->frame = 12;
|
||
} else if (pos == 1) {
|
||
princess->x -= 1;
|
||
princess->frame = 13;
|
||
} else if (pos == 2) {
|
||
princess->x -= 1;
|
||
princess->frame = 14;
|
||
} else if (pos == 3) {
|
||
princess->x -= 3;
|
||
princess->frame = 15;
|
||
} else if (pos == 4) {
|
||
princess->x -= 1;
|
||
princess->frame = 16;
|
||
} else {
|
||
princess->frame = 17;
|
||
}
|
||
}
|
||
|
||
/* Pslump: один переходный frame 1, затем frame 18 до fade-out. */
|
||
if (tick == PV_SLUMP_START) princess->frame = 1;
|
||
else if (tick == PV_SLUMP_START + 7) princess->frame = 18;
|
||
}
|
||
|
||
static void pv_jaffar_exit_step(pv_actor_t *jaffar)
|
||
{
|
||
uint8_t pos = jaffar->step++;
|
||
|
||
if (pos < 6) {
|
||
jaffar->frame = (uint8_t)(77 + pos);
|
||
} else if (pos < 12) {
|
||
if (pos == 6) --jaffar->x;
|
||
jaffar->frame = 54;
|
||
} else if (pos < 22) {
|
||
uint8_t frame = (uint8_t)(57 + pos - 12);
|
||
|
||
if (frame == 62) jaffar->x -= 2;
|
||
else if (frame == 63) ++jaffar->x;
|
||
else if (frame == 64) jaffar->x += 3;
|
||
else if (frame == 66) ++jaffar->x;
|
||
jaffar->frame = frame;
|
||
} else if (pos == 22) {
|
||
/* Vexit заканчивается flip, dx(16), dx(3), затем Vwalk2. */
|
||
jaffar->left = 0;
|
||
jaffar->x += 19;
|
||
jaffar->frame = 49;
|
||
} else {
|
||
/* Vexit прыгает на метку Vwalk2: кадр 49 уже выведен в ветке
|
||
* flip, следующий шаг обязан начинаться с dx(6), frame 50. */
|
||
uint8_t walk = (uint8_t)(pos - 23 + 2);
|
||
int dx = pv_walk_dx[walk % 6];
|
||
|
||
jaffar->x += dx;
|
||
jaffar->frame = pv_walk_frame[walk % 6];
|
||
}
|
||
}
|
||
|
||
static void pv_jaffar_step(pv_actor_t *jaffar, uint16_t tick)
|
||
{
|
||
uint16_t pos;
|
||
|
||
if (tick == PV_WALK1_START || tick == PV_WALK2_START) jaffar->step = 0;
|
||
if (((tick >= PV_WALK1_START && tick < PV_DIALOG1_START) ||
|
||
(tick >= PV_WALK2_START && tick < PV_DIALOG2_START)) &&
|
||
((tick - ((tick < PV_DIALOG1_START) ? PV_WALK1_START :
|
||
PV_WALK2_START)) & 7) == 0) {
|
||
jaffar->visible = 1;
|
||
pv_walk_step(jaffar);
|
||
}
|
||
|
||
/* Vstop: dx(1), 55, 56, Vstand. */
|
||
if (tick == PV_DIALOG1_START || tick == PV_DIALOG2_START) {
|
||
--jaffar->x;
|
||
jaffar->frame = 55;
|
||
} else if (tick == PV_DIALOG1_START + 8 || tick == PV_DIALOG2_START + 8) {
|
||
jaffar->frame = 56;
|
||
} else if ((tick >= PV_DIALOG1_START + 16 && tick < PV_WALK2_START) ||
|
||
(tick >= PV_DIALOG2_START + 16 && tick < PV_RAISE_START)) {
|
||
jaffar->frame = 54;
|
||
}
|
||
|
||
if (tick >= PV_RAISE_START && tick < PV_EXIT_START &&
|
||
((tick - PV_RAISE_START) % 7) == 0) {
|
||
pos = (uint16_t)((tick - PV_RAISE_START) / 7);
|
||
if (pos < sizeof(pv_raise_frame)) jaffar->frame = pv_raise_frame[pos];
|
||
else jaffar->frame = 76;
|
||
}
|
||
|
||
if (tick == PV_EXIT_START) jaffar->step = 0;
|
||
if (tick >= PV_EXIT_START && ((tick - PV_EXIT_START) % 7) == 0) {
|
||
pv_jaffar_exit_step(jaffar);
|
||
if (!jaffar->left && jaffar->x > 205) jaffar->visible = 0;
|
||
}
|
||
}
|
||
|
||
static void pv_blit_jaffar(const atlas_t *j0, const atlas_t *j1,
|
||
const atlas_t *j2, const pv_actor_t *jaffar)
|
||
{
|
||
uint8_t image;
|
||
int x;
|
||
|
||
if (!jaffar->visible || jaffar->frame < 48 || jaffar->frame > 85) return;
|
||
image = (uint8_t)(jaffar->frame - 48);
|
||
x = pv_actor_x(jaffar, pv_jaffar_dx[image], pv_jaffar_parity[image]);
|
||
if (image < 13)
|
||
pv_blit_actor(j0, image, x, 166, (uint8_t)!jaffar->left);
|
||
else if (image < 24)
|
||
pv_blit_actor(j1, (uint8_t)(image - 13), x, 166,
|
||
(uint8_t)!jaffar->left);
|
||
else
|
||
pv_blit_actor(j2, (uint8_t)(image - 24), x, 166,
|
||
(uint8_t)!jaffar->left);
|
||
}
|
||
|
||
static void pv_blit_princess(const atlas_t *princess, const pv_actor_t *actor)
|
||
{
|
||
uint8_t frame = actor->frame;
|
||
|
||
if (frame == 0 || frame > 18) return;
|
||
pv_blit_actor(princess, pv_princess_image[frame],
|
||
pv_actor_x(actor, pv_princess_dx[frame],
|
||
pv_princess_parity[frame]),
|
||
166, (uint8_t)!actor->left);
|
||
}
|
||
|
||
/* Внутренняя шкала сценария пока остаётся в исходных тиках 60 Гц: так
|
||
* сохраняются точки начала реплик и последовательностей. Внешне каждый
|
||
* такой логический кадр показывается четыре физических кадра Sprinter. */
|
||
static uint8_t pv_seq_period(uint16_t tick)
|
||
{
|
||
if (tick < PV_WAIT_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,
|
||
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,
|
||
const uint8_t *star_phase,
|
||
uint8_t torch_left, uint8_t torch_right,
|
||
uint8_t sand_frame, uint8_t flash,
|
||
uint16_t tick)
|
||
{
|
||
gfx_rect_t full;
|
||
uint8_t back = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||
|
||
gfx_set_draw_page(back);
|
||
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
|
||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
/* y=116 в princess_room_torch — нижняя строка, не верх спрайта. */
|
||
pv_blit_floor(torch, torch_left, 93, 116);
|
||
pv_blit_floor(torch, torch_right, 211, 116);
|
||
pv_draw_stars(star_phase);
|
||
pv_blit_princess(princess, p);
|
||
pv_blit_jaffar(j0, j1, j2, j);
|
||
if (tick >= PV_MAGIC_START) {
|
||
/* В SDLPoP hourglass_sandflow=0 означает первый кадр потока,
|
||
* а -1 — отсутствие песка. Корпус появляется ещё при молнии. */
|
||
pv_blit_floor(hourglass,
|
||
(uint8_t)(tick < PV_GLASS_DONE ? 1 : 2), 152, 168);
|
||
}
|
||
if (tick >= PV_EXIT_START) {
|
||
/* hourglass_sandflow=0 оригинал ставит лишь после окончания
|
||
* conjuring — когда Jaffar опускает руки и начинает уходить. */
|
||
pv_blit_floor(hourglass, (uint8_t)(8 + sand_frame), 160, 164);
|
||
}
|
||
/* draw_princess_room_bg() SDLPoP помещает image 2 chtab PV в
|
||
* foretable с x=30*8, ybottom=167. В нашем h0 это кадр 0 (res952):
|
||
* его надо повторить ПОСЛЕ актёров, иначе Jaffar проходит перед
|
||
* правой передней колонной. */
|
||
pv_blit_floor(hourglass, 0, 240, 167);
|
||
gfx_wait_vsync();
|
||
gfx_set_visible_page(back);
|
||
if (flash) {
|
||
uint8_t pulse;
|
||
|
||
/* Молния намеренно живёт на физической, а не cutscene-шкале:
|
||
* пять быстрых white/black импульсов, актёры на это время стоят. */
|
||
for (pulse = 0; pulse < 5; pulse++) {
|
||
pop_pal_flash_white(1);
|
||
gfx_wait_vsync();
|
||
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();
|
||
}
|
||
}
|
||
|
||
/* Сцена pv_scene() SDLPoP: ожидание Princess с факелами/звёздами, поворот,
|
||
* вход и речь Jaffar, заклинание+молния, часы с песком, уход. Тайминг
|
||
* воспроизводится на кадровой шкале; пропуск проверяется каждый кадр. */
|
||
static int intro_pv_animated(void)
|
||
{
|
||
atlas_t princess, jaffar0, jaffar1, jaffar2, hourglass, torch;
|
||
pv_actor_t princess_actor;
|
||
pv_actor_t jaffar_actor;
|
||
uint16_t tick;
|
||
uint16_t frame_end;
|
||
uint16_t render_tick;
|
||
uint8_t anim_step = 0;
|
||
uint8_t torch_left = 1;
|
||
uint8_t torch_right = 6;
|
||
uint8_t star_phase[6] = { 1, 1, 1, 1, 1, 1 };
|
||
uint8_t sand_frame = 0;
|
||
uint8_t flash_done = 0;
|
||
uint8_t flash;
|
||
intro_skip_t skip;
|
||
int result = -1;
|
||
|
||
intro_prepare_palette(INTRO_PAL_PV);
|
||
/* commit обязан быть до сборки base: gfx_pal_sync обновляет и видимую
|
||
* страницу, поэтому там в этот момент должен оставаться только чёрный
|
||
* растр из intro_prepare_palette(). */
|
||
intro_commit_palette();
|
||
if (intro_pv_draw_base() != 0) return -1;
|
||
|
||
/* b0_* собирается полосами: на время очередной полосы ей тоже нужна
|
||
* временная EMM-страница. Поэтому фон готовится до постоянных
|
||
* actor-атласов: порядок сохраняет независимость загрузчика полос от
|
||
* числа одновременно живущих анимационных ресурсов. */
|
||
if (atlas_load(&princess, "PV\\a0.atl") != 0) return -1;
|
||
if (atlas_load(&jaffar0, "PV\\j0.atl") != 0) {
|
||
goto free_princess;
|
||
}
|
||
if (atlas_load(&jaffar1, "PV\\j1.atl") != 0) {
|
||
goto free_jaffar0;
|
||
}
|
||
if (atlas_load(&jaffar2, "PV\\j2.atl") != 0) {
|
||
goto free_jaffar1;
|
||
}
|
||
if (atlas_load(&hourglass, "PV\\h0.atl") != 0) {
|
||
goto free_jaffar2;
|
||
}
|
||
if (atlas_load(&torch, "PV\\t0.atl") != 0) {
|
||
goto free_hourglass;
|
||
}
|
||
|
||
pop_pal_fade_in(INTRO_FADE);
|
||
/* Все атласы сцены уже в EMM: дальше файлового ввода-вывода нет, и
|
||
* насос CBL можно открывать (иначе ESTEX не даёт долить блок — см.
|
||
* контракт pop_sfx_start). Полноэкранная accel-копия кадра насосу не
|
||
* мешает: leaf режет DI бандами по 16 строк. */
|
||
(void)pop_sfx_start();
|
||
result = 0;
|
||
princess_actor.x = 120;
|
||
princess_actor.frame = 11; /* Pstand, facing left */
|
||
princess_actor.left = 1;
|
||
princess_actor.visible = 1;
|
||
princess_actor.step = 0;
|
||
jaffar_actor.x = 198;
|
||
jaffar_actor.frame = 54; /* Vstand, behind the closed door */
|
||
jaffar_actor.left = 1;
|
||
jaffar_actor.visible = 0;
|
||
jaffar_actor.step = 0;
|
||
tick = 0;
|
||
intro_skip_begin(&skip);
|
||
while (tick < PV_ANIM_TICKS) {
|
||
if (intro_skip_requested(&skip)) {
|
||
result = 1;
|
||
break;
|
||
}
|
||
|
||
/* Один проход = один логический кадр cutscene. Внутри проходим все
|
||
* его исходные 6/8/7 тиков, снаружи draw_frame держит результат
|
||
* четыре физических кадра — единые номинальные 12,5 FPS. */
|
||
flash = 0;
|
||
frame_end = (uint16_t)(tick + pv_seq_period(tick));
|
||
if (frame_end > PV_ANIM_TICKS) frame_end = PV_ANIM_TICKS;
|
||
do {
|
||
pv_princess_step(&princess_actor, tick);
|
||
pv_jaffar_step(&jaffar_actor, tick);
|
||
if (tick == PV_MAGIC_START && !flash_done) {
|
||
flash = 1;
|
||
flash_done = 1;
|
||
}
|
||
/* Границы шкалы уже стоят там, где оригинал ЖДЁТ окончания
|
||
* очередного сэмпла, — заявляем эффект ровно на них. */
|
||
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);
|
||
++tick;
|
||
} while (tick < frame_end);
|
||
|
||
{
|
||
uint8_t star = (uint8_t)(anim_step % 6);
|
||
|
||
torch_left = (uint8_t)((torch_left + 1) % 9);
|
||
torch_right = (uint8_t)((torch_right + 1) % 9);
|
||
star_phase[star] = (uint8_t)((star_phase[star] + 1) % 5);
|
||
if (tick > PV_EXIT_START)
|
||
sand_frame = (uint8_t)((sand_frame + 1) % 3);
|
||
++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);
|
||
pop_sfx_tick(); /* как в оригинале — в конце отрисовки кадра */
|
||
}
|
||
pop_sfx_pause(); /* дальше снова только загрузка файлов */
|
||
if (!result) pop_pal_fade_out(INTRO_FADE);
|
||
|
||
free_torch:
|
||
atlas_free(&torch);
|
||
free_hourglass:
|
||
atlas_free(&hourglass);
|
||
free_jaffar2:
|
||
atlas_free(&jaffar2);
|
||
free_jaffar1:
|
||
atlas_free(&jaffar1);
|
||
free_jaffar0:
|
||
atlas_free(&jaffar0);
|
||
free_princess:
|
||
atlas_free(&princess);
|
||
return result;
|
||
}
|
||
|
||
/* hourglass_frame() SDLPoP: четыре границы оставшегося времени выбирают
|
||
* один из кадров 6..2. В h0.atl номер кадра совпадает с hourglass_state. */
|
||
static uint8_t pre_hourglass_state(void)
|
||
{
|
||
static const uint8_t bound[] = { 6, 17, 33, 65 };
|
||
uint8_t i;
|
||
|
||
for (i = 0; i < sizeof(bound); i++)
|
||
if (bound[i] > pop_timer_minutes) break;
|
||
return (uint8_t)(6 - i);
|
||
}
|
||
|
||
/* cutscene_2_6()/cutscene_4(): 26 логических кадров по 100 мс. У SDLPoP
|
||
* это 6/60 секунды, у Sprinter — 5/50, то есть 130 физических кадров.
|
||
* Статична только поза Princess; draw_princess_room_bg оригинала
|
||
* каждый логический кадр заново обновляет факелы, звёзды, корпус часов и
|
||
* одну из трёх фаз сыплющегося песка. */
|
||
static int pre_princess_animated(pop_pre_cutscene_t scene)
|
||
{
|
||
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;
|
||
int result = -1;
|
||
|
||
intro_prepare_palette(INTRO_PAL_PV);
|
||
intro_commit_palette();
|
||
/* Обе shadow-страницы содержат чистую статическую сцену. Динамика ниже
|
||
* рисуется GFX_BANK_SPRITE и потому не оседает в этом источнике. */
|
||
if (intro_draw_page(picture, 0) != 0 ||
|
||
intro_draw_page(picture, 1) != 0) return -1;
|
||
if (atlas_load(&hourglass, "PV\\h0.atl") != 0) return -1;
|
||
if (atlas_load(&torch, "PV\\t0.atl") != 0) goto free_hourglass_pre;
|
||
|
||
pop_screen_present(1);
|
||
pop_pal_fade_in(INTRO_FADE);
|
||
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++) {
|
||
uint8_t back, hold, star;
|
||
|
||
if (intro_skip_requested(&skip)) { result = 1; break; }
|
||
back = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||
gfx_set_draw_page(back);
|
||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
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);
|
||
pv_blit_floor(&hourglass, 0, 240, 167); /* передняя колонна */
|
||
gfx_wait_vsync();
|
||
gfx_set_visible_page(back);
|
||
/* Первый тик уже потрачен на commit: ещё четыре = 5/50 с. */
|
||
for (hold = 1; hold < 5; hold++) gfx_wait_vsync();
|
||
|
||
torch_left = (uint8_t)((torch_left + 1) % 9);
|
||
torch_right = (uint8_t)((torch_right + 1) % 9);
|
||
sand = (uint8_t)((sand + 1) % 3);
|
||
star = (uint8_t)(frame % 6);
|
||
star_phase[star] = (uint8_t)((star_phase[star] + 1) % 5);
|
||
}
|
||
if (!result) pop_pal_fade_out(INTRO_FADE);
|
||
|
||
atlas_free(&torch);
|
||
free_hourglass_pre:
|
||
atlas_free(&hourglass);
|
||
return result;
|
||
}
|
||
|
||
/* SDLPoP transition_ltr(): подготовленная story-страница копируется на
|
||
* видимый title узкими вертикальными полосами слева направо. У оригинала
|
||
* 2px/120 FPS; здесь 4px/60 FPS даёт ту же длительность (~1.3 с), не
|
||
* удваивая число полных копий 320x256 в ограниченном bank 9. */
|
||
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;
|
||
for (x = 0; x < GFX_WIDTH; x += 4) {
|
||
if (kbd_raw_any_down()) {
|
||
stripe.x = 0; stripe.w = GFX_WIDTH;
|
||
gfx_copy_page(&stripe, GFX_COPY_DIRECT);
|
||
break;
|
||
}
|
||
stripe.x = x;
|
||
stripe.w = (uint16_t)((GFX_WIDTH - x < 4) ? GFX_WIDTH - x : 4);
|
||
gfx_copy_page(&stripe, GFX_COPY_DIRECT);
|
||
gfx_wait_vsync();
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void intro_flash(uint16_t ticks)
|
||
{
|
||
uint8_t page;
|
||
|
||
page = pop_screen_begin();
|
||
pop_screen_fill(WHITE);
|
||
pop_screen_present(page);
|
||
while (ticks--) gfx_wait_vsync();
|
||
/* Вспышка ничего не меняет в сценарии: вернуть ту же собранную стадию. */
|
||
intro_draw(intro_scene);
|
||
}
|
||
|
||
static void intro_restore_game_palette(void)
|
||
{
|
||
/* Обе страницы — именно чёрный растр до gfx_pal_fload/sync. Иначе
|
||
* синхронизация palette 1 способна на один кадр высветить credits перед
|
||
* переходом в demo. */
|
||
intro_black_pages();
|
||
pop_pal_game_load();
|
||
pop_pal_apply(4);
|
||
}
|
||
|
||
/* ================= ПЕРСОНАЖИ КАТСЦЕН ================================= *
|
||
*
|
||
* Порт cutscene_8 / cutscene_9 (seg001:034D/03B7) и end_sequence_anim
|
||
* (seg001:041C). Все три идут через load_intro(1, …): фон — комната
|
||
* принцессы (та же b0_*, что у intro), а поверх играют ДВА персонажа.
|
||
*
|
||
* Движок общий с игрой, и это главное: последовательности крутит наш
|
||
* play_seq (pop_kid_play/pop_guard_play), кадры принцессы берутся из
|
||
* таблицы КАТСЦЕН (kid_data.bin, KID_BIN_CFRAMES_OFF), мышь и Кид — из
|
||
* таблицы Кида, как в оригинале (frame_table_kid общая для kid и mouse).
|
||
* Отличается только ОТРИСОВКА: спрайты принцессы лежат в PV-атласах, а
|
||
* Кид/мышь — в игровых kid-атласах, поэтому в pv.pal под них отведена
|
||
* своя палитровая строка 0x70.
|
||
*
|
||
* Слоты — как в оригинале: Kid-окно занимает мышь (или Кид в финале),
|
||
* Guard-окно — принцесса.
|
||
*/
|
||
|
||
/* Набор PV2 (res901..930) не влезает в одну EMM: image 0..16 в a1,
|
||
* 17..29 в a2. Номер кадра при этом остаётся исходным image-id. */
|
||
#define CUT_PV2_SPLIT 17
|
||
/* Один логический кадр катсцены = 5 vsync, как у остальных PV-сцен
|
||
* (cutscene_frame_time SDLPoP; см. pre_princess_animated). */
|
||
#define CUT_FRAME_VSYNC 5
|
||
|
||
static atlas_t cut_pv1, cut_pv2a, cut_pv2b, cut_hg, cut_torch;
|
||
static uint8_t cut_atlases; /* сколько успели загрузить — столько и вернём */
|
||
/* Детектор фронта живёт на ВСЮ сцену: она склеена из нескольких cut_run, и
|
||
* взводить его заново на каждом отрезке значило бы ловить одно удержание
|
||
* несколько раз. */
|
||
static intro_skip_t cut_skip;
|
||
/* Фазы «живого» фона: пламя двух факелов, песок и мерцание звёзд. Счётчики
|
||
* общие на всю сцену — она склеена из нескольких cut_run. */
|
||
static uint8_t cut_torch_l, cut_torch_r, cut_sand, cut_star_phase[6];
|
||
static uint8_t cut_hg_state;
|
||
|
||
static void cut_free(void)
|
||
{
|
||
if (cut_atlases > 4) atlas_free(&cut_torch);
|
||
if (cut_atlases > 3) atlas_free(&cut_hg);
|
||
if (cut_atlases > 2) atlas_free(&cut_pv2b);
|
||
if (cut_atlases > 1) atlas_free(&cut_pv2a);
|
||
if (cut_atlases > 0) atlas_free(&cut_pv1);
|
||
cut_atlases = 0;
|
||
}
|
||
|
||
static int cut_load(void)
|
||
{
|
||
cut_atlases = 0;
|
||
if (atlas_load(&cut_pv1, "PV\\a0.atl") != 0) return -1;
|
||
cut_atlases = 1;
|
||
if (atlas_load(&cut_pv2a, "PV\\a1.atl") != 0) { cut_free(); return -1; }
|
||
cut_atlases = 2;
|
||
if (atlas_load(&cut_pv2b, "PV\\a2.atl") != 0) { cut_free(); return -1; }
|
||
cut_atlases = 3;
|
||
/* Комната принцессы живёт и в катсценах: пламя факелов, звёзды в окне,
|
||
* песок в часах — и ПЕРЕДНЯЯ КОЛОННА, которая по оригиналу закрывает
|
||
* персонажей (draw_princess_room_bg + foretable). */
|
||
if (atlas_load(&cut_hg, "PV\\h0.atl") != 0) { cut_free(); return -1; }
|
||
cut_atlases = 4;
|
||
if (atlas_load(&cut_torch, "PV\\t0.atl") != 0) { cut_free(); return -1; }
|
||
cut_atlases = 5;
|
||
return 0;
|
||
}
|
||
|
||
/* Экранная X кадра: та же математика, что у актёров intro — logical
|
||
* координата (x*2 − 116) с нечётной фазой по flags кадра, затем
|
||
* calc_screen_x_coord (×8/7). */
|
||
static int cut_screen_x(const pop_char_t *ch, const kframe *fr)
|
||
{
|
||
int x = ch->x;
|
||
int logical;
|
||
uint8_t left = (uint8_t)(ch->direction < 0);
|
||
|
||
if (left) x -= fr->dx;
|
||
else x += fr->dx;
|
||
logical = x * 2 - 116;
|
||
if (left == (uint8_t)((fr->flags & 0x80) != 0)) ++logical;
|
||
return logical * 8 / 7;
|
||
}
|
||
|
||
/* Принцесса: набор выбирает поле sword кадра (>>6: 1 = PV1 res800,
|
||
* 2 = PV2 res900) — ровно как chtab_base + (cur_frame.sword >> 6)
|
||
* в seg008:1759. */
|
||
static void cut_blit_princess(const pop_char_t *ch, const kframe *fr)
|
||
{
|
||
const atlas_t *at;
|
||
uint8_t idx;
|
||
|
||
if (fr->image == 255 || cut_atlases < 3) return;
|
||
if ((uint8_t)(fr->sword >> 6) >= 2) {
|
||
if (fr->image < CUT_PV2_SPLIT) { at = &cut_pv2a; idx = fr->image; }
|
||
else { at = &cut_pv2b; idx = (uint8_t)(fr->image - CUT_PV2_SPLIT); }
|
||
} else {
|
||
at = &cut_pv1; idx = fr->image;
|
||
}
|
||
pv_blit_actor(at, idx, cut_screen_x(ch, fr),
|
||
(int)ch->y + fr->dy, (uint8_t)(ch->direction >= 0));
|
||
}
|
||
|
||
/* Кид и мышь — спрайты chtab_2 из игровых атласов (kidp[]). Атласы Кида
|
||
* хранятся column-major (ради бесплатного флипа), поэтому блит колоночный. */
|
||
static void cut_blit_kid(const pop_char_t *ch, const kframe *fr)
|
||
{
|
||
const uint8_t *img;
|
||
uint8_t pg, idx, flip;
|
||
int x;
|
||
|
||
if (fr->image == 255) return;
|
||
pg = (uint8_t)(fr->image >> 3);
|
||
idx = (uint8_t)(fr->image & 7);
|
||
if (pg >= kid_npages) return;
|
||
flip = (uint8_t)(ch->direction >= 0);
|
||
x = cut_screen_x(ch, fr);
|
||
gfx_w0_map(kidp[pg].page);
|
||
img = (const uint8_t *)atlas_image(&kidp[pg], idx);
|
||
if (flip) x -= img[0];
|
||
gfx_blit_cols(x, POP_YOFF + (int)ch->y + fr->dy - img[2] + 1, img, flip);
|
||
gfx_w0_unmap();
|
||
}
|
||
|
||
/* Сменить последовательность персонажа в его окне (seqtbl_offset_kid_char /
|
||
* seqtbl_offset_shad_char оригинала). */
|
||
static void cut_kid_seq(uint8_t seq)
|
||
{
|
||
pop_loadkid(); pop_char_set_seq(seq); pop_savekid();
|
||
}
|
||
|
||
static void cut_shad_seq(uint8_t seq)
|
||
{
|
||
pop_loadshad(); pop_char_set_seq(seq); pop_saveshad();
|
||
}
|
||
|
||
/* Поставить персонажа так, как это делают init_* оригинала: позиция,
|
||
* направление и стартовая последовательность, затем первый play_seq. */
|
||
static void cut_kid_init(uint8_t charid, uint8_t x, uint8_t y, uint8_t seq)
|
||
{
|
||
Kid.charid = charid;
|
||
Kid.x = x;
|
||
Kid.y = y;
|
||
Kid.direction = -1; /* dir_FF_left */
|
||
Kid.alive = -1;
|
||
Kid.frame = 0;
|
||
Kid.fall_x = Kid.fall_y = 0;
|
||
Kid.repeat = 0;
|
||
cut_kid_seq(seq);
|
||
pop_kid_play();
|
||
}
|
||
|
||
static void cut_shad_init(uint8_t x, uint8_t y, uint8_t seq)
|
||
{
|
||
Guard.charid = CHARID_5_PRINCESS;
|
||
Guard.x = x;
|
||
Guard.y = y;
|
||
Guard.direction = -1;
|
||
Guard.alive = -1;
|
||
Guard.frame = 0;
|
||
Guard.fall_x = Guard.fall_y = 0;
|
||
Guard.repeat = 0;
|
||
cut_shad_seq(seq);
|
||
pop_guard_play();
|
||
}
|
||
|
||
/* proc_cutscene_frame(n): прокрутить n логических кадров, каждый — шаг
|
||
* последовательности обоих персонажей плюс отрисовка. Возврат 1 — игрок
|
||
* прервал сцену (любое НОВОЕ нажатие). */
|
||
static uint8_t cut_run(uint16_t frames, uint8_t kid_visible)
|
||
{
|
||
gfx_rect_t full;
|
||
uint16_t i;
|
||
uint8_t hold;
|
||
|
||
full.x = 0; full.y = 0; full.w = GFX_WIDTH; full.h = GFX_HEIGHT;
|
||
for (i = 0; i < frames; i++) {
|
||
uint8_t back;
|
||
kframe kid_fr, pr_fr;
|
||
|
||
if (intro_skip_requested(&cut_skip)) return 1;
|
||
|
||
/* Кадр забираем СРАЗУ после play каждого слота. load_frame кладёт
|
||
* его в кэш ПО charid активного персонажа: у Кида это kid_frame, у
|
||
* всех прочих — общий pop_gframe. В Kid-окне здесь сидит МЫШЬ
|
||
* (charid 24), то есть её кадр уходит в pop_gframe и затирается
|
||
* следом кадром принцессы, а kid_frame держит позу игрового Кида.
|
||
* Читать кэши в конце кадра значило рисовать мышь спрайтом Кида и
|
||
* терять принцессу — ровно это и было видно в сценах 8/9. */
|
||
pop_kid_play(); /* мышь либо Кид */
|
||
kid_fr = (Kid.charid == CHARID_0_KID) ? kid_frame : pop_gframe;
|
||
pop_guard_play(); /* принцесса */
|
||
pr_fr = pop_gframe;
|
||
|
||
back = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||
gfx_set_draw_page(back);
|
||
/* Чистый фон обеих страниц лежит в shadow-копии; персонажи рисуются
|
||
* банком спрайтов и потому в неё не оседают. */
|
||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||
gfx_set_bank(GFX_BANK_SPRITE);
|
||
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);
|
||
cut_blit_princess(&Guard, &pr_fr);
|
||
if (kid_visible) cut_blit_kid(&Kid, &kid_fr);
|
||
/* ПОСЛЕ персонажей: передняя колонна — передний слой комнаты, она
|
||
* обязана закрывать и принцессу, и мышь (иначе мышь «просвечивает»
|
||
* сквозь неё — поймано пользователем 2026-08-25). */
|
||
pv_blit_floor(&cut_hg, 0, 240, 167);
|
||
gfx_set_bank(GFX_BANK_NORMAL);
|
||
gfx_wait_vsync();
|
||
gfx_set_visible_page(back);
|
||
for (hold = 1; hold < CUT_FRAME_VSYNC; hold++) gfx_wait_vsync();
|
||
|
||
cut_torch_l = (uint8_t)((cut_torch_l + 1) % 9);
|
||
cut_torch_r = (uint8_t)((cut_torch_r + 1) % 9);
|
||
cut_sand = (uint8_t)((cut_sand + 1) % 3);
|
||
{
|
||
uint8_t star = (uint8_t)(i % 6);
|
||
cut_star_phase[star] = (uint8_t)((cut_star_phase[star] + 1) % 5);
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Общая обвязка всех трёх сцен: чистый фон комнаты принцессы в обе
|
||
* страницы, PV-палитра, актёрские атласы. Возврат -1 — ресурсов нет
|
||
* (неполный HDD), и caller откатывается на статический сценарий. */
|
||
static int cut_begin(void)
|
||
{
|
||
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();
|
||
intro_prepare_palette(INTRO_PAL_PV);
|
||
intro_commit_palette();
|
||
if (intro_pv_draw_base() != 0) return -1;
|
||
if (cut_load() != 0) return -1;
|
||
return 0;
|
||
}
|
||
|
||
static void cut_end(uint8_t aborted)
|
||
{
|
||
if (!aborted) pop_pal_fade_out(INTRO_FADE);
|
||
cut_free();
|
||
}
|
||
|
||
/* cutscene_8 (seg001:034D): мышь сидит рядом с принцессой, встаёт и
|
||
* убегает; принцесса поднимается ей вслед. */
|
||
static int cut_scene_8(void)
|
||
{
|
||
uint8_t aborted;
|
||
|
||
if (cut_begin() != 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): сидит и гладит мышь. */
|
||
cut_shad_init(131, 169, 110);
|
||
pop_pal_fade_in(INTRO_FADE);
|
||
aborted = cut_run(20, 1);
|
||
if (!aborted) {
|
||
cut_kid_seq(107); /* мышь встаёт и уходит */
|
||
aborted = cut_run(20, 1);
|
||
}
|
||
if (!aborted) {
|
||
cut_shad_seq(111); /* принцесса встаёт */
|
||
aborted = cut_run(20, 1);
|
||
}
|
||
cut_end(aborted);
|
||
return 0;
|
||
}
|
||
|
||
/* cutscene_9 (seg001:03B7): принцесса стоит, мышь прибегает, принцесса
|
||
* приседает к ней. */
|
||
static int cut_scene_9(void)
|
||
{
|
||
uint8_t aborted;
|
||
|
||
if (cut_begin() != 0) return -1;
|
||
/* princess_stand (seg001:026A) — поза PV1, лицом вправо. */
|
||
cut_shad_init(144, 169, 94);
|
||
Guard.direction = 0;
|
||
pop_pal_fade_in(INTRO_FADE);
|
||
/* init_mouse_go (seg001:022A): мышь вбегает справа. */
|
||
cut_kid_init(CHARID_24_MOUSE, 199, 167, 105);
|
||
aborted = cut_run(5, 1);
|
||
if (!aborted) {
|
||
cut_shad_seq(112); /* принцесса приседает */
|
||
aborted = cut_run(9, 1);
|
||
}
|
||
if (!aborted) {
|
||
cut_kid_seq(114); /* мышь останавливается */
|
||
aborted = cut_run(58, 1);
|
||
}
|
||
cut_end(aborted);
|
||
return 0;
|
||
}
|
||
|
||
/* end_sequence_anim (seg001:041C): Кид вбегает к принцессе, она
|
||
* поворачивается и обнимает его; следом появляется мышь. */
|
||
static int cut_ending(void)
|
||
{
|
||
uint8_t aborted;
|
||
|
||
if (cut_begin() != 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);
|
||
pop_pal_fade_in(INTRO_FADE);
|
||
pop_sfx_play(26); /* «arrived to princess» */
|
||
aborted = cut_run(8, 1);
|
||
if (!aborted) {
|
||
cut_shad_seq(108); /* поворот и объятие */
|
||
aborted = cut_run(5, 1);
|
||
}
|
||
if (!aborted) {
|
||
cut_kid_seq(13); /* Кид останавливается */
|
||
aborted = cut_run(2, 1);
|
||
}
|
||
/* Kid.frame = 0 (seg001:0348): дальше Кида не видно — он в объятиях,
|
||
* и рисуется только принцесса. */
|
||
if (!aborted) aborted = cut_run(39, 0);
|
||
if (!aborted) {
|
||
/* init_mouse_1 (seg001:02D5): мышь приходит к паре. */
|
||
cut_kid_init(CHARID_24_MOUSE, 197, 164, 105);
|
||
aborted = cut_run(9, 1);
|
||
}
|
||
if (!aborted) {
|
||
cut_kid_seq(101); /* мышь встаёт на задние лапы */
|
||
aborted = cut_run(41, 1);
|
||
}
|
||
cut_end(aborted);
|
||
return 0;
|
||
}
|
||
|
||
static uint8_t intro_run(const pop_cs_cmd_t *script)
|
||
{
|
||
pop_cutscene_t cs;
|
||
intro_skip_t skip;
|
||
uint16_t arg;
|
||
uint8_t aborted = 0;
|
||
|
||
pop_cutscene_begin(&cs, script);
|
||
intro_skip_begin(&skip);
|
||
for (;;) {
|
||
pop_cs_event_t ev;
|
||
|
||
if (intro_skip_requested(&skip)) {
|
||
pop_cutscene_abort(&cs);
|
||
aborted = 1;
|
||
break;
|
||
}
|
||
ev = pop_cutscene_tick(&cs, &arg);
|
||
if (ev == POP_CS_EVENT_END) break;
|
||
if (ev == POP_CS_EVENT_PALETTE) intro_prepare_palette((uint8_t)arg);
|
||
else if (ev == POP_CS_EVENT_SHOW) {
|
||
intro_scene = (uint8_t)arg;
|
||
if ((intro_pal_pending ? intro_draw_new_palette(intro_scene) :
|
||
intro_draw(intro_scene)) != 0) {
|
||
pop_cutscene_abort(&cs);
|
||
aborted = 1;
|
||
break;
|
||
}
|
||
} else if (ev == POP_CS_EVENT_FADE_IN) pop_pal_fade_in(INTRO_FADE);
|
||
else if (ev == POP_CS_EVENT_FADE_OUT) pop_pal_fade_out(INTRO_FADE);
|
||
else if (ev == POP_CS_EVENT_FLASH) intro_flash(arg);
|
||
gfx_wait_vsync();
|
||
}
|
||
|
||
return aborted;
|
||
}
|
||
|
||
uint8_t pop_intro_show(void) __banked
|
||
{
|
||
int pv_result;
|
||
|
||
/* Title уже заглушил CBL; оставляем это явным lifecycle-правилом, если
|
||
* intro позднее будет вызвано отдельно (например, из debug-маршрута). */
|
||
pop_sfx_pause();
|
||
if (intro_story_transition() != 0) {
|
||
/* Ошибка файла не должна оставить старый title без выхода: менее
|
||
* эффектный, но корректный fallback остаётся обычным fade-in. */
|
||
intro_prepare_palette(INTRO_PAL_STORY);
|
||
if (intro_draw_new_palette(INTRO_STORY_ABSENCE) == 0)
|
||
pop_pal_fade_in(INTRO_FADE);
|
||
}
|
||
if (intro_run(intro_before_pv)) {
|
||
intro_restore_game_palette();
|
||
return 1;
|
||
}
|
||
pv_result = intro_pv_animated();
|
||
if (pv_result > 0) {
|
||
intro_restore_game_palette();
|
||
return 1;
|
||
}
|
||
if (pv_result < 0 && intro_run(intro_pv_fallback)) {
|
||
intro_restore_game_palette();
|
||
return 1;
|
||
}
|
||
if (intro_run(intro_after_pv)) {
|
||
intro_restore_game_palette();
|
||
return 1;
|
||
}
|
||
/* Abort возможен в любой WAIT и не оставляет загруженных атласов: каждая
|
||
* полоса освобождена внутри intro_draw. */
|
||
intro_restore_game_palette();
|
||
return 0;
|
||
}
|
||
|
||
void pop_pre_cutscene_show(pop_pre_cutscene_t scene) __banked
|
||
{
|
||
const pop_cs_cmd_t *script = 0;
|
||
|
||
pop_sfx_pause();
|
||
if (scene == POP_PRE_2_6 || scene == POP_PRE_4 || scene == POP_PRE_12) {
|
||
if (pre_princess_animated(scene) >= 0) {
|
||
intro_restore_game_palette();
|
||
return;
|
||
}
|
||
/* Неполный HDD: статический fallback сохраняет корректные 130 тиков. */
|
||
}
|
||
/* Сцены 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) {
|
||
intro_restore_game_palette();
|
||
return;
|
||
}
|
||
}
|
||
if (scene == POP_PRE_2_6 || scene == POP_PRE_12) script = pre_2_6;
|
||
else if (scene == POP_PRE_4) script = pre_4;
|
||
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) return;
|
||
|
||
intro_run(script);
|
||
intro_restore_game_palette();
|
||
}
|
||
|
||
void pop_time_expired_show(void) __banked
|
||
{
|
||
pop_sfx_pause();
|
||
intro_run(time_expired_script);
|
||
intro_restore_game_palette();
|
||
}
|
||
|
||
void pop_ending_show(void) __banked
|
||
{
|
||
pop_sfx_pause();
|
||
/* Экран гасим ПЕРВЫМ действием — как load_intro оригинала, который
|
||
* начинается с draw_rect(&screen_rect, color_0_black). Иначе загрузка
|
||
* ресурсов сцены идёт поверх ЖИВОЙ комнаты уровня 14, и она «мелькает»
|
||
* перед встречей. */
|
||
pop_pal_apply(4);
|
||
/* Встреча с принцессой — живая сцена (end_sequence_anim). Дальше по
|
||
* скрипту идёт только текст HAIL, поэтому статический сценарий ниже
|
||
* начинается с него; при нехватке ресурсов отыгрывается целиком. */
|
||
if (cut_ending() == 0) intro_run(ending_hail_script);
|
||
else intro_run(ending_script);
|
||
intro_restore_game_palette();
|
||
}
|