Меню, статус-строка и оболочка игры: title/intro/cutscene/HoF, POP.CFG, палитры
Эта сессия (меню + текст в служебных полосах):
* Меню: рамка выделения считается от силуэта текста (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>
This commit is contained in:
@@ -28,6 +28,13 @@
|
||||
#include "pop_vflip.h" /* сброс зеркальных страниц на смене уровня */
|
||||
#include "pop_cheat.h" /* pop_cheats — режим разработки включает pop_boot */
|
||||
#include "pop_pace.h" /* pop_speed_mode — режим скорости в отладочной метке */
|
||||
#include "pop_settings.h" /* pop_settings_defaults — стартовый профиль VANILLA */
|
||||
#include "pop_cfg.h" /* pop_cfg_load — POP.CFG поверх дефолтов */
|
||||
#include "pop_status.h" /* статус-строка и отладочная полоса — банк 9 */
|
||||
#include "pop_menu.h" /* pop_menu_init/free — атлас UI в bank 9 */
|
||||
#include "pop_ui.h" /* ранняя чёрная палитра + snapshot стартового экрана */
|
||||
#include "pop_pal.h" /* единый lifecycle game/title/level палитр */
|
||||
#include "pop_timer.h" /* global 60-minute timer — core in bank 9 */
|
||||
#include <stdio.h> /* puts/printf — сообщения об ошибках загрузки */
|
||||
#include <kbd_raw.h>
|
||||
|
||||
@@ -41,6 +48,11 @@
|
||||
#define TILE_EMPTY 0
|
||||
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
|
||||
|
||||
/* Во время смены уровня enter_room должен только СОБРАТЬ новую страницу.
|
||||
* Физический flip делает pop_level_switch после окончательной палитры. */
|
||||
static uint8_t level_prepare;
|
||||
static uint8_t level_prepared_page;
|
||||
|
||||
/* find_start_level_door (seg003:02E6): дверь уровня В СТАРТОВОЙ КОМНАТЕ —
|
||||
* это дверь, через которую Кид ВОШЁЛ, поэтому она открыта полностью
|
||||
* (modif = 43) и заводится trob'ом типа 3 (start_level_door, seg007:0D72).
|
||||
@@ -211,9 +223,21 @@ void pop_start_level(void) __banked
|
||||
}
|
||||
}
|
||||
Kid.sword = 0; /* set_start_pos: sword_0_sheathed */
|
||||
/* Остальные одноразовые флаги того же set_start_pos (seg003:028A). Они
|
||||
* НЕ косметика: droppedout переживал границу уровня и на следующем
|
||||
* уровне заставлял стража идти «за упавшим Кидом» вниз
|
||||
* (guard_follows_kid_down) — на 4-м уровне в комнате 22 страж по этому
|
||||
* флагу шёл прямо в челюсти, хотя Кида он даже не видел. Взводится он
|
||||
* на ПРОШЛОМ уровне: Кид сорвался с уступа в боевой позе (кадры
|
||||
* 150..179, pop_map.c start_fall) — обычная концовка боя со скелетом на
|
||||
* 3-м. Поймано пользователем 2026-08-24 (переход 3 -> 4 ногами, через
|
||||
* дверь уровня; читом Shift+L старт чистый, и баг не воспроизводился). */
|
||||
pop_droppedout = 0;
|
||||
offguard = 0;
|
||||
knock = 0;
|
||||
/* play_level (seg003:106): have_sword = level >= have_sword_from_level
|
||||
* (=2) — начиная со второго уровня меч у Кида уже есть. */
|
||||
pop_have_sword = (uint8_t)(pop_current_level >= 2);
|
||||
* (=2); исключение — demo level 0, где меч уже выдан. */
|
||||
pop_have_sword = (uint8_t)(pop_current_level == 0 || pop_current_level >= 2);
|
||||
/* seamless (seg003:158): пришли БЕЗ двери уровня, прямо на бегу — HP не
|
||||
* трогаем, оно переезжает как есть. Флаг одноразовый: у оригинала его
|
||||
* гасит заставка «LEVEL N», которую этот же переход и пропускает. */
|
||||
@@ -320,7 +344,8 @@ uint8_t pop_dbg_roomnav(uint8_t dir) __banked
|
||||
* Смысл прежний: одно нажатие = одно срабатывание.
|
||||
*/
|
||||
static uint8_t cheat_prev, immo_prev, sound_prev, nlvl_prev;
|
||||
static uint8_t upsd_prev, xdec_prev, xinc_prev;
|
||||
static uint8_t upsd_prev, xdec_prev, xinc_prev, cfg_prev;
|
||||
static uint8_t time_sub_prev, time_add_prev;
|
||||
|
||||
void pop_cheat_tick(void) __banked
|
||||
{
|
||||
@@ -335,10 +360,12 @@ void pop_cheat_tick(void) __banked
|
||||
* пользовательскую раскладку оригинала незачем;
|
||||
* Shift+L — следующий уровень (seg000:698, ветка next_level =
|
||||
* current_level + 1). Единственный вменяемый способ
|
||||
* тестировать уровни 2..15. NB: L без Shift — отладочный
|
||||
* тестировать уровни 2..14. NB: L без Shift — отладочный
|
||||
* «осторожный шаг вправо» (pop_ctrl KBD_DBG_STEPR); с
|
||||
* Shift шаг тоже пройдёт, но уровень тут же сменится, так
|
||||
* что конфликта на практике нет;
|
||||
* F7 / F8 — отнять / добавить минуту. Это точный аналог
|
||||
* keypad −/+ SDLPoP, но +/- заняты обходом комнат;
|
||||
* [ / ] — подвинуть Кида на ПИКСЕЛЬ влево/вправо (seg000:1828,
|
||||
* `--Char.x` / `++Char.x`). Нужен потому, что мост MAME
|
||||
* теряет нажатия при быстрой отправке, и подогнать Кида в
|
||||
@@ -356,12 +383,23 @@ void pop_cheat_tick(void) __banked
|
||||
uint8_t up = kbd_raw_down(KBD_CHEAT_UPSIDE);
|
||||
uint8_t xd = kbd_raw_down(KBD_CHEAT_XDEC);
|
||||
uint8_t xi = kbd_raw_down(KBD_CHEAT_XINC);
|
||||
uint8_t ts = kbd_raw_down(KBD_CHEAT_TIME_SUB);
|
||||
uint8_t ta = kbd_raw_down(KBD_CHEAT_TIME_ADD);
|
||||
/* C — записать POP.CFG сейчас (отладка MS2, до экрана Settings:
|
||||
* там сохранять будет Apply/выход). */
|
||||
uint8_t cf = kbd_raw_down(0x21); /* C (PS/2 set 2) */
|
||||
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
|
||||
/* Бессмертие по кругу: 0 выкл -> 1 «только бой» -> 2 «бой и
|
||||
* мелкий урон» -> 0. Что означают уровни — pop_state.h. */
|
||||
if (pop_cheats && i && !immo_prev)
|
||||
pop_immortal = (uint8_t)(pop_immortal >= 2 ? 0 : pop_immortal + 1);
|
||||
if (s && !sound_prev) pop_sfx_toggle(); /* не под pop_cheats */
|
||||
if (s && !sound_prev) {
|
||||
pop_sfx_toggle(); /* не под pop_cheats */
|
||||
pop_settings.sound_enabled = pop_snd_want;
|
||||
/* Порт seg000:657: оригинал отвечает строкой в статус-полосе. */
|
||||
pop_status_msg = (uint8_t)(pop_snd_want ? POP_MSG_SOUND_ON
|
||||
: POP_MSG_SOUND_OFF);
|
||||
}
|
||||
if (pop_cheats && nl && !nlvl_prev && pop_current_level < POP_LAST_LEVEL)
|
||||
pop_next_level = (uint8_t)(pop_current_level + 1);
|
||||
/* U — переворот экрана (seg000:0793 — чит и в оригинале).
|
||||
@@ -372,6 +410,9 @@ void pop_cheat_tick(void) __banked
|
||||
}
|
||||
if (pop_cheats && xd && !xdec_prev) Kid.x = (uint8_t)(Kid.x - 1);
|
||||
if (pop_cheats && xi && !xinc_prev) Kid.x = (uint8_t)(Kid.x + 1);
|
||||
if (pop_cheats && ts && !time_sub_prev) pop_timer_cheat_subtract();
|
||||
if (pop_cheats && ta && !time_add_prev) pop_timer_cheat_add();
|
||||
if (pop_cheats && cf && !cfg_prev) (void)pop_cfg_save();
|
||||
cheat_prev = k;
|
||||
immo_prev = i;
|
||||
sound_prev = s;
|
||||
@@ -379,104 +420,15 @@ void pop_cheat_tick(void) __banked
|
||||
upsd_prev = up;
|
||||
xdec_prev = xd;
|
||||
xinc_prev = xi;
|
||||
time_sub_prev = ts;
|
||||
time_add_prev = ta;
|
||||
cfg_prev = cf;
|
||||
}
|
||||
|
||||
/* ---- ОТЛАДКА: состояние прогона в верхнем борте ---------------------- *
|
||||
*
|
||||
* Четыре блока ПАЛОЧКАМИ, каждый своим цветом (outtextxy не годится: текст
|
||||
* тянет системный знакогенератор, _gfx_font_buf — 2 КБ статики в W2, и
|
||||
* сажает кучу). Двузначные числа — двумя группами: слева десятки, справа
|
||||
* единицы, || |||| = 24.
|
||||
*
|
||||
* жёлтый x 10.. 85 уровень (1..15)
|
||||
* белый x 100..175 комната (1..24)
|
||||
* голубой x 190..205 скорость (1 палочка NORMAL, 2 FAST, 3 FASTEST)
|
||||
* красный x 220..230 бессмертие (1 палочка = только бой, 2 = и мелкий урон)
|
||||
* пурпурный x 245 звук ВЫКЛЮЧЕН (Ctrl+S) либо не поднялся вовсе
|
||||
|
||||
* Звук показан «от противного» — палочка есть, когда звука НЕТ. Иначе
|
||||
* индикатор ничего не сообщает: включённый звук слышно и так, а вот
|
||||
* молчание неотличимо от «в этот момент нечему звучать».
|
||||
*
|
||||
* ЦВЕТА — индексы ИГРОВОЙ палитры, и выбраны они не по вкусу, а по двум
|
||||
* ограничениям.
|
||||
*
|
||||
* Первое: записи палитры лежат в ФОРМАТЕ BGR (BIOS $A4), а не RGB — в
|
||||
* kid.pal 0x3E это `55 ff ff`, то есть жёлтый, а не голубой. Читать файл
|
||||
* «как привычно» нельзя, цвета выйдут переставленными (на этом я и
|
||||
* попался в первом заходе).
|
||||
*
|
||||
* Второе, важнее: часть палитры ПЕРЕЗАПИСЫВАЕТСЯ на ходу — 0x40..0x4F
|
||||
* зелья, 0x50..0x5F env, 0x60..0x6F стена (эти два меняются при смене
|
||||
* ТАЙЛСЕТА, подземелье/дворец), 0x90..0x9F страж. Прежняя метка комнаты
|
||||
* рисовалась цветом 0x57, то есть из env-диапазона: белой она была только
|
||||
* в подземелье, а во дворце брала случайный цвет тайлсета. Здесь берём
|
||||
* только 0x3A..0x3F — их не трогает никто:
|
||||
* 0x3E жёлтый (255,255,85), 0x3F белый, 0x3B голубой (85,255,255),
|
||||
* 0x3C красный (226,0,48) — тот же, которым игра даёт вспышку урона.
|
||||
*
|
||||
* Перерисовка change-driven: пока ничего из показанного не изменилось, в
|
||||
* борт не пишем вовсе. Счётчик 2 — обе страницы дабл-буфера. */
|
||||
|
||||
static uint8_t room_label_sig = 0xFF; /* последняя показанная комната */
|
||||
static uint8_t lvl_label_sig = 0xFF; /* ...уровень */
|
||||
static uint8_t spd_label_sig = 0xFF; /* ...режим скорости */
|
||||
static uint8_t imm_label_sig = 0xFF; /* ...бессмертие */
|
||||
static uint8_t snd_label_sig = 0xFF; /* ...звук (pop_snd_want) */
|
||||
static uint8_t room_label_redraw; /* остаток кадров (2 = обе страницы) */
|
||||
|
||||
/* Группа палочек: n штук от x вправо, шаг 5, ширина 2. */
|
||||
static void label_bars(int x, uint8_t n)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < n; i++) bar(x + i * 5, 0, x + 1 + i * 5, 5);
|
||||
}
|
||||
|
||||
void pop_dbg_room_label(uint8_t room) __banked
|
||||
{
|
||||
/* СНАЧАЛА сохранить, потом сравнивать — обход sdcc_z80_cmp_store_a_bug
|
||||
* (`if (n != g) g = n;` пишет разность вместо значения). */
|
||||
uint8_t prev_room = room_label_sig, prev_lvl = lvl_label_sig;
|
||||
uint8_t prev_spd = spd_label_sig, prev_imm = imm_label_sig;
|
||||
uint8_t prev_snd = snd_label_sig;
|
||||
uint8_t lvl = pop_current_level, spd = pop_speed_mode, imm = pop_immortal;
|
||||
uint8_t snd = pop_snd_want;
|
||||
room_label_sig = room;
|
||||
lvl_label_sig = lvl;
|
||||
spd_label_sig = spd;
|
||||
imm_label_sig = imm;
|
||||
snd_label_sig = snd;
|
||||
if (room != prev_room || lvl != prev_lvl || spd != prev_spd ||
|
||||
imm != prev_imm || snd != prev_snd)
|
||||
room_label_redraw = 2;
|
||||
if (!room_label_redraw) return;
|
||||
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(10, 0, 250, 5);
|
||||
|
||||
setfillstyle(SOLID_FILL, 0x3E); /* уровень — жёлтый */
|
||||
label_bars(10, (uint8_t)(lvl / 10));
|
||||
label_bars(40, (uint8_t)(lvl % 10));
|
||||
|
||||
setfillstyle(SOLID_FILL, 0x3F); /* комната — белый */
|
||||
label_bars(100, (uint8_t)(room / 10));
|
||||
label_bars(130, (uint8_t)(room % 10));
|
||||
|
||||
/* Скорость: номер режима + 1, то есть NORMAL 1, FAST 2, FASTEST 3 —
|
||||
* чем больше палочек, тем быстрее (см. нумерацию в pop_pace.h). */
|
||||
setfillstyle(SOLID_FILL, 0x3B); /* скорость — голубой */
|
||||
label_bars(190, (uint8_t)(spd + 1));
|
||||
|
||||
if (imm) { /* бессмертие — красный */
|
||||
setfillstyle(SOLID_FILL, 0x3C);
|
||||
label_bars(220, imm); /* палочек = уровень (1 или 2) */
|
||||
}
|
||||
if (!snd) { /* звука НЕТ — пурпурный */
|
||||
setfillstyle(SOLID_FILL, 0x3D);
|
||||
label_bars(245, 1);
|
||||
}
|
||||
room_label_redraw--;
|
||||
}
|
||||
/* Отладочная строка в верхнем борте переехала в pop_status.c (банк 9):
|
||||
* она теперь ТЕКСТОВАЯ, а шрифт живёт там же. Здесь не осталось ничего —
|
||||
* см. заголовок pop_status.h про то, почему заявка сообщения стоит один
|
||||
* байт, а весь рендер сидит в банке. */
|
||||
|
||||
/* Загрузить комнату room в рабочие массивы и отрисовать её фон в ОБЕ
|
||||
* страницы дабл-буфера (у каждой своя видео-ОЗУ/теневая копия). Оставляет
|
||||
@@ -568,7 +520,7 @@ void enter_room_side(uint8_t room, uint8_t side) __banked
|
||||
}
|
||||
seam_redraw = 0; seam_rows = 0; /* шов свежий на обеих страницах — редрой не нужен */
|
||||
/* ENTER-ROOM-FAST: комната рисуется ОДИН раз — в скрытую страницу, — и
|
||||
* тут же показывается флипом; вторая страница дабл-буфера получает её
|
||||
* обычно тут же показывается флипом; вторая страница дабл-буфера получает её
|
||||
* КОПИЕЙ акселератора (gfx_copy_page, ~384 burst-строки против почти
|
||||
* полумиллиона тактов на вторую отрисовку). Побочный эффект важнее
|
||||
* тактов: игрок больше не видит, КАК рисуются тайлы — только готовый
|
||||
@@ -585,13 +537,20 @@ void enter_room_side(uint8_t room, uint8_t side) __banked
|
||||
gfx_rect_t full;
|
||||
pg = (uint8_t)(gfx_get_visible_page() ^ 1); /* скрытая */
|
||||
gfx_set_draw_page(pg);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, 0, 319, 255);
|
||||
pop_screen_fill_field();
|
||||
pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg);
|
||||
gfx_set_visible_page(pg); /* показать готовое */
|
||||
gfx_set_draw_page((uint8_t)(pg ^ 1));
|
||||
full.x = 0; full.y = 0; full.w = 320; full.h = 256;
|
||||
gfx_copy_page(&full, GFX_COPY_DIRECT); /* вторую — копией */
|
||||
if (level_prepare) {
|
||||
/* Межуровневый staging: пока pop_pal_level_load последовательно
|
||||
* пишет KID.PAL и env/wall, видима нулевая чёрная страница.
|
||||
* Показывать эту комнату разрешено только после финальной
|
||||
* палитры — иначе level 4 успевает мелькнуть цветами level 3. */
|
||||
level_prepared_page = pg;
|
||||
} else {
|
||||
gfx_set_visible_page(pg); /* показать готовое */
|
||||
gfx_set_draw_page((uint8_t)(pg ^ 1));
|
||||
full.x = 0; full.y = 0; full.w = 320; full.h = 256;
|
||||
gfx_copy_page(&full, GFX_COPY_DIRECT); /* вторую — копией */
|
||||
}
|
||||
}
|
||||
cur_room = room;
|
||||
kid_room = room; /* S1: комната Kid синхронна отрисованной (S3 разведёт) */
|
||||
@@ -647,7 +606,7 @@ void pop_qsave_restore_room(uint8_t room) __banked
|
||||
seam_redraw = seam_rows = 0;
|
||||
pg = (uint8_t)(gfx_get_visible_page() ^ 1);
|
||||
gfx_set_draw_page(pg);
|
||||
setfillstyle(SOLID_FILL, BLACK); bar(0, 0, 319, 255);
|
||||
pop_screen_fill_field();
|
||||
pop_room_draw(room, room_fg, room_mod, lcol_fg, lcol_bg);
|
||||
gfx_set_visible_page(pg);
|
||||
gfx_set_draw_page((uint8_t)(pg ^ 1));
|
||||
@@ -898,16 +857,44 @@ void pop_flip_screen(void) __banked
|
||||
|
||||
int pop_boot(void) __banked
|
||||
{
|
||||
/* Набор фона — по типу СТАРТОВОГО уровня. Атласы грузятся ДО initgraph
|
||||
* (тот снимает DSS-страницу W0); уровень к этому моменту ещё не прочитан,
|
||||
* поэтому тип берём напрямую от номера. */
|
||||
/* Настройки: дефолты, затем POP.CFG, если он есть и корректен. */
|
||||
pop_settings_defaults();
|
||||
pop_cfg_load();
|
||||
pop_timer_new_game(); /* default 60:00, Settings may freeze it */
|
||||
|
||||
/* Экран Sprinter хранит VRAM и палитры между программами. Перейти в
|
||||
* графику надо ДО долгих чтений, но первой же операцией погасить ОБЕ
|
||||
* палитры: тогда ни старый экран, ни частично собранный первый уровень
|
||||
* не могут мелькнуть перед первым title fade-in. */
|
||||
/* PIC-палитры графики живут и в text mode. Предзапись чёрного сокращает
|
||||
* окно между аппаратным SETVMOD и первой строкой нашего кода, в котором
|
||||
* иначе успевает мелькнуть VRAM предыдущей программы. */
|
||||
pop_pal_black();
|
||||
initgraph_black();
|
||||
/* initgraph_black уже обнулил VRAM до SETVMOD. Повторная заливка после
|
||||
* него фиксирует и shadow-копии, откуда затем работают heal/page-copy. */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
gfx_set_draw_page(0);
|
||||
bar(0, 0, 319, 255);
|
||||
gfx_set_draw_page(1);
|
||||
bar(0, 0, 319, 255);
|
||||
gfx_set_visible_page(0);
|
||||
pop_pal_black();
|
||||
|
||||
/* Набор фона — по типу СТАРТОВОГО уровня; atlas_load безопасен и после
|
||||
* initgraph: при первом вызове он сохраняет страницу DSS W0. Уровень к
|
||||
* этому моменту ещё не прочитан, поэтому тип берём напрямую от номера. */
|
||||
pop_current_level = FIRST_LEVEL;
|
||||
if (pop_bg_load(pop_level_type()) != 0) {
|
||||
puts("pop_bg_load failed");
|
||||
return -1;
|
||||
}
|
||||
/* pop_bg_load установил только свои слоты палитры. До конца загрузки
|
||||
* всё равно держим экран чёрным. */
|
||||
pop_pal_black();
|
||||
pop_cd_init(); /* метка «фон трогали» — пустые диапазоны */
|
||||
pop_cheats = 1; /* режим разработки: читы включены */
|
||||
/* pop_cheats/speed применит pop_settings_apply после pop_sfx_init. */
|
||||
pop_guard_reset();
|
||||
if (pop_kid_data_load("KID\\kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */
|
||||
printf("kid_data.bin load failed\n");
|
||||
@@ -917,6 +904,7 @@ int pop_boot(void) __banked
|
||||
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
|
||||
pop_shadow_load(); /* запечённый набор Тени — один раз на запуск */
|
||||
pop_sfx_init(); /* звук: 8 страниц оцифровки (вывод пока молчит) */
|
||||
pop_settings_apply(1); /* CFG -> speed/cheats/CBL */
|
||||
if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
|
||||
puts("kid atlas load failed");
|
||||
return -1;
|
||||
@@ -929,38 +917,34 @@ int pop_boot(void) __banked
|
||||
pop_kid_hp_newgame(); /* init_game: hitp_beg_lev = start_hitp */
|
||||
pop_checkpoint = 0; /* init_game (seg003:37) */
|
||||
|
||||
initgraph();
|
||||
gfx_set_visible_page(0);
|
||||
if (gfx_pal_fload(0, "KID\\kid.pal") < 0) /* единая игровая палитра */
|
||||
gfx_pal_fload(0, "a:\\kid.pal");
|
||||
gfx_pal_sync(); /* палитра 0 -> плоскость 1 */
|
||||
/* kid.pal — ЕДИНАЯ игровая палитра, собранная из VDUNGEON, и она только
|
||||
* что затёрла слоты тайлсета. Возвращаем цвета текущего набора: на
|
||||
* дворцовых уровнях без этого рисуется дворцовая геометрия подземельными
|
||||
* красками (сине-серое вместо песочного). */
|
||||
pop_bg_pal_apply();
|
||||
/* ...и палитру Тени: kid.pal затёр и её 16 слотов (pop_shadow.c). */
|
||||
pop_shadow_pal_apply();
|
||||
/* FONT.ATL нужен palette snapshot/dim; загрузка идёт ещё на чёрной
|
||||
* палитре и поэтому не открывает игроку старое содержимое VRAM. */
|
||||
if (pop_menu_init() != 0) {
|
||||
puts("menu font atlas load failed");
|
||||
return -1;
|
||||
}
|
||||
/* kid.pal + динамические slots tileset/Shadow — один логический
|
||||
* источник. pop_pal_game_load делает снимок ПОСЛЕ всех доплат. */
|
||||
pop_pal_game_load();
|
||||
/* Сохраняем ИМЕННО игровую палитру: после стартового title/intro она
|
||||
* понадобится как источник обычного fade-in. Для build-info экран
|
||||
* остаётся штатный яркий игровой фон. */
|
||||
if (!pop_settings.show_build_info) {
|
||||
pop_pal_apply(4);
|
||||
}
|
||||
|
||||
/* Фон комнаты рисуется в ОБЕ графические страницы: у каждой своя
|
||||
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
|
||||
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
|
||||
* ТОЛЬКО прямоугольник Кида (pop_char_heal), а не весь экран. */
|
||||
/* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить
|
||||
* trob/room_modif (ленивая инициализация из уровня при первом входе). */
|
||||
/* Старт — ПО ДАННЫМ УРОВНЯ (комната/тайл/направление/поза входа), см.
|
||||
* pop_start_level. Управление: ←/→ бег, ↓ присед, ↑+→ прыжок с места,
|
||||
* Shift шаг, ESC — выход. Карта коллизии задана внутри enter_room. */
|
||||
pop_start_level();
|
||||
/* Зеркальный набор персонажей — ТУТ ЖЕ, если стартуем сразу на уровне с
|
||||
* зельем инверсии (make LEVEL=9). Иначе первый переворот утащил бы
|
||||
* чтение 34 файлов (~1.5 с) прямо в кадр: пользователь видел «затык» в
|
||||
* синей фазе профиля. Тот же вызов есть в смене уровня; функция
|
||||
* идемпотентна и звать её можно откуда угодно (pop_vflip.h). */
|
||||
if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all();
|
||||
gfx_set_draw_page(0);
|
||||
pop_char_draw(POP_CH_KID);
|
||||
pop_char_fore(POP_CH_KID);
|
||||
/* Стартовая комната до title НЕ НУЖНА: LEVEL_LOAD всё равно заново
|
||||
* выполняет pop_level_switch. Если нарисовать её сейчас в обе страницы,
|
||||
* она становится сохранённой VRAM и может мелькнуть перед первым title
|
||||
* при следующем запуске приложения. Единственное исключение — явно
|
||||
* включённый build-info: его фон по контракту является игровой сценой. */
|
||||
if (pop_settings.show_build_info) {
|
||||
pop_start_level();
|
||||
if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all();
|
||||
gfx_set_draw_page(0);
|
||||
pop_char_draw(POP_CH_KID);
|
||||
pop_char_fore(POP_CH_KID);
|
||||
}
|
||||
/* Звук включаем ПОСЛЕДНИМ действием инициализации — когда все атласы,
|
||||
* уровень и палитры уже прочитаны. Раньше нельзя: ESTEX уходит в диск
|
||||
* надолго, насос не успевает долить блок, и на слух это скрежет
|
||||
@@ -975,6 +959,16 @@ int pop_boot(void) __banked
|
||||
* следующий. Возврат -1 — фатально (не удалось сменить набор фона). */
|
||||
int pop_level_switch(void) __banked
|
||||
{
|
||||
uint8_t old_page;
|
||||
gfx_rect_t full;
|
||||
|
||||
/* Граница FG0: повреждённый snapshot или старый внешний вызов не должен
|
||||
* даже пытаться открыть res2015.bin. Финальный маршрут 14 -> ENDING
|
||||
* добавит автомат приложения (FG11); пока просто оставляем уровень. */
|
||||
if (pop_next_level > POP_LEVEL_LAST) {
|
||||
pop_next_level = pop_current_level;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Порт play_level_2 (seg003:0386..0410) + play_level (seg003:81):
|
||||
* цикл уровня кончается, когда next_level разошёлся с
|
||||
@@ -990,6 +984,20 @@ int pop_level_switch(void) __banked
|
||||
if (pop_level_load_num(pop_next_level) != 0) {
|
||||
pop_next_level = pop_current_level; /* файла нет — остаёмся */
|
||||
} else {
|
||||
/* С этого места видим только индекс 0. Это одновременно
|
||||
* скрывает дисковую подготовку и даёт безопасный экран,
|
||||
* пока loaders физически меняют отдельные диапазоны palette. */
|
||||
pop_pal_black();
|
||||
old_page = gfx_get_visible_page();
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* Обнуляем ОБЕ страницы, а не только software-visible:
|
||||
* аппаратный SCREEN_SWITCH и сохранённое BGI-состояние не
|
||||
* должны оставлять старые sprites на соседней странице. */
|
||||
gfx_set_draw_page(0);
|
||||
pop_screen_fill_game();
|
||||
gfx_set_draw_page(1);
|
||||
pop_screen_fill_game();
|
||||
pop_wait_edge();
|
||||
/* Набор спрайтов соперника у каждого уровня СВОЙ
|
||||
* (load_lev_spr, seg000:1092): уровень 3 — скелет, у него и
|
||||
* кадров меньше, чем у стража. Без перезагрузки соперник
|
||||
@@ -997,6 +1005,9 @@ int pop_level_switch(void) __banked
|
||||
* пределы, и персонаж просто не виден. */
|
||||
pop_guard_free();
|
||||
pop_guard_load();
|
||||
/* guard loader применяет свои цветовые слоты физически;
|
||||
* пока остальной уровень читается, экран обязан быть чёрным. */
|
||||
pop_pal_black();
|
||||
/* Зеркальные копии атласов сделаны из СТАРЫХ страниц —
|
||||
* после перезагрузки спрайтов они мусор (pop_vflip.h). */
|
||||
pop_vflip_reset();
|
||||
@@ -1008,18 +1019,82 @@ int pop_level_switch(void) __banked
|
||||
puts("bg set load failed");
|
||||
return -1;
|
||||
}
|
||||
/* Смена tileset тоже пишет env/wall slots. Не открывать их
|
||||
* до готовности обеих страниц нового уровня. */
|
||||
pop_pal_black();
|
||||
/* Всё состояние прошлого уровня сносит сам pop_start_level:
|
||||
* pop_trob_reset обнуляет room_seen (модификаторы комнат
|
||||
* перечитаются из НОВОЙ страницы), тайлы/стражи приезжают
|
||||
* из неё же, loose чистит enter_room. */
|
||||
level_prepare = 1;
|
||||
pop_start_level();
|
||||
level_prepare = 0;
|
||||
/* pop_bg_load мог заменить только env/wall, а до этого
|
||||
* экран жил после fade на чёрной палитре заставки. Полный
|
||||
* game source обязателен даже для dungeon->dungeon: иначе
|
||||
* fade_in восстановит старый (либо уже чёрный) snapshot.
|
||||
* Яркость caller выберет сам: gameplay может показать кадр
|
||||
* сразу, lifecycle title/demo — сначала apply(4)+fade_in. */
|
||||
/* Уровень с зельем инверсии — заранее поднять зеркальный
|
||||
* набор персонажей (pop_vflip.h): 34 страницы читаются ~1.5 с,
|
||||
* и делать это в момент глотка нельзя. Звать можно откуда
|
||||
* угодно — функция идемпотентна. */
|
||||
* и делать это после открытия палитры либо в момент глотка
|
||||
* нельзя. Звать можно откуда угодно — функция идемпотентна. */
|
||||
if (pop_current_level == POP_UPSIDE_LEVEL) pop_vflip_load_all();
|
||||
pop_sfx_start(); /* ...и обратно, когда всё прочитано */
|
||||
/* Последнее действие подготовки: собрать полный logical
|
||||
* source новой палитры. Пока он собирается физическими
|
||||
* записями, видимая old_page заполнена нулём. */
|
||||
pop_pal_level_load(1);
|
||||
/* Атомарный commit на границе растра: впервые показать
|
||||
* готовую комнату уже с окончательной палитрой уровня.
|
||||
* Вторую страницу синхронизировать аппаратной копией. */
|
||||
pop_wait_edge();
|
||||
gfx_set_visible_page(level_prepared_page);
|
||||
gfx_set_draw_page(old_page);
|
||||
full.x = 0; full.y = 0; full.w = 320; full.h = 256;
|
||||
gfx_copy_page(&full, GFX_COPY_DIRECT);
|
||||
}
|
||||
/* CBL намеренно НЕ открываем здесь. После загрузки caller ещё
|
||||
* может делать BIOS fade/palette либо восстанавливать QuickSave;
|
||||
* открытый буфер за это время успевает повторить старую половину
|
||||
* и даёт скрежет. Caller запускает звук последним действием. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Общий холодный маршрут title/intro/credits/demo -> настоящая игра.
|
||||
* Держим его в банке 8: это разовый lifecycle-код, а не часть кадрового
|
||||
* пути, и перенос возвращает резиденту W1 место под стек/heap. */
|
||||
int pop_new_game_load(void) __banked
|
||||
{
|
||||
/* Клавиша, которой пропустили заставку/demo, не должна стать первым
|
||||
* движением Кида или тут же открыть pause menu на уровне. */
|
||||
while (kbd_raw_any_down()) {
|
||||
kbd_raw_sync();
|
||||
gfx_wait_vsync();
|
||||
}
|
||||
|
||||
pop_kid_hp_newgame();
|
||||
hitp_max = POP_START_HITP;
|
||||
pop_timer_new_game();
|
||||
pop_checkpoint = 0;
|
||||
pop_next_level = FIRST_LEVEL;
|
||||
|
||||
/* Title мог завершиться на яркой title-палитре. Сначала погасить её,
|
||||
* затем восстановить полный game snapshot; level_switch собирает обе
|
||||
* страницы уже невидимо, и первый игровой кадр проявляется. */
|
||||
pop_pal_apply(4);
|
||||
gfx_set_draw_page(0);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
pop_screen_fill_game();
|
||||
gfx_set_draw_page(1);
|
||||
pop_screen_fill_game();
|
||||
gfx_set_visible_page(1);
|
||||
pop_pal_game_load();
|
||||
pop_pal_apply(4);
|
||||
if (pop_level_switch() != 0) return -1;
|
||||
/* Первый уровень появляется сразу, без fade, но только после того как
|
||||
* обе страницы и его собственная палитра полностью готовы. */
|
||||
pop_pal_apply(0);
|
||||
(void)pop_sfx_start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1039,6 +1114,7 @@ void pop_shutdown(void) __banked
|
||||
pop_bg_free();
|
||||
pop_kid_free();
|
||||
pop_shadow_free();
|
||||
pop_menu_free();
|
||||
pop_guard_free();
|
||||
pop_kid_data_free();
|
||||
pop_level_free();
|
||||
|
||||
Reference in New Issue
Block a user