Зелье медленного падения (перо) + цвет пузырьков по типу зелья
L7-FEATHER: тип 3 (уровень 7, комната 1) больше не пустой TODO. - pop_map.c: pop_feather — счётчик кадров эффекта (POP_FEATHER_FRAMES = 225, ванильный порог do_timers seg003:0517; привязку к звуку взять неоткуда). fall_accel — ускорение 1 / потолок 4 (seg006:057C) вместо 3 / 33. proc_get_object case 3: взвести эффект + зелёная вспышка на 3 кадра. - pop_kid.c: опкод JMP_IF_FEATHER (0xF7) больше не пропускает адрес безусловно — под пером прыгает по нему, то есть seqtbl уводит падение и удар в ветки stepfloat/bumpfloat (плавные кадры, без урона). - roomtest.c: pop_flash_red -> pop_flash_color (жёлтый/красный/ЗЕЛЁНЫЙ); сброс pop_feather в pop_start_level (seg003:189). - Цвет пузырька по ТИПУ зелья (seg008:652), чего у нас не было вовсе: 3/4 зелёный, 5/6 СИНИЙ, остальные красный. Mono-блиттера с параметром цвета в libbgi нет, поэтому цвет запекается при упаковке: pop_pack_bg.py кладёт те же 7 кадров ещё дважды (id 30..36 зелёные, 40..46 синие), pop_potion_draw выбирает набор. Атлас 23 -> 37 спрайтов (+423 Б). - Синее зелье «−HP» приведено к оригиналу (seg006:1892): своей вспышки не ставит (красный кадр даёт общий flash_if_hurt — иначе экран красился дважды), а на уровне зелий забирает ПОЛОВИНУ запаса HP. Такое зелье стоит уже на пройденном уровне 2 (комната 13, тайл (1,3)), а также ур.8 комн.2 и весь ур.15 — до сих пор оно было красным. Тесты: phys_feather_fall_is_slow_and_harmless (обычное падение с двух рядов разгоняется и стоит HP, под пером скорость <= 4 и HP целое); tests-host 5/5 (1727 в [phys]), size-check OK. Расхождения с ванилью — в docs/impl_diff.md (перо ловит только Кида; синее зелье без своей вспышки). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -171,3 +171,41 @@ for (short column = 9; column >= 0; --column) {
|
||||
направления: нажать её из комнаты 11 и войти в 24; и наоборот — войти в 24
|
||||
поверху и наступить на неё, стоя в шве. Картинка кнопки обязана совпадать
|
||||
со статусом ворот в обоих случаях.
|
||||
|
||||
---
|
||||
|
||||
## Перо (медленное падение) ловит ТОЛЬКО Кида
|
||||
|
||||
**Оригинал** (`fall_accel`, seg006:057C): `is_feather_fall` — глобальный флаг,
|
||||
и медленное падение достаётся ЛЮБОМУ персонажу, который окажется в `Char`,
|
||||
пока эффект жив. То есть страж, сошедший с уступа в те же секунды, парит
|
||||
вместе с Кидом, хотя зелье пил не он. SDLPoP считает это багом и чинит
|
||||
опцией `fix_feather_fall_affects_guards`.
|
||||
|
||||
**Мы** берём поведение С ФИКСОМ: `pop_feather` проверяется вместе с
|
||||
`Char.charid == CHARID_0_KID` — и в `fall_accel` (`pop_map.c`), и в опкоде
|
||||
`JMP_IF_FEATHER` интерпретатора seqtbl (`pop_kid.c`), чтобы физика и анимация
|
||||
не разъехались.
|
||||
|
||||
**Чем платим.** Сцена, где страж падает при живом пере, будет выглядеть иначе,
|
||||
чем в DOS-оригинале (у нас он падает нормально, там — парит). На уровне 7,
|
||||
единственном с этим зельем, такой сцены нет: зелье в комнате 1, стражи — в
|
||||
других комнатах.
|
||||
|
||||
**Что проверять при регрессе.** Уровень 7: выпить зелье в комнате 1, тут же
|
||||
столкнуть стража в провал — он обязан падать БЫСТРО, а Кид рядом — медленно.
|
||||
|
||||
---
|
||||
|
||||
## Синее зелье («−HP») не ставит свою вспышку
|
||||
|
||||
**Оригинал** (`proc_get_object`, seg006:1892): ветка `case 5` только глушит
|
||||
звуки, играет `sound_13_kid_hurt` и ставит `hitp_delta`. Экран краснеет не
|
||||
здесь, а общим механизмом «Кид ранен» (`flash_if_hurt`, seg003:0AFC).
|
||||
|
||||
**Мы** раньше ставили в этой ветке ещё и `pop_flash_*` (красную вспышку на 2
|
||||
кадра) — то есть красили экран дважды: своей вспышкой и кадром урона.
|
||||
Приведено к оригиналу: ветка правит только `hitp_delta`, краснеет `pop_kid_hurt`.
|
||||
|
||||
**Что проверять при регрессе.** Уровень 2, комната 13, зелье `(1,3)`: выпить —
|
||||
HP убавляется на единицу, экран краснеет РОВНО один раз (без двойного строба).
|
||||
|
||||
@@ -37,6 +37,11 @@ static const char *const pop_tile_pal[POP_TILESETS] = { "pop_tile.pal", "pal_til
|
||||
#endif /* POP_BG_ATLAS_NAMES */
|
||||
#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья, от набора не зависит */
|
||||
#define POP_PAL_POT 0x40
|
||||
/* Пузырёк зелья: красный набор = id 16..22 (кадры оригинала),
|
||||
зелёный (перо/переворот) и синий (вред/открыть) = те же кадры
|
||||
под id 30..36 и 40..46 (draw_tile_anim, seg008:652). */
|
||||
#define POP_POT_BUBB_GREEN 30
|
||||
#define POP_POT_BUBB_BLUE 40
|
||||
#define POP_BG_PAL "pop_bg.pal"
|
||||
|
||||
#endif
|
||||
|
||||
Binary file not shown.
@@ -58,6 +58,37 @@
|
||||
3. Хост-тест `t_grab.c` уже умеет мерить ширину окна (карта исходов по фазе X и
|
||||
задержке нажатия) — на нём и проверять «стало стабильнее».
|
||||
|
||||
### Протокол замера (написан 2026-08-12, чтобы следующий заход начался с фактов)
|
||||
|
||||
Пользователь связывает с этим же и «двойные срабатывания не вовремя». Прежде
|
||||
чем трогать драйвер — доказать, что он вообще виноват: **сегодняшний случай
|
||||
«Кид ходит как с зажатым Shift» оказался артефактом MCP-моста, а не драйвера**
|
||||
(`pause` освободил 6 незакрытых входов, которые плагин переустанавливал каждый
|
||||
кадр; как только они отпустились, драйвер снял бит сам). То есть break-коды
|
||||
драйвер обрабатывает верно, и ложная тревога здесь стоила часа.
|
||||
|
||||
Что мерить:
|
||||
|
||||
1. **Поток скан-кодов**: кольцевой лог в трамплине прерывания
|
||||
(`libc/irq/_irq_tramp.c` / `kbd_raw_poll.c`) — байт кода, флаг make/break,
|
||||
номер кадра. Читать из MAME по адресу буфера (`mem`), как читаем `_Kid`.
|
||||
2. **Что увидел движок**: `pop_ctrl_shift_held()` и битмап `_kbdraw_down`
|
||||
(адрес — из `.map`, для roomtest 0xAEF9) на тех же кадрах. Расхождение
|
||||
«код пришёл, а движок не увидел» = потеря в драйвере; «кода не было, а бит
|
||||
стоит» = потеря break (то, что подозревает пользователь).
|
||||
3. **Сцены**: бег с отпусканием, разворот, вис + отпускание Shift, падение с
|
||||
зажатым заранее Shift (вход на уровень 7), быстрое повторное нажатие
|
||||
(проверка «двойного срабатывания» — typematic, см. memory
|
||||
`kbd_overrun_wipe_modifiers`).
|
||||
|
||||
Что считать нормой: каждому make ровно один break; между make и видимым
|
||||
`kbd_raw_down()` — не больше кадра; при удержании typematic-повторы НЕ должны
|
||||
доходить до `pop_ctrl` как новые нажатия (диспетчер ждёт фронт).
|
||||
|
||||
Отдельная гипотеза, которую замер тоже закроет: окно зацепа отмеряется В
|
||||
КАДРАХ, а игра идёт на ~39 % быстрее оригинала ([L1-SPEED](TASKS_OPEN.md#l1-speed))
|
||||
— тогда «менее стабильно» это не клавиатура, а темп, и чинить надо темп.
|
||||
|
||||
---
|
||||
|
||||
<a id="уровень-2"></a>
|
||||
|
||||
@@ -89,8 +89,14 @@ MEM-BANK2, MEM-BANK5, CLIP-1, KBD-1, DBG-CHEATS.
|
||||
|
||||
### <a id="l7-feather"></a>L7-FEATHER. Зелье МЕДЛЕННОГО ПАДЕНИЯ (уровень 7, комната 1)
|
||||
|
||||
**Задача (пользователь, 2026-08-12): составить план — здесь он и есть; код
|
||||
следующим заходом.**
|
||||
> **Статус 2026-08-12: код написан, ждёт живой проверки в MAME.** Сделаны все
|
||||
> шесть шагов ниже плюс синие пузырьки зелья «−HP» (тип 5) — они всплыли по
|
||||
> ходу: пользователь напомнил, что такое зелье стоит уже на уровне 2 (комната
|
||||
> 13, тайл `(1,3)`; ещё ур.8 комн.2 и весь ур.15), а цвет у него был красный.
|
||||
> Хост-тесты: `phys_feather_fall_is_slow_and_harmless` (скорость ≤ 4 и HP
|
||||
> целое против обычного падения с двух рядов). **Осталось**: проверить в MAME
|
||||
> — ур.7 комн.1 (зелёные пузырьки, зелёная вспышка, медленный спуск в шахту) и
|
||||
> ур.2 комн.13 (синие пузырьки, −1 HP, экран краснеет один раз).
|
||||
|
||||
Зелье на `(2,8)` комнаты 1 уровня 7 (сверено по `res2007.bin`: modifier 3, то
|
||||
есть `potion_type = 3` — «slow fall»). Сейчас `pop_proc_get_object`
|
||||
|
||||
@@ -426,9 +426,18 @@ void play_seq(void)
|
||||
case 0xFE: /* FLIP */
|
||||
Char.direction = (int8_t)~Char.direction;
|
||||
break;
|
||||
case 0xF7: /* JMP_IF_FEATHER: нет пера -> пропустить адрес */
|
||||
Char.curr_seq += 2;
|
||||
break;
|
||||
case 0xF7: /* JMP_IF_FEATHER (seg006:586): под зельем медленного
|
||||
* падения — перейти по адресу (в seqtbl это ветки
|
||||
* stepfloat/bumpfloat: «парящие» кадры падения и удара,
|
||||
* без урона), иначе пропустить адрес и идти дальше.
|
||||
* Эффект достаётся только Киду — как и сама физика пера
|
||||
* (fall_accel, pop_map.c). */
|
||||
if (!pop_feather || Char.charid != CHARID_0_KID) {
|
||||
Char.curr_seq += 2;
|
||||
break;
|
||||
}
|
||||
/* иначе — ПРОВАЛ в JMP ниже: адрес лежит ровно там же */
|
||||
/* fallthrough */
|
||||
case 0xFF: { /* JMP */
|
||||
uint16_t a = (uint16_t)(SEQ(Char.curr_seq) |
|
||||
((uint16_t)SEQ((uint16_t)(Char.curr_seq + 1)) << 8));
|
||||
|
||||
@@ -118,6 +118,10 @@
|
||||
#define SCREENSPACE_X 58
|
||||
#define FALL_ACCEL 3
|
||||
#define FALL_MAX 33
|
||||
/* Перо (seg006:057C, types.h:1435): ускорение и потолок скорости падения
|
||||
* под зельем медленного падения. */
|
||||
#define FALL_ACCEL_FEATHER 1
|
||||
#define FALL_MAX_FEATHER 4
|
||||
|
||||
#define TILE_MIDX 7
|
||||
#define FRAME_THIN 0x20
|
||||
@@ -158,6 +162,11 @@ static uint8_t *g_fg; /* fg[30], row*10+col (МУТАБЕЛЬНАЯ
|
||||
static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */
|
||||
static uint8_t g_obj_xl; /* позиция внутри тайла (get_tile_div_mod) */
|
||||
static uint8_t grab_timer; /* после зацепа (12): блокирует climb-up */
|
||||
/* is_feather_fall (seg000:15F8): кадров, пока действует зелье медленного
|
||||
* падения. У оригинала это флаг + счётчик тиков со сроком по звуку; у нас
|
||||
* просто обратный отсчёт кадров (POP_FEATHER_FRAMES, pop_tune.h). Публичный:
|
||||
* его читают play_seq (опкод JMP_IF_FEATHER, pop_kid.c) и приложение. */
|
||||
uint8_t pop_feather;
|
||||
/* Состояние edge-триггера бампа — массивы флагов перекрытия по колонкам
|
||||
* (порт prev_coll_flags/curr_row_coll_flags, seg004); объявлены ниже, рядом
|
||||
* с check_collisions. Сброс при смене комнаты — coll_prev_row. */
|
||||
@@ -823,8 +832,19 @@ static void do_fall(void)
|
||||
static void fall_accel(void)
|
||||
{
|
||||
if (Char.action == ACT_FREEFALL) {
|
||||
int fy = (uint8_t)Char.fall_y + FALL_ACCEL;
|
||||
if (fy > FALL_MAX) fy = FALL_MAX;
|
||||
int fy;
|
||||
/* Перо (seg006:057C). Оригинал ловит эффектом ЛЮБОГО персонажа в
|
||||
* Char — зелье пьёт только Кид, но флаг глобальный, и страж, упавший
|
||||
* в ту же минуту, тоже парит; SDLPoP чинит это опцией
|
||||
* fix_feather_fall_affects_guards. Берём поведение С ФИКСОМ (только
|
||||
* Кид): расхождение осознанное, запись в ../docs/impl_diff.md. */
|
||||
if (pop_feather && Char.charid == CHARID_0_KID) {
|
||||
fy = (uint8_t)Char.fall_y + FALL_ACCEL_FEATHER;
|
||||
if (fy > FALL_MAX_FEATHER) fy = FALL_MAX_FEATHER;
|
||||
} else {
|
||||
fy = (uint8_t)Char.fall_y + FALL_ACCEL;
|
||||
if (fy > FALL_MAX) fy = FALL_MAX;
|
||||
}
|
||||
Char.fall_y = (int8_t)fy;
|
||||
}
|
||||
}
|
||||
@@ -1157,7 +1177,7 @@ uint8_t pop_item_taken; /* 0 = нет; иначе tilepos+1 подн
|
||||
uint8_t pop_flash_time; /* кадров вспышки фона (flash_time) */
|
||||
/* Цвет вспышки: 0 = ярко-жёлтая (меч), 1 = красная (зелье). В оригинале это
|
||||
* flash_color; нам хватает двух значений, которые реально встречаются. */
|
||||
uint8_t pop_flash_red;
|
||||
uint8_t pop_flash_color;
|
||||
static int8_t pickup_obj_type; /* -1 = меч (порт pickup_obj_type) */
|
||||
|
||||
/* do_pickup (seg006:1671): стереть предмет с карты (тайл → пол) и запомнить
|
||||
@@ -1222,22 +1242,38 @@ void pop_proc_get_object(void) __banked
|
||||
case 1: /* здоровье: +1 HP, красная вспышка */
|
||||
if (hitp_curr != hitp_max) {
|
||||
hitp_delta = 1;
|
||||
pop_flash_red = 1;
|
||||
pop_flash_color = POP_FLASH_RED;
|
||||
pop_flash_time = 2;
|
||||
}
|
||||
break;
|
||||
case 2: /* большая склянка: +1 к ПОТОЛКУ HP */
|
||||
pop_kid_add_life();
|
||||
pop_flash_red = 1;
|
||||
pop_flash_color = POP_FLASH_RED;
|
||||
pop_flash_time = 4; /* seg006:1878 — вспышка длиннее */
|
||||
break;
|
||||
case 5: /* «злое» зелье: −1 HP */
|
||||
hitp_delta = -1;
|
||||
pop_flash_red = 1;
|
||||
pop_flash_time = 2;
|
||||
case 3: /* ПЕРО: медленное падение */
|
||||
/* feather_fall (seg000:15F8): взвести эффект + ЗЕЛЁНАЯ вспышка
|
||||
* на 3 кадра (flash_color = 2). Оригинал здесь же глушит звуки
|
||||
* и играет sound_39_low_weight — по нему же меряет срок; у нас
|
||||
* срок задан кадрами (POP_FEATHER_FRAMES). */
|
||||
pop_feather = POP_FEATHER_FRAMES;
|
||||
pop_flash_color = POP_FLASH_GREEN;
|
||||
pop_flash_time = 3;
|
||||
break;
|
||||
default: /* 3 перо / 4 переворот / 6 открыть —
|
||||
* TODO вместе с их уровнями (7/9) */
|
||||
case 5: /* «злое» зелье (синее): −1 HP */
|
||||
/* seg006:1892. Своей вспышки у этой ветки НЕТ (в отличие от
|
||||
* лечебных): красный кадр даёт общий механизм «Кид ранен»
|
||||
* (flash_if_hurt, seg003:0AFC) — у нас pop_kid_hurt. Раньше мы
|
||||
* ставили вспышку руками, то есть красили экран дважды.
|
||||
* Спецсобытие уровня зелий: там синие склянки забирают ПОЛОВИНУ
|
||||
* запаса, а не одну единицу. */
|
||||
if (pop_current_level == POP_POTIONS_LEVEL)
|
||||
hitp_delta = (int8_t)(-((hitp_max + 1) >> 1));
|
||||
else
|
||||
hitp_delta = -1;
|
||||
break;
|
||||
default: /* 4 переворот / 6 открыть — TODO
|
||||
* вместе с их уровнями (9/13) */
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2556,6 +2592,10 @@ static void check_chomped_guard(void)
|
||||
static void kid_phys(void)
|
||||
{
|
||||
if (grab_timer) grab_timer--;
|
||||
/* do_timers (seg003:0517): срок действия пера тикает раз в игровой кадр.
|
||||
* Место — у Кида, а не у стража: эффект в оригинале снимается общим
|
||||
* таймером главного цикла, а у нас единственный кадровый тик Кида. */
|
||||
if (pop_feather) pop_feather--;
|
||||
fall_accel();
|
||||
fall_speed();
|
||||
determine_col();
|
||||
|
||||
@@ -84,8 +84,17 @@ extern uint8_t pop_item_taken;
|
||||
/* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока
|
||||
* НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */
|
||||
extern uint8_t pop_flash_time;
|
||||
/* Цвет вспышки: 0 = ярко-жёлтая (подобран меч), 1 = красная (зелье). */
|
||||
extern uint8_t pop_flash_red;
|
||||
/* Цвет вспышки (порт flash_color, seg006 proc_get_object / seg000 feather_fall):
|
||||
* жёлтая — подобран меч, красная — зелья HP, зелёная — перо. */
|
||||
#define POP_FLASH_YELLOW 0
|
||||
#define POP_FLASH_RED 1
|
||||
#define POP_FLASH_GREEN 2
|
||||
extern uint8_t pop_flash_color;
|
||||
|
||||
/* is_feather_fall: кадров, пока действует зелье медленного падения. Читают
|
||||
* play_seq (опкод JMP_IF_FEATHER — ветки stepfloat/bumpfloat в seqtbl) и
|
||||
* приложение (сброс на старте уровня, seg003:189). */
|
||||
extern uint8_t pop_feather;
|
||||
uint8_t pop_get_item_action(void) __banked; /* 0 нет / 1 присесть / 2 поднять меч / 3 пить */
|
||||
void pop_proc_get_object(void) __banked; /* эффект — из play_seq (опкод GET_ITEM) */
|
||||
|
||||
|
||||
@@ -646,11 +646,25 @@ void pop_potion_draw(int row, int col, uint8_t modif) __banked
|
||||
int pot_size = (type >= 2 && type < 5) ? 1 : 0;
|
||||
int x = (POP_COL_XH[col] + 3) * 8 + 1;
|
||||
int yb = 63 * row + 62 - (pot_size << 2) - 14;
|
||||
uint8_t bubb;
|
||||
if (type == 0) return;
|
||||
bubb = POTION_FRAM_BUBB[modif & 7];
|
||||
/* Цвет пузырька по ТИПУ зелья (draw_tile_anim, seg008:652): 3/4 (перо /
|
||||
* переворот) — зелёный, 5/6 (вред / открыть ворота) — синий, остальные
|
||||
* (1 лечение, 2 склянка жизни) — красный. У нас цвет запечён в спрайт
|
||||
* при упаковке (mono-блиттера с параметром цвета нет), поэтому «цвет» =
|
||||
* другой набор кадров: id 30..36 / 40..46 вместо 16..22 (pop_pack_bg.py).
|
||||
* Кадр 0 («пузырька нет») общий — его подменять нечем и не нужно. */
|
||||
if (bubb) {
|
||||
if (type == 3 || type == 4)
|
||||
bubb = (uint8_t)(POP_POT_BUBB_GREEN + (bubb - POTION_FRAM_BUBB[1]));
|
||||
else if (type == 5 || type == 6)
|
||||
bubb = (uint8_t)(POP_POT_BUBB_BLUE + (bubb - POTION_FRAM_BUBB[1]));
|
||||
}
|
||||
pop_heal_off(x - 1, yb - 10, 8, 12);
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
pop_pot_b(POT_MASK_ID, x, yb); /* маска (под пузырьком) */
|
||||
pop_pot_b(POTION_FRAM_BUBB[modif & 7], x, yb); /* кадр пузырька */
|
||||
pop_pot_b(bubb, x, yb); /* кадр пузырька */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
|
||||
|
||||
@@ -79,4 +79,20 @@
|
||||
#define POP_DBG_SHADOW_NOWAIT 0
|
||||
#endif
|
||||
|
||||
/* ---- Зелье МЕДЛЕННОГО ПАДЕНИЯ (SDLPoP: FEATHER_FALL_LENGTH) ---------- *
|
||||
* В оригинале длительность эффекта задана НЕ таймером, а звуком: эффект
|
||||
* держится, пока играет sound_39_low_weight, и снимается принудительно на
|
||||
* 225-м тике (do_timers, seg003:0517). Звука у нас нет, поэтому берём тот
|
||||
* же порог напрямую — 225 ИГРОВЫХ КАДРОВ (у оригинала тик do_timers = кадр
|
||||
* игрового цикла; 225 кадров при его 12 fps и есть FEATHER_FALL_LENGTH =
|
||||
* 18,75 с из фикса SDLPoP). По ВРЕМЕНИ у нас выйдет короче ровно настолько,
|
||||
* насколько наша игра быстрее оригинала — это общий вопрос темпа
|
||||
* (TASKS_OPEN.md, L1-SPEED), а не свойство зелья. */
|
||||
#define POP_FEATHER_FRAMES 225
|
||||
|
||||
/* ---- Уровень зелий (SDLPoP: «potions level», seg006:1894) ------------ *
|
||||
* На нём синие склянки «вреда» забирают ПОЛОВИНУ запаса HP, а не единицу.
|
||||
* В оригинале это условие `current_level == 15` прямо в proc_get_object. */
|
||||
#define POP_POTIONS_LEVEL 15
|
||||
|
||||
#endif /* POP_TUNE_H */
|
||||
|
||||
@@ -443,6 +443,7 @@ static uint8_t pop_checkpoint;
|
||||
static void pop_start_level(void)
|
||||
{
|
||||
pop_leveldoor_open = 0; /* seg003:97 — дверь прошлого уровня не в счёт */
|
||||
pop_feather = 0; /* seg003:189 — перо не переживает старт уровня */
|
||||
uint8_t entry_pose = pop_level_entry_pose(pop_current_level);
|
||||
uint8_t start_room = pop_level_start_room();
|
||||
uint8_t pos = pop_level_start_pos();
|
||||
@@ -976,12 +977,13 @@ int main(void)
|
||||
* Приоритет как в оригинале: активный flash_time > кадр урона
|
||||
* (hitp_delta<0 -> ярко-красный, ровно на один кадр). */
|
||||
if (pop_flash_time) {
|
||||
/* Цвет вспышки: меч — ярко-жёлтый, зелье — красный (flash_color
|
||||
* оригинала; нам хватает этих двух). */
|
||||
if (pop_flash_red) flash_bg(255, 0, 0); /* color_4_red */
|
||||
else flash_bg(255, 255, 85); /* color_14_brightyellow */
|
||||
/* Цвет вспышки (flash_color оригинала): меч — ярко-жёлтый, зелья
|
||||
* HP — красный, перо (медленное падение) — зелёный. */
|
||||
if (pop_flash_color == POP_FLASH_RED) flash_bg(255, 0, 0);
|
||||
else if (pop_flash_color == POP_FLASH_GREEN) flash_bg(85, 255, 85);
|
||||
else flash_bg(255, 255, 85);
|
||||
pop_flash_time--;
|
||||
if (!pop_flash_time) pop_flash_red = 0;
|
||||
if (!pop_flash_time) pop_flash_color = POP_FLASH_YELLOW;
|
||||
flash_on = 1;
|
||||
} else if (pop_kid_hurt) {
|
||||
flash_bg(224, 0, 48); /* color_12: игра правит его на */
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "stubs.h"
|
||||
#include "pop_kid.h"
|
||||
#include "pop_map.h"
|
||||
#include "pop_guard.h" /* pop_do_delta_hp — урон сводится дельтой */
|
||||
|
||||
/* Коды тайлов (pop_map): 0 пусто, 1 пол, 3 колонна, 11 loose, 20 стена. */
|
||||
#define E 0
|
||||
@@ -231,6 +232,56 @@ TC_TEST(phys_running_jump_over_3tile_gap)
|
||||
SC_CHECK(golden_rjump_gap3, "rjump_gap3");
|
||||
}
|
||||
|
||||
/* Падение с ДВУХ рядов: пол только внизу, Кид стартует на кромке ряда 0.
|
||||
* В обычном падении это −1 HP (land, seg005:173), под пером — ни урона, ни
|
||||
* разгона. */
|
||||
static const uint8_t room_drop2[30] = {
|
||||
W, F, E, E, E, E, E, E, E, E,
|
||||
W, E, E, E, E, E, E, E, E, E,
|
||||
W, F, F, F, F, F, F, F, F, W,
|
||||
};
|
||||
|
||||
/* Максимальная скорость падения за прогон и итоговое HP — через сцену не
|
||||
* видно (в трассу пишется поза), поэтому меряем прямо по Kid. */
|
||||
static uint8_t drop2_max_fall_y(uint8_t frames)
|
||||
{
|
||||
uint8_t i, mx = 0;
|
||||
for (i = 0; i < frames; i++) {
|
||||
sc_trace_clear();
|
||||
sc_run(SC_R, 1);
|
||||
if ((uint8_t)Kid.fall_y > mx) mx = (uint8_t)Kid.fall_y;
|
||||
}
|
||||
return mx;
|
||||
}
|
||||
|
||||
TC_TEST(phys_feather_fall_is_slow_and_harmless)
|
||||
{
|
||||
uint8_t mx_normal, mx_feather, hp_normal, hp_feather;
|
||||
|
||||
/* (1) как обычно: разгон до FALL_MAX и −1 HP за два ряда */
|
||||
sc_room(room_drop2, 1);
|
||||
sc_kid_at(1, 0, 0);
|
||||
pop_feather = 0;
|
||||
mx_normal = drop2_max_fall_y(40);
|
||||
pop_do_delta_hp(); /* урон копится дельтой, сводит её цикл */
|
||||
hp_normal = hitp_curr;
|
||||
|
||||
/* (2) под зельем медленного падения (seg006:057C: ускорение 1, потолок 4;
|
||||
* seqtbl уводит приземление в ветку stepfloat — без урона) */
|
||||
sc_room(room_drop2, 1);
|
||||
sc_kid_at(1, 0, 0);
|
||||
pop_feather = 200;
|
||||
mx_feather = drop2_max_fall_y(40);
|
||||
pop_do_delta_hp();
|
||||
hp_feather = hitp_curr;
|
||||
pop_feather = 0;
|
||||
|
||||
TC_TRUE(mx_normal > 4); /* обычное падение разгоняется */
|
||||
TC_TRUE(mx_feather <= 4); /* под пером потолок скорости — 4 */
|
||||
TC_TRUE(hp_normal < 3); /* два ряда обычно стоят HP */
|
||||
TC_EQ(hp_feather, 3); /* под пером — целым */
|
||||
}
|
||||
|
||||
/* ---- точка входа ----------------------------------------------------- */
|
||||
|
||||
void main(void)
|
||||
@@ -245,4 +296,5 @@ void main(void)
|
||||
TC_RUN(phys_run_off_ledge);
|
||||
TC_RUN(phys_loose_floor_breaks);
|
||||
TC_RUN(phys_running_jump_over_3tile_gap);
|
||||
TC_RUN(phys_feather_fall_is_slow_and_harmless);
|
||||
}
|
||||
|
||||
@@ -144,6 +144,15 @@ VGA16 = [(0,0,0),(0,0,42),(0,42,0),(0,42,42),(42,0,0),(42,0,42),(48,38,20),
|
||||
(63,21,63),(63,63,21),(63,63,63)] # 6-битные каналы, как в .pal
|
||||
POT_MASK_COLOR = VGA16_PAL_BASE + 0 # чёрный (blitters_40h_mono без цвета)
|
||||
POT_BUBBLE_COLOR = VGA16_PAL_BASE + 12 # ярко-красный (color 12: «лечение»)
|
||||
# Цвет пузырька зависит от ТИПА зелья (draw_tile_anim, seg008:652): типы 3/4
|
||||
# (перо / переворот) — зелёный, 5/6 (вред / открыть) — синий, остальные —
|
||||
# красный. У нас спрайт красится при УПАКОВКЕ (mono-блиттера с параметром
|
||||
# цвета в libbgi нет), поэтому каждый цвет — свой набор кадров: те же семь
|
||||
# силуэтов под сдвинутыми id. Синий появится вместе с зельями 5/6.
|
||||
POT_BUBBLE_GREEN_COLOR = VGA16_PAL_BASE + 10 # color 10 (seg008:663)
|
||||
POT_BUBBLE_GREEN_BASE = 30 # id 30..36 = кадры 16..22
|
||||
POT_BUBBLE_BLUE_COLOR = VGA16_PAL_BASE + 9 # color 9 (seg008:658)
|
||||
POT_BUBBLE_BLUE_BASE = 40 # id 40..46 = кадры 16..22
|
||||
CHOMPER_BLOOD_COLOR = VGA16_PAL_BASE + 12 # тот же mono-цвет 12 (seg008:678)
|
||||
|
||||
# Кладка ДВОРЦА (wall_pattern, seg008:1928, ветка palace): тело стены —
|
||||
@@ -475,6 +484,13 @@ def write_header(out_path, env_pages):
|
||||
"#endif /* POP_BG_ATLAS_NAMES */",
|
||||
'#define POP_POT_ATL "pop_pot.atl" /* chtab_1: зелья, от набора не зависит */',
|
||||
"#define POP_PAL_POT 0x%02X" % POT_PAL_BASE,
|
||||
"/* Пузырёк зелья: красный набор = id 16..22 (кадры оригинала),",
|
||||
" зелёный (перо/переворот) и синий (вред/открыть) = те же кадры",
|
||||
" под id %d..%d и %d..%d (draw_tile_anim, seg008:652). */"
|
||||
% (POT_BUBBLE_GREEN_BASE, POT_BUBBLE_GREEN_BASE + len(POT_BUBBLE_IDS) - 1,
|
||||
POT_BUBBLE_BLUE_BASE, POT_BUBBLE_BLUE_BASE + len(POT_BUBBLE_IDS) - 1),
|
||||
"#define POP_POT_BUBB_GREEN %d" % POT_BUBBLE_GREEN_BASE,
|
||||
"#define POP_POT_BUBB_BLUE %d" % POT_BUBBLE_BLUE_BASE,
|
||||
'#define POP_BG_PAL "pop_bg.pal"',
|
||||
"",
|
||||
"#endif",
|
||||
@@ -578,6 +594,11 @@ def main():
|
||||
pot_entries[id] = load_potion(id, opaque=True)
|
||||
else:
|
||||
pot_entries[id] = load_potion(id)
|
||||
# Наборы пузырьков ДРУГИХ цветов: те же кадры другим mono-цветом.
|
||||
# зелёный — типы 3/4 (перо, переворот), синий — типы 5/6 (вред, открыть).
|
||||
for i, id in enumerate(POT_BUBBLE_IDS):
|
||||
pot_entries[POT_BUBBLE_GREEN_BASE + i] = load_potion(id, POT_BUBBLE_GREEN_COLOR)
|
||||
pot_entries[POT_BUBBLE_BLUE_BASE + i] = load_potion(id, POT_BUBBLE_BLUE_COLOR)
|
||||
# chtab_1 (пламя/меч/зелья) от тайлсета НЕ зависит — пакуем один раз.
|
||||
cnt, sz = pack_atlas(os.path.join(OUT_DIR, "pop_pot.atl"), pot_entries)
|
||||
total_bytes += sz
|
||||
|
||||
Reference in New Issue
Block a user