Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1214785a56 | |||
| 63a25530f5 | |||
| fb495ace42 | |||
| e1846369ad | |||
| 1c91c5f92a | |||
| 6769b6a01b | |||
| 5f5eefc9fc | |||
| 517d225091 | |||
| fc69316c2c | |||
| 08ebf09d4d |
@@ -36,4 +36,6 @@ static const char *const kid_atl[KID_PAGES] = {
|
||||
"kid27.atl",
|
||||
};
|
||||
#define KID_PAL_FILE "kid.pal"
|
||||
#define KID_SWORD_ATL "sword.atl" /* chtab_0: меч в руке */
|
||||
#define KID_SWORD_ID0 20 /* индекс в атласе = sword_tbl.id - ID0 */
|
||||
#endif
|
||||
|
||||
@@ -21,7 +21,7 @@ BG_DATA := $(BG_DIR)/pop_env0.atl $(BG_DIR)/pop_env1.atl $(BG_DIR)/pop_env2.
|
||||
# не рисуются (kid_draw пропускает незагруженную страницу). Палитра —
|
||||
# ЕДИНАЯ игровая (env+wall+kid).
|
||||
KID_PAGES := $(foreach n,0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27,$(KID_DIR)/kid$(n).atl)
|
||||
KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal
|
||||
KID_DATA := $(KID_PAGES) $(KID_DIR)/kid.pal $(KID_DIR)/sword.atl
|
||||
|
||||
EXTRA_DATA := $(BG_DATA) $(KID_DATA) $(LVL_DATA)
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
|
||||
@@ -132,6 +132,16 @@ openness рисует `bar(BLACK)` по всему col0 + **полный `draw_t
|
||||
`pop_button_redraw` — wipe своей ячейки + правой грани (дальний угол в
|
||||
0,3), низ строго yb+64 (не залезать в стену ряда 1).
|
||||
|
||||
## НЕ БАГИ (кривая картинка, но совпадает с оригиналом — НЕ чинить)
|
||||
|
||||
- **Голова стоящего Кида поверх падающей на него loose-плиты.** Комната 12:
|
||||
зацеп не удался, Кид остался стоять, сбитая плита падает прямо на него —
|
||||
голова рисуется ПОВЕРХ плиты. Сверено покадрово с SDLPoP v1.24 (2026-07-29):
|
||||
там ровно то же самое. Артефакт оригинального движка (порядок midtable), а
|
||||
не наш баг; «починка» увела бы от эталона. Отличать от соседних случаев,
|
||||
которые БЫЛИ нашими багами и исправлены: вис/подтягивание на кромке плиты и
|
||||
падение вместе с плитой — там плита обязана быть поверх Кида.
|
||||
|
||||
## Заметки
|
||||
|
||||
- Тестовые клавиши осторожного шага: **J** = шаг влево, **L** = шаг вправо
|
||||
@@ -406,29 +416,48 @@ Kid ставится на первый пол, `pop_trob_reset()` возвращ
|
||||
исходное. Номер комнаты — полосками в верхнем борте: слева десятки, справа
|
||||
единицы (`||` `||||` = 24). Убирается закомментированием одной строки.
|
||||
|
||||
|
||||
**Связность уровня 1 (проверено по `res2001.bin`, links @1952).** Обход графа
|
||||
от стартовой комнаты 1 показывает: комнаты **13, 18, 24 НЕДОСТИЖИМЫ** в
|
||||
обычной игре. У всех трёх ссылки наружу есть, а на них не ссылается никто
|
||||
(24: `L→9`, но у 9 `R=0`; 13 и 18 связаны только друг с другом). Признак
|
||||
«комнату выкинули из компоновки, связи не почистили» — несимметричные ссылки
|
||||
ровно у этих трёх, у остальных 21 симметрия полная:
|
||||
|
||||
```
|
||||
13 L→22, у 22 R=16 | 18 L→15, у 15 R=12 | 24 L→9, у 9 R=0
|
||||
13 R→16, у 16 L=22 | 18 R→12, у 12 L=15
|
||||
| 18 D→19, у 19 U=12
|
||||
```
|
||||
|
||||
Следствие для обхода: в 13/18/24 возможен «мусор в шве» — наш рендер кромки
|
||||
читает крайнюю колонку соседа ПО ССЫЛКЕ, а сосед этих комнат соседом себя не
|
||||
считает. Это свойство данных, а не наш баг. Приоритет багов в этих трёх
|
||||
комнатах — низкий (в игре их не видно).
|
||||
|
||||
| комната | статус | что не так |
|
||||
|---------|--------|------------|
|
||||
| 1 | | |
|
||||
| 1 | пофикшено | падающая плита (2,6): правая грань видна через пол (2,7) и перекрывает его переднюю грань — `mob_render` брал ряд соседа из `m->row` (счётчик, уже ушедший на ряд вперёд), а не из координаты |
|
||||
| 2 | | |
|
||||
| 3 | | |
|
||||
| 4 | | |
|
||||
| 5 | | |
|
||||
| 5 | пофикшено | прыжок в решётку: Kid оставался стоять на 6 px ВЫШЕ пола и без приземления-приседания — от `bumped()` (seg004) был портирован только хвост (`seq_47`), не хватало `bumped_floor` (прижатие Y к полу + `seq_46_hardbump` на кадрах прыжка 24/25/40..42/102..106) и `bumped_fall` |
|
||||
| 6 | | |
|
||||
| 7 | | |
|
||||
| 8 | | |
|
||||
| 9 | | |
|
||||
| 9 | сделано | дверь уровня (1,3)-(1,4) рисовалась чёрным проёмом: не был портирован `draw_leveldoor` (seg008:1D29) — створка (слайсы 33 + верх 34), лестница за ней (99/144) и анимация подъёма по кнопке (`animate_leveldoor`, seg007:05F1, modif 0→43). Спрайты 33/34/99/144 добавлены в атлас явным набором (render_room.py дверь не рисует) |
|
||||
| 10 | | |
|
||||
| 11 | | |
|
||||
| 12 | | |
|
||||
| 13 | | |
|
||||
| 12 | пофикшено | вис/подтягивание на кромке loose-плиты: плита рисовалась ПОД Кидом. Не хватало двух кусков `draw_tile`: (а) `draw_loose` кладёт нижнюю грань плиты И в foretable (поверх персонажа), (б) `draw_tile_base` подставляет верх плиты из `loose_fram_left`, а в нашем midtable-оверлее стоял голый `base_id` (у loose он 0). Голова Кида поверх падающей на него плиты — см. «НЕ БАГИ» |
|
||||
| 13 | недостижима в игре | |
|
||||
| 14 | | |
|
||||
| 15 | | |
|
||||
| 15 | сделано | меч (2,2) не рисовался: тайл 22 в draw_tile_anim не был портирован. Добавлены отрисовка предмета (chtab_1 id 10/11 на draw_main_y−3), подъём по Shift (check_get_item/get_item/do_pickup: присед → seq_91 pickupsword → меч исчезает с пола) и статус `pop_have_sword` |
|
||||
| 16 | | |
|
||||
| 17 | | |
|
||||
| 18 | | |
|
||||
| 18 | недостижима в игре | |
|
||||
| 19 | | |
|
||||
| 20 | | |
|
||||
| 21 | | |
|
||||
| 22 | | |
|
||||
| 23 | | |
|
||||
| 24 | | |
|
||||
| 24 | недостижима в игре | |
|
||||
|
||||
|
After Width: | Height: | Size: 15 KiB |
@@ -464,6 +464,32 @@ static uint8_t get_loose_frame(uint8_t m)
|
||||
return m;
|
||||
}
|
||||
|
||||
/* draw_leveldoor (seg008:1D29) — дверь уровня. Рисуется при обработке
|
||||
* ПРАВОЙ половины (tile_left = 0x10), все куски со сдвигом +8px (xh+1):
|
||||
* 99 низ лестницы (всегда),
|
||||
* 144 марш лестницы за створкой (когда створка приподнята, modif != 0),
|
||||
* 33 слайс створки — повторяется вниз с шагом 4px, пока не дойдёт до
|
||||
* y = ybottom − modif: modif 0 = закрыто (створка на всю высоту),
|
||||
* 43 = открыто (остаётся только верхняя кромка),
|
||||
* 34 верх коробки двери.
|
||||
* Куски непрозрачные (blitters_0_no_transp) — как в оригинале. */
|
||||
static void draw_leveldoor(uint8_t modif, int xh, int dmy)
|
||||
{
|
||||
int x = (xh + 1) * 8;
|
||||
int ybottom = dmy - 13;
|
||||
int ylo = ybottom - (int)(modif & 3) - 48; /* leveldoor_ybottom */
|
||||
int y = ybottom - (int)modif;
|
||||
env_b(99, x, ybottom);
|
||||
if (modif)
|
||||
env_b(144, x, ybottom - 4); /* лестница (комната ≠ стартовой) */
|
||||
for (;;) {
|
||||
env_b(33, x, ylo);
|
||||
if (y > ylo) ylo += 4;
|
||||
else break;
|
||||
}
|
||||
env_b(34, x, dmy - 64);
|
||||
}
|
||||
|
||||
/* ---- draw_tile (seg008.c:155) — одна клетка ------------------------ */
|
||||
static void draw_tile(int row, int col)
|
||||
{
|
||||
@@ -539,6 +565,8 @@ static void draw_tile(int row, int col)
|
||||
uint8_t sf = (lmod & 0x80) ? 5 : lmod;
|
||||
if (sf < 10 && SPIKES_FRAM_RIGHT[sf])
|
||||
env_b(SPIKES_FRAM_RIGHT[sf], x, dmy - 7);
|
||||
} else if (lcode == 16) { /* дверь уровня */
|
||||
draw_leveldoor(lmod, xh, dmy);
|
||||
}
|
||||
/* Пламя факела (seg008:560, chtab_1 id modifier+1) в фон НЕ запекаем:
|
||||
* оно анимируется каждый кадр, а heal вернул бы запечённый кадр из-под
|
||||
@@ -583,6 +611,12 @@ static void draw_tile(int row, int col)
|
||||
if (sf > 0 && sf < 10 && SPIKES_FRAM_LEFT[sf])
|
||||
env_b(SPIKES_FRAM_LEFT[sf], x, dmy - 2);
|
||||
}
|
||||
/* Меч-предмет на полу (тот же draw_tile_anim, case tiles_22_sword):
|
||||
* chtab_1 (наш pot-атлас), id = (modif==1)+10, низ на draw_main_y-3.
|
||||
* В оригинале это midtable (рисуется вместе с объектами) — у нас статикой
|
||||
* в фоне: пока меч лежит, он не анимируется, а Kid и так поверх фона. */
|
||||
if (row >= 0 && code == 22)
|
||||
pot_b((uint8_t)((tile_mod(row, col) == 1) + 10), x, dmy - 3);
|
||||
|
||||
/* --- draw_tile_fore (seg008.c:690) --- */
|
||||
if (code == 20) {
|
||||
@@ -763,6 +797,17 @@ void pop_floor_bake(int row, int col)
|
||||
|
||||
void pop_button_redraw(int row, int col) { pop_floor_bake(row, col); }
|
||||
|
||||
/* Перерисовать ПРАВУЮ половину двери уровня (в её ячейке рисуется створка,
|
||||
* см. draw_leveldoor) на back-странице С ЗАПЕЧКОЙ в ОЗУ-копию: створка едет,
|
||||
* а heal обязан возвращать её ТЕКУЩЕЕ положение, а не запечённое закрытое.
|
||||
* Куски двери непрозрачные — wipe не нужен. */
|
||||
void pop_leveldoor_redraw(int row, int col)
|
||||
{
|
||||
gfx_set_bank(GFX_BANK_TRANSPARENT);
|
||||
draw_tile(row, col);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
}
|
||||
|
||||
/* Запечь ПУСТОТУ на месте упавшего loose (текущая страница). Порт
|
||||
* redraw_at_cur_mob (seg007:132C): перерисовать сам тайл (теперь empty) И
|
||||
* тайл СПРАВА (его левая грань зависит от нашего кода: пока был loose —
|
||||
@@ -967,9 +1012,24 @@ static void mob_render(mob_t *m, uint8_t pg)
|
||||
* сосед-тайл В ТОМ ЖЕ КАДРЕ поверх куска (иначе край торчит на полу
|
||||
* до heal'а следующего кадра — виден как хвост во время падения). */
|
||||
if (m->col + 1 < 10) {
|
||||
if (m->row < 0) g_clip_top = POP_YOFF; /* полоса у потолка — только срез */
|
||||
draw_tile(m->row, m->col + 1);
|
||||
/* Ряд соседа берём ИЗ КООРДИНАТЫ куска (y_to_row_mod4(ypos)), а НЕ из
|
||||
* m->row: последний — логический счётчик «сквозь какой ряд летим», и
|
||||
* mob_down_a_row уводит его на ряд ВПЕРЁД (для плиты нижнего ряда —
|
||||
* сразу в 3, которого нет). Тогда пол соседа не перерисовывался
|
||||
* вообще, и правый кусок 42 оставался ПОВЕРХ него: грань плиты видна
|
||||
* через пол + перекрыта передняя грань пола.
|
||||
* Плюс ВТОРОЙ тайл — под ВЕРХОМ куска (ypos-18), пока кусок висит на
|
||||
* границе рядов. Порт draw_mob (seg007:13E5). */
|
||||
int8_t r = (int8_t)(((m->y + 60) / 63) % 4 - 1);
|
||||
int8_t rt = (int8_t)(((m->y - 18 + 60) / 63) % 4 - 1);
|
||||
if (r < 0) g_clip_top = POP_YOFF; /* полоса у потолка — только срез */
|
||||
draw_tile(r, m->col + 1);
|
||||
g_clip_top = 0;
|
||||
if (rt != r) {
|
||||
if (rt < 0) g_clip_top = POP_YOFF;
|
||||
draw_tile(rt, m->col + 1);
|
||||
g_clip_top = 0;
|
||||
}
|
||||
}
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* клип СВОЕГО выхода за поле (верхний борт / статус-полоса) — по ВСЕЙ ширине */
|
||||
@@ -1122,6 +1182,17 @@ static void ceil_over_kid_tile(int col)
|
||||
|
||||
static void fore_tile(int row, int col) /* передний слой одного тайла */
|
||||
{
|
||||
/* draw_loose (seg008:0A38) — единственный кусок ТАЙЛА, который оригинал
|
||||
* кладёт В ОБЕ таблицы БЕЗУСЛОВНО: нижняя грань loose-плиты идёт и в
|
||||
* backtable, и в foretable. Значит передняя грань плиты рисуется ПОВЕРХ
|
||||
* персонажа ВСЕГДА, а не только у потолка (ceil_over_kid_tile). Верх
|
||||
* плиты (draw_tile_base) остаётся в backtable — поэтому голова и руки
|
||||
* висящего/подтягивающегося Kid видны НАД плитой, а тело уходит ЗА неё.
|
||||
* Без этого Kid лез на плиту «с переднего края» (комната 12: вис на
|
||||
* кромке loose над дырой от соседней упавшей плиты). */
|
||||
if (row >= 0 && tile_code_drawn(row, col) == 11)
|
||||
env_b(LOOSE_FRAM_BOTTOM[get_loose_frame(pop_loose_modif[row * 10 + col])],
|
||||
COL_XH[col] * 8, 63 * row + 65);
|
||||
/* draw_tile_fore (seg008:690) — ТОЛЬКО add_foretable-часть. Раньше здесь
|
||||
* рисовалась ещё и передняя кромка пола (bottom_id) «чтобы перекрывала
|
||||
* Kid», но в оригинале bottom идёт через draw_tile_bottom в backtable
|
||||
@@ -1185,8 +1256,20 @@ static void overlay_mid_tile(int row, int col)
|
||||
ov_mark(x, 63 * row + 2, 64, 64); /* тайл + правый сосед (грани) */
|
||||
if (code != 20 && lcode == 1) /* draw_tile_right: только 42 */
|
||||
env_b(42, x, tile_table[1].right_y + dmy);
|
||||
if (t->base_id) /* draw_tile_base (loose base = 0) */
|
||||
env_b(t->base_id, x, t->base_y + dmy);
|
||||
{ /* draw_tile_base (seg008:0A8E) — ЦЕЛИКОМ, вместе с подстановками id:
|
||||
* у loose верх плиты берётся из loose_fram_left (в tile_table base_id=0),
|
||||
* у opener'а без пола слева — 148. Раньше здесь стоял голый base_id, и
|
||||
* верх loose-плиты в оверлей не попадал: передняя грань ложилась поверх
|
||||
* Kid (foretable), а сама плита — нет, и подтягивающийся Kid рисовался
|
||||
* поверх её верхней плоскости (комната 12). */
|
||||
uint8_t base_id = t->base_id;
|
||||
if (code == 11 && row >= 0)
|
||||
base_id = LOOSE_FRAM_LEFT[get_loose_frame(pop_loose_modif[row * 10 + col])];
|
||||
else if (code == 0x0F && lcode == 0)
|
||||
base_id = 148;
|
||||
if (base_id)
|
||||
env_b(base_id, x, t->base_y + dmy);
|
||||
}
|
||||
if (code == 2) { /* draw_tile_anim: свои пики */
|
||||
uint8_t sm = tile_mod(row, col);
|
||||
uint8_t sf = (sm & 0x80) ? 5 : sm;
|
||||
|
||||
@@ -102,6 +102,10 @@ void pop_room_redraw_seam_left(void);
|
||||
void pop_floor_bake(int row, int col);
|
||||
void pop_button_redraw(int row, int col); /* = pop_floor_bake (кнопка) */
|
||||
|
||||
/* Дверь уровня: перерисовать её ПРАВУЮ половину (row, col = правая) с
|
||||
* запечкой — створка анимируется (pop_trob animate_leveldoor). */
|
||||
void pop_leveldoor_redraw(int row, int col);
|
||||
|
||||
/* Падающий кусок loose (mob, seg007 move_loose): спавн при падении тайла
|
||||
* (row,col) и покадровое продвижение+отрисовка на back-странице. tick
|
||||
* зовётся каждый кадр (внутри pop_loose_tick); клип низа делает clip. */
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#define SEQ_49_STAND_UP_FROM_CROUCH 49
|
||||
#define SEQ_50_CROUCH 50
|
||||
#define SEQ_79_CROUCH_HOP 79
|
||||
#define SEQ_91_PICKUP_SWORD 91 /* поднять меч (pickupsword) */
|
||||
#define SEQ_STEP_BASE 28 /* +distance(1..14) = seq 29..42 шаг-N */
|
||||
#define SEQ_39_UNSAFE_STEP 39 /* шаг за кромку (distance 11) */
|
||||
#define SEQ_44_STEP_ON_EDGE 44
|
||||
@@ -104,6 +105,22 @@ static void standing_jump(void)
|
||||
seqtbl_offset_char(SEQ_3_STANDING_JUMP);
|
||||
}
|
||||
|
||||
/* check_get_item (seg005:061F): решение по тайлам принимает pop_map, здесь
|
||||
* только исполнение. Триггер в оригинале — Shift (control_shift2) в стойке
|
||||
* или в приседе: первый раз Kid приседает к предмету, второй — поднимает.
|
||||
* Возврат 1 = управление обработано (caller делает return). */
|
||||
static uint8_t get_item_action(void)
|
||||
{
|
||||
uint8_t a = pop_get_item_action();
|
||||
if (a == 1) { crouch(); return 1; }
|
||||
if (a == 2) {
|
||||
control_shift2 = CONTROL_IGNORE; /* do_pickup: без авто-повтора */
|
||||
seqtbl_offset_char(SEQ_91_PICKUP_SWORD);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void run_jump(void)
|
||||
{
|
||||
/* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
|
||||
@@ -191,6 +208,8 @@ static void safe_step(void)
|
||||
/* ---- control_* обработчики состояний ------------------------------ */
|
||||
static void control_crouched(void)
|
||||
{
|
||||
/* Shift в приседе — поднять предмет (seg005 control_crouched). */
|
||||
if (control_shift2 == CONTROL_HELD && get_item_action()) return;
|
||||
if (control_y != CONTROL_HELD_DOWN) {
|
||||
seqtbl_offset_char(SEQ_49_STAND_UP_FROM_CROUCH);
|
||||
} else if (control_forward == CONTROL_HELD) {
|
||||
@@ -201,6 +220,11 @@ static void control_crouched(void)
|
||||
|
||||
static void control_standing(void)
|
||||
{
|
||||
/* Shift в стойке над предметом — присесть к нему (seg005:0358, первая
|
||||
* строка control_standing). Идёт ДО остальных shift-веток. */
|
||||
if (control_shift2 == CONTROL_HELD && control_shift == CONTROL_HELD &&
|
||||
get_item_action())
|
||||
return;
|
||||
/* Kid charid_0, без меча: остаётся нижняя часть control_standing. */
|
||||
if (control_shift == CONTROL_HELD) {
|
||||
if (control_backward == CONTROL_HELD) back_pressed();
|
||||
|
||||
@@ -30,6 +30,25 @@ static const int16_t y_land[5] = {-8, 55, 118, 181, 244};
|
||||
static atlas_t kidp[KID_MAXPAGES];
|
||||
static uint8_t kid_npages;
|
||||
|
||||
/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): отдельный
|
||||
* спрайт клинка поверх Kid в кадрах «нашёл меч»/убирание в ножны (229..237).
|
||||
* Из sword_tbl взяты только строки 35..42 — те, что адресуют эти кадры (бой
|
||||
* не портирован); индекс в атласе = sword_frame − 35. Смещения таблицы —
|
||||
* в ЭКРАННЫХ пикселях: оригинал применяет их ПОСЛЕ calc_screen_x_coord. */
|
||||
#define SWORD_SF_LO 35
|
||||
#define SWORD_SF_HI 42
|
||||
static const int8_t SWORD_DX[8] = { 7, 14, 14, 14, 28, 28, 21, 14 };
|
||||
static const int8_t SWORD_DY[8] = {-32,-32,-31,-29,-28,-28,-25,-22 };
|
||||
static atlas_t swordp;
|
||||
static uint8_t sword_ok;
|
||||
|
||||
int pop_sword_load(void)
|
||||
{
|
||||
if (atlas_load(&swordp, "sword.atl") != 0) return -1;
|
||||
sword_ok = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pop_kid_load(uint8_t npages)
|
||||
{
|
||||
static const char *const names[KID_MAXPAGES] = {
|
||||
@@ -125,7 +144,10 @@ void play_seq(void)
|
||||
break;
|
||||
case 0xF1: /* END_LEVEL */
|
||||
break;
|
||||
case 0xF3: /* GET_ITEM — потребить 1 байт */
|
||||
case 0xF3: /* GET_ITEM: аргумент 1 — применить эффект предмета
|
||||
* (порт play_seq -> proc_get_object, seg006) */
|
||||
if (SEQ(Kid.curr_seq) == 1)
|
||||
pop_proc_get_object();
|
||||
Kid.curr_seq++;
|
||||
break;
|
||||
case 0xF6: /* DIE nop */
|
||||
@@ -321,5 +343,45 @@ void kid_draw(void)
|
||||
kid_lvalid[dp] = (skip < (int)h);
|
||||
kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */
|
||||
}
|
||||
/* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх
|
||||
* Kid. Прямоугольник heal расширяем объединением — иначе клинок, выйдя
|
||||
* за габарит Kid, оставит след. */
|
||||
if (sword_ok && Kid.frame >= 229 && Kid.frame < 238) {
|
||||
uint8_t sf = (uint8_t)(fr.sword & 0x3F);
|
||||
if (sf >= SWORD_SF_LO && sf <= SWORD_SF_HI) {
|
||||
uint8_t si = (uint8_t)(sf - SWORD_SF_LO);
|
||||
const uint8_t *simg = (const uint8_t *)atlas_image(&swordp, si);
|
||||
uint16_t sw, sh;
|
||||
gfx_w0_map(swordp.page);
|
||||
sw = (uint16_t)(simg[0] | ((uint16_t)simg[1] << 8));
|
||||
sh = (uint16_t)(simg[2] | ((uint16_t)simg[3] << 8));
|
||||
if (sw && sh) {
|
||||
uint8_t sflip = (Kid.direction >= 0);
|
||||
int sbx = obj_x + ((Kid.direction < 0) ? -(int)SWORD_DX[si]
|
||||
: (int)SWORD_DX[si]);
|
||||
int stop = obj_y + (int)SWORD_DY[si] - (int)sh + 1;
|
||||
uint8_t dp = gfx_get_draw_page() & 1;
|
||||
if (sflip) sbx -= (int)sw;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
gfx_blit_cols_part(sbx, stop + POP_YOFF, simg, sflip, 0, 0);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
if (!kid_lvalid[dp]) {
|
||||
kid_lx[dp] = sbx; kid_ly[dp] = stop;
|
||||
kid_lw[dp] = sw; kid_lh[dp] = sh;
|
||||
kid_lvalid[dp] = 1;
|
||||
} else {
|
||||
int x0 = kid_lx[dp], y0 = kid_ly[dp];
|
||||
int x1 = x0 + (int)kid_lw[dp], y1 = y0 + (int)kid_lh[dp];
|
||||
if (sbx < x0) x0 = sbx;
|
||||
if (stop < y0) y0 = stop;
|
||||
if (sbx + (int)sw > x1) x1 = sbx + (int)sw;
|
||||
if (stop + (int)sh > y1) y1 = stop + (int)sh;
|
||||
kid_lx[dp] = x0; kid_ly[dp] = y0;
|
||||
kid_lw[dp] = (uint16_t)(x1 - x0);
|
||||
kid_lh[dp] = (uint16_t)(y1 - y0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ extern int8_t knock;
|
||||
/* Загрузить npages атласов Kid (kid0..kid{npages-1}.atl). ДО initgraph.
|
||||
* 0 — OK, -1 — ошибка. */
|
||||
int pop_kid_load(uint8_t npages);
|
||||
|
||||
/* Атлас chtab_0 «меч в руке» (sword.atl) — спрайт клинка поверх Kid в кадрах
|
||||
* подъёма меча/убирания в ножны (add_sword_to_objtable, seg006:1798).
|
||||
* Грузить ДО initgraph, как и страницы Kid. 0 — OK, -1 — ошибка (тогда
|
||||
* кадры рисуются без клинка). */
|
||||
int pop_sword_load(void);
|
||||
void pop_kid_free(void);
|
||||
|
||||
/* Инициализировать Kid: последовательность seq_id, клетка col/row,
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
#define TILE_DEBRIS 14 /* щебень (упавший loose) */
|
||||
#define TILE_SPIKE 2 /* пики */
|
||||
#define TILE_CLOSER 6 /* кнопка-закрывалка (DROP) */
|
||||
#define TILE_POTION 10 /* зелье */
|
||||
#define TILE_SWORD 22 /* меч на полу */
|
||||
#define TILE_OPENER 15 /* кнопка-открывалка (RAISE, 0x0F) */
|
||||
#define ACT_STAND 0
|
||||
#define ACT_RUNJUMP 1
|
||||
@@ -50,6 +52,8 @@
|
||||
#define SEQ_19_FALL 19 /* stepfall2 (run frames 9/13) */
|
||||
#define SEQ_20_MEDIUM_LAND 20
|
||||
#define SEQ_21_FALL_RUNJUMP 21
|
||||
#define SEQ_45_BUMPFALL 45 /* удар В ВОЗДУХЕ: отскок и падение */
|
||||
#define SEQ_46_HARDBUMP 46 /* удар после прыжка: отскок + ПРИСЕДАНИЕ */
|
||||
#define SEQ_47_BUMP 47 /* удар в стену: dx(-4) отскок + 50-52 + stand */
|
||||
/* K4 — зацеп/вис/подтягивание/спуск */
|
||||
#define SEQ_8_GRAB_STRAIGHT 8 /* прыжок вверх и зацеп (прямо) */
|
||||
@@ -64,6 +68,10 @@
|
||||
#define SEQ_68_CLIMB_DOWN 68 /* спуск через зацеп (не приседание) */
|
||||
#define SEQ_73_CLIMB_UP_TO_GATE 73 /* подъём к закрытой калитке */
|
||||
#define SEQ_51_SPIKED 51 /* напороться на пики (смерть) */
|
||||
#define SEQ_22_CRUSHED 22 /* разбиться при падении (смерть) */
|
||||
|
||||
#define TILE_FLOOR 1
|
||||
#define FRAME_109_CROUCH 109
|
||||
|
||||
/* Кадры анимации для check_spiked (seg006:0658). */
|
||||
#define FRAME_7_RUN 7
|
||||
@@ -420,10 +428,26 @@ static void land(void)
|
||||
/* к краю пола — чуть назад (как оригинал) */
|
||||
if (!tile_is_floor(get_tile_infrontof_char()) && distance_to_edge_weight() < 3)
|
||||
Kid.x = (uint8_t)char_dx_forward(-3);
|
||||
if (Kid.fall_y < 22)
|
||||
seq = SEQ_17_SOFT_LAND; /* 1 этаж — мягко */
|
||||
else
|
||||
seq = SEQ_20_MEDIUM_LAND; /* 2+ этажа — присесть (HP/смерть — позже) */
|
||||
/* Урон от падения (seg005 land): 1 этаж — мягко; 2 этажа — −1 HP и
|
||||
* присед; 3+ — насмерть. pop_kid_hurt = hitp_delta<0 оригинала: по нему
|
||||
* приложение рисует «брызги» И даёт КРАСНУЮ вспышку фона (flash_if_hurt,
|
||||
* seg003:0AFC). */
|
||||
if (Kid.fall_y < 22) {
|
||||
seq = SEQ_17_SOFT_LAND;
|
||||
} else {
|
||||
uint8_t deadly = (uint8_t)(Kid.fall_y >= 33); /* СНЯТЬ до обнуления */
|
||||
pop_kid_hurt = 1;
|
||||
Kid.fall_x = Kid.fall_y = 0;
|
||||
if (deadly || take_hp(1)) { /* 3+ этажа или последнее HP */
|
||||
take_hp(100);
|
||||
kid_set_seq(SEQ_22_CRUSHED);
|
||||
play_seq();
|
||||
determine_col();
|
||||
pop_kid_dead = 1;
|
||||
return;
|
||||
}
|
||||
seq = SEQ_20_MEDIUM_LAND;
|
||||
}
|
||||
Kid.fall_x = Kid.fall_y = 0;
|
||||
kid_set_seq(seq);
|
||||
play_seq();
|
||||
@@ -573,10 +597,33 @@ int pop_edge_distance(void)
|
||||
if (d >= 0) return edge_classify(d);
|
||||
}
|
||||
t = get_tile_infrontof_char();
|
||||
/* Верх двери лицом ВПРАВО — кромка (в оригинале ПЕРВОЙ проверкой, до
|
||||
* wall_type: у doortop wall_type != 0). */
|
||||
if (t == TILE_DOORTOP && Kid.direction >= 0) {
|
||||
g_edge_type = EDGE_CLOSER;
|
||||
return distance_to_edge_weight();
|
||||
}
|
||||
if (wall_type(t)) {
|
||||
d = dist_from_wall_forward(t, g_infrontx);
|
||||
if (d >= 0) return edge_classify(d);
|
||||
}
|
||||
/* ПРОВАЛИВАЮЩИЙСЯ пол впереди — КРОМКА, хотя tile_is_floor считает его
|
||||
* полом. Это и есть механизм «проверки ногой»: осторожный шаг доводит
|
||||
* Kid ровно до границы плиты (distance -> 0), там safe_step играет
|
||||
* seq_44 (testfoot) — Kid щупает плиту ногой, та трясётся (SEQ_KNOCK_DOWN),
|
||||
* и он остаётся на месте; шаг НА плиту — только со второго нажатия
|
||||
* (Kid.repeat). Без этой ветки плита читалась как обычный пол, и Kid
|
||||
* заходил на неё первым же осторожным шагом и падал вместе с ней. */
|
||||
if (t == TILE_LOOSE) {
|
||||
g_edge_type = EDGE_CLOSER;
|
||||
return distance_to_edge_weight();
|
||||
}
|
||||
/* Кнопка-закрывалка/меч/зелье: кромка, пока до границы есть зазор. */
|
||||
if (t == TILE_CLOSER || t == TILE_SWORD || t == TILE_POTION) {
|
||||
d = distance_to_edge_weight();
|
||||
if (d) { g_edge_type = EDGE_CLOSER; return d; }
|
||||
g_edge_type = EDGE_FLOOR; return 11;
|
||||
}
|
||||
if (tile_is_floor(t)) { g_edge_type = EDGE_FLOOR; return 11; }
|
||||
g_edge_type = EDGE_CLOSER; /* яма впереди — до кромки уступа */
|
||||
return distance_to_edge_weight();
|
||||
@@ -736,6 +783,72 @@ uint8_t pop_down_action(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- Предметы: меч (seg005 check_get_item/get_item + seg006 do_pickup/
|
||||
* proc_get_object) --------------------------------------------------- *
|
||||
* pop_have_sword — порт глобала have_sword: «меч у Кида». Боевой режим
|
||||
* (стойка с мечом, бой) НЕ реализован и здесь не нужен: сиквенс подъёма
|
||||
* seq_91 сам заканчивается убиранием меча в ножны (кадры 230..240 sheathe)
|
||||
* и возвратом в обычную стойку — как в оригинале до встречи со стражем. */
|
||||
uint8_t pop_have_sword;
|
||||
uint8_t pop_item_taken; /* 0 = нет; иначе tilepos+1 поднятого */
|
||||
uint8_t pop_flash_time; /* кадров вспышки фона (flash_time) */
|
||||
static int8_t pickup_obj_type; /* -1 = меч (порт pickup_obj_type) */
|
||||
|
||||
/* do_pickup (seg006:1671): стереть предмет с карты (тайл → пол) и запомнить
|
||||
* тип; сам эффект применяет опкод GET_ITEM внутри сиквенса. */
|
||||
static void do_pickup(int8_t obj_type, uint8_t tilepos)
|
||||
{
|
||||
pickup_obj_type = obj_type;
|
||||
g_fg[tilepos] = TILE_FLOOR;
|
||||
pop_item_taken = (uint8_t)(tilepos + 1);
|
||||
}
|
||||
|
||||
/* proc_get_object (seg006:16CB): эффект поднятого предмета. Зовётся из
|
||||
* play_seq по опкоду SEQ_GET_ITEM с аргументом 1 (pop_kid). */
|
||||
void pop_proc_get_object(void)
|
||||
{
|
||||
if (pickup_obj_type == -1) {
|
||||
pop_have_sword = 1; /* в оригинале have_sword = -1 */
|
||||
/* Вспышка фона: flash_color = color_14_brightyellow, flash_time = 8
|
||||
* (seg006 proc_get_object). do_flash = set_bg_attr(0, color) —
|
||||
* оригинал подменяет НУЛЕВУЮ запись палитры, т.е. вспыхивает всё
|
||||
* чёрное поле экрана. У нас так же (gfx_pal_set 0), см. roomtest. */
|
||||
pop_flash_time = 8;
|
||||
}
|
||||
pickup_obj_type = 0; /* зелья (типы 1..6) не портированы */
|
||||
}
|
||||
|
||||
/* check_get_item + get_item (seg005:061F/073E). Возврат: 0 — предмета нет
|
||||
* (управление идёт обычной веткой); 1 — присесть (caller зовёт crouch());
|
||||
* 2 — поднять меч (caller ставит seq_91). */
|
||||
uint8_t pop_get_item_action(void)
|
||||
{
|
||||
uint8_t t;
|
||||
int8_t icol;
|
||||
t = get_tile_at_char();
|
||||
if (t == TILE_POTION || t == TILE_SWORD) { /* стоим НА предмете */
|
||||
if (!tile_is_floor(get_tile_behind_char())) return 0;
|
||||
Kid.x = (uint8_t)char_dx_forward(-TILE_SIZEX); /* отступить на тайл */
|
||||
determine_col();
|
||||
}
|
||||
t = get_tile_infrontof_char(); /* заодно ставит g_infrontx */
|
||||
if (t != TILE_POTION && t != TILE_SWORD) return 0;
|
||||
icol = g_infrontx;
|
||||
if (icol < 0 || icol > 9) return 0; /* предмет за швом — не наш */
|
||||
if (Kid.frame != FRAME_109_CROUCH) {
|
||||
/* Ещё стоим: подровняться к предмету и присесть. */
|
||||
int d = pop_edge_distance(); /* ставит g_edge_type */
|
||||
if (g_edge_type != EDGE_FLOOR)
|
||||
Kid.x = (uint8_t)char_dx_forward((int8_t)d);
|
||||
if (Kid.direction >= 0)
|
||||
Kid.x = (uint8_t)char_dx_forward((int8_t)((t == TILE_POTION) - 2));
|
||||
return 1;
|
||||
}
|
||||
if (t != TILE_SWORD) return 0; /* питьё зелий не портировано */
|
||||
do_pickup(-1, (uint8_t)(Kid.curr_row * 10 + icol));
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* ---- Диспетчер ----------------------------------------------------- */
|
||||
static void check_on_floor(void)
|
||||
{
|
||||
@@ -762,11 +875,58 @@ static void check_action(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* bumped_fall (seg004:0522): удар, когда под персонажем НЕ пол ИЛИ он высоко
|
||||
* над полом — отскок назад и падение (seq_45). В свободном падении сиквенс
|
||||
* не меняем, только гасим горизонтальную составляющую. */
|
||||
static void bumped_fall(void)
|
||||
{
|
||||
Kid.x = (uint8_t)char_dx_forward(-4);
|
||||
if (Kid.action == ACT_FREEFALL) {
|
||||
Kid.fall_x = 0;
|
||||
} else {
|
||||
kid_set_seq(SEQ_45_BUMPFALL); /* (TODO: bumped_sound) */
|
||||
play_seq();
|
||||
}
|
||||
}
|
||||
|
||||
/* bumped_floor (seg004:053B): удар о стену, когда под персонажем ПОЛ.
|
||||
* Портируется целиком — раньше у нас был только хвост (seq_47), из-за чего
|
||||
* терялись ДВЕ вещи: прижатие Y к полу и «жёсткий» вариант удара. Симптом
|
||||
* (комната 5, прыжок в решётку): standjump бампится на кадре 25 — том самом,
|
||||
* где сиквенс уже сделал dy(−6), а парного dy(+6) ещё не было, — и Kid
|
||||
* оставался стоять на 6 пикселей ВЫШЕ пола, без приземления-приседания. */
|
||||
static void bumped_floor(void)
|
||||
{
|
||||
uint8_t frame;
|
||||
/* Оригинал сравнивает БЕЗЗНАКОВО: если персонаж НИЖЕ уровня пола,
|
||||
* разность заворачивается в большое число и тоже даёт «падать». */
|
||||
if ((uint16_t)(y_land[Kid.curr_row + 1] - (int16_t)Kid.y) >= 15) {
|
||||
bumped_fall(); /* высоко над полом — в воздухе */
|
||||
return;
|
||||
}
|
||||
Kid.y = (uint8_t)y_land[Kid.curr_row + 1]; /* ПРИЖАТЬ к полу */
|
||||
if (Kid.fall_y >= 22) { /* влетел быстро — только отжать */
|
||||
Kid.x = (uint8_t)char_dx_forward(-5);
|
||||
return;
|
||||
}
|
||||
Kid.fall_y = 0;
|
||||
if (Kid.action == ACT_BUMPED) return; /* уже в отскоке — не рестартить */
|
||||
frame = Kid.frame;
|
||||
/* Кадры прыжков и начала падения — «жёсткий» удар с приседанием
|
||||
* (24/25 = standjump, 40..42 = runjump, 102..106 = start_fall). */
|
||||
if (frame == 24 || frame == 25 || (frame >= 40 && frame < 43) ||
|
||||
(frame >= 102 && frame < 107))
|
||||
kid_set_seq(SEQ_46_HARDBUMP);
|
||||
else
|
||||
kid_set_seq(SEQ_47_BUMP); /* (TODO: bumped_sound) */
|
||||
play_seq();
|
||||
}
|
||||
|
||||
/* check_bumped (seg004): если передний край ЗАШЁЛ за грань стены — выровнять
|
||||
* к грани И проиграть отскок seq_47_bump (dx−4 назад + кадры 50-52 + stand),
|
||||
* как в оригинале (bumped_floor). Это даёт «шаг назад со звуком» при беге/
|
||||
* шаге в стену вместо падения. Уже в отскоке (action bumped) — только
|
||||
* выравниваем, без рестарта. В падении/висе не трогаем. */
|
||||
* к грани и отдать исход в bumped_floor/bumped_fall (порт bumped, seg004).
|
||||
* Это даёт «шаг назад со звуком» при беге/шаге в стену вместо падения. Уже
|
||||
* в отскоке (action bumped) — только выравниваем, без рестарта сиквенса.
|
||||
* В падении/висе не трогаем. */
|
||||
static void check_bumped(void)
|
||||
{
|
||||
uint8_t t, wt; int8_t tcol; int d;
|
||||
@@ -842,10 +1002,23 @@ static void check_bumped(void)
|
||||
if (d >= 0) return; /* ещё не зашёл за грань */
|
||||
Kid.x = (uint8_t)char_dx_forward((int8_t)d); /* край -> грань */
|
||||
determine_col();
|
||||
if (Kid.action == ACT_BUMPED) return; /* уже в отскоке — не рестартить */
|
||||
Kid.fall_y = 0;
|
||||
kid_set_seq(SEQ_47_BUMP); /* отскок (TODO: bumped_sound) */
|
||||
play_seq();
|
||||
/* Хвост bumped (seg004:04E4): чем КОНЧИТСЯ удар, решает тайл, на котором
|
||||
* персонаж оказался после отжатия от грани. Сквозь стену/верх двери он
|
||||
* стоит на СОСЕДНЕЙ клетке (в сторону толчка), для прочих препятствий
|
||||
* (ворота, зеркало, кусаки) — на клетке самого препятствия (у ворот пол
|
||||
* есть, поэтому tile_is_floor их считает полом). */
|
||||
{
|
||||
uint8_t ct = t;
|
||||
if (Kid.direction < 0) { /* смотрит влево => толчок ВПРАВО */
|
||||
if (ct == TILE_DOORTOP || ct == TILE_DOORTOP_FLOOR || ct == TILE_WALL)
|
||||
ct = get_tile((int8_t)(tcol + 1), Kid.curr_row);
|
||||
} else { /* смотрит вправо => толчок ВЛЕВО */
|
||||
if (ct == TILE_WALL)
|
||||
ct = get_tile((int8_t)(tcol - 1), Kid.curr_row);
|
||||
}
|
||||
if (tile_is_floor(ct)) bumped_floor();
|
||||
else bumped_fall();
|
||||
}
|
||||
determine_col();
|
||||
}
|
||||
|
||||
@@ -978,7 +1151,6 @@ void pop_loose_reset(void)
|
||||
* (action >= hang_climb, кроме turn). Иначе −1 HP: смерть → seq_22 (crushed),
|
||||
* живой → seq_52 (плита придавила: Kid приседает), с отступом на 2 назад,
|
||||
* если сзади пусто. Кадр 109 (присед) последовательность не меняет. */
|
||||
#define SEQ_22_CRUSHED 22
|
||||
#define SEQ_52_LOOSE_FELL_ON_KID 52
|
||||
#define FRAME_109_CROUCH 109
|
||||
|
||||
|
||||
@@ -37,6 +37,18 @@ extern uint8_t pop_kid_dead;
|
||||
extern uint8_t hitp_curr;
|
||||
void pop_kid_hp_reset(void);
|
||||
|
||||
/* Предметы (seg005 check_get_item/get_item, seg006 do_pickup/proc_get_object).
|
||||
* pop_have_sword — «меч у Кида» (порт have_sword). pop_item_taken — 0 или
|
||||
* tilepos+1 только что поднятого предмета: вызывающий обязан запечь тайл
|
||||
* (он стал полом) на ОБЕИХ страницах и записать override комнаты. */
|
||||
extern uint8_t pop_have_sword;
|
||||
extern uint8_t pop_item_taken;
|
||||
/* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока
|
||||
* НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */
|
||||
extern uint8_t pop_flash_time;
|
||||
uint8_t pop_get_item_action(void); /* 0 нет / 1 присесть / 2 поднять меч */
|
||||
void pop_proc_get_object(void); /* эффект — из play_seq (опкод GET_ITEM) */
|
||||
|
||||
/* Уход из комнаты ВБОК/ВВЕРХ: 0=нет; 1=left,2=right,3=up (= side+1 для
|
||||
* pop_room_link). Координаты Kid уже репроецированы (x∓140 / y+189).
|
||||
* Приложение переключает комнату по соответствующей связи и снимает флаг. */
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#define TILE_DEBRIS 0x0E /* button_type для «насовсем открыть» (died_on_button) */
|
||||
#define TILE_OPENER 0x0F
|
||||
#define TILE_POTION 0x0A
|
||||
#define TILE_SWORD 0x16 /* меч-предмет на полу */
|
||||
#define TILE_LEVELDOOR 0x10 /* левая половина двери уровня (на ней modif) */
|
||||
#define TILE_TORCH 0x13
|
||||
#define TILE_TORCH_DEBRIS 0x1E
|
||||
|
||||
@@ -23,7 +25,7 @@ static uint8_t room_seen[POP_NROOMS]; /* modif проинициализиро
|
||||
|
||||
/* ---- Список анимируемых тайлов ------------------------------------ */
|
||||
typedef struct { uint8_t room, tilepos; int8_t type; } trob_t;
|
||||
#define TROBS_MAX 24
|
||||
#define TROBS_MAX 30 /* как в оригинале (types.h) */
|
||||
static trob_t trobs[TROBS_MAX];
|
||||
static uint8_t trobs_count;
|
||||
static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */
|
||||
@@ -47,6 +49,12 @@ static uint8_t trob_drawn[POP_ROOMTILES];
|
||||
* кадре 430 080, то есть ~12 % кадра на пустой проход. */
|
||||
static uint8_t rest_pending;
|
||||
|
||||
/* Дверь уровня: отложенный редрой ВТОРОЙ страницы дабл-буфера. Створка
|
||||
* анимируется покадрово в back-страницу, поэтому последний кадр анимации
|
||||
* попадает только на одну из двух страниц — вторая застревает на шаг раньше
|
||||
* и дверь «дрожит» через кадр. ldoor_pos = tilepos ПРАВОЙ половины. */
|
||||
static uint8_t ldoor_rest, ldoor_pos;
|
||||
|
||||
/* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и
|
||||
* выбора следующего кадра пламени. */
|
||||
static unsigned long trob_seed;
|
||||
@@ -64,6 +72,7 @@ void pop_trob_reset(void)
|
||||
for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0;
|
||||
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
|
||||
rest_pending = 0;
|
||||
ldoor_rest = 0;
|
||||
trobs_count = 0;
|
||||
}
|
||||
|
||||
@@ -109,6 +118,7 @@ void pop_trob_room_changed(void)
|
||||
uint8_t i;
|
||||
for (i = 0; i < POP_ROOMTILES; i++) { spike_rest[i] = 0; button_rest[i] = 0; trob_drawn[i] = 0; }
|
||||
rest_pending = 0;
|
||||
ldoor_rest = 0;
|
||||
}
|
||||
|
||||
/* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её
|
||||
@@ -121,6 +131,25 @@ void pop_trob_anim_room(uint8_t room)
|
||||
uint8_t i, code;
|
||||
if (!mod) return;
|
||||
pop_level_access_begin();
|
||||
/* Освободить список от «декоративных» trob ДРУГИХ комнат: факел, зелье и
|
||||
* блеск меча анимируются только в ОТРИСОВАННОЙ комнате, а место в списке
|
||||
* занимают. Оригинал их копит и на 30-й выдаёт «Trobs Overflow»; у нас
|
||||
* отладочный обход всех 24 комнат упирается в лимит за пару минут, после
|
||||
* чего новые анимации молча не заводятся. Механику (ворота/пики/кнопки,
|
||||
* в т.ч. кросс-комнатные) чистка не трогает. */
|
||||
{
|
||||
uint8_t k, wr = 0;
|
||||
for (k = 0; k < trobs_count; k++) {
|
||||
uint8_t c = (uint8_t)(pop_level_tile_raw(trobs[k].room,
|
||||
trobs[k].tilepos) & 0x1F);
|
||||
if (trobs[k].room != room &&
|
||||
(c == TILE_TORCH || c == TILE_TORCH_DEBRIS ||
|
||||
c == TILE_POTION || c == TILE_SWORD))
|
||||
continue;
|
||||
trobs[wr++] = trobs[k];
|
||||
}
|
||||
trobs_count = wr;
|
||||
}
|
||||
for (i = 0; i < POP_ROOMTILES; i++) {
|
||||
code = (uint8_t)(pop_level_tile_raw(room, i) & 0x1F);
|
||||
if (code == TILE_TORCH || code == TILE_TORCH_DEBRIS) {
|
||||
@@ -129,6 +158,10 @@ void pop_trob_anim_room(uint8_t room)
|
||||
} else if (code == TILE_POTION) {
|
||||
mod[i] = (uint8_t)((mod[i] & 0xF8) | (trob_prandom(6) + 1));
|
||||
pop_add_trob(room, i, 1);
|
||||
} else if (code == TILE_SWORD) {
|
||||
/* start_anim_sword (seg007:087C): случайная фаза счётчика блеска. */
|
||||
mod[i] = (uint8_t)(trob_prandom(0xFF) & 0x1F);
|
||||
pop_add_trob(room, i, 1);
|
||||
}
|
||||
}
|
||||
pop_level_access_end();
|
||||
@@ -216,6 +249,13 @@ static int8_t trigger_1(uint8_t target_type, uint8_t room, uint8_t tilepos, uint
|
||||
{
|
||||
if (target_type == TILE_GATE)
|
||||
return trigger_gate(room, tilepos, button_type);
|
||||
if (target_type == TILE_LEVELDOOR) {
|
||||
/* trigger_1 (seg007:0999): дверь уровня открывается ОДИН раз — если
|
||||
* створка уже тронулась (modif != 0), кнопка ничего не делает. */
|
||||
uint8_t *m = pop_trob_modif(room);
|
||||
if (m && m[tilepos] == 0) return 1;
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -258,6 +298,27 @@ static void animate_button(uint8_t idx, int8_t *type)
|
||||
}
|
||||
}
|
||||
|
||||
/* animate_leveldoor (seg007:05F1): створка двери уровня. type 0..2 —
|
||||
* открытие (modif 0 -> 43, по пикселю за кадр), type >= 3 — быстрое
|
||||
* закрытие со скоростями {0,5,17,99} (после выхода из уровня). Готово ->
|
||||
* type = -1. */
|
||||
static const uint8_t leveldoor_close_speeds[5] = {0, 5, 17, 99, 0};
|
||||
|
||||
static void animate_leveldoor(uint8_t *m, int8_t *type)
|
||||
{
|
||||
if (*type < 0) return;
|
||||
if (*type >= 3) {
|
||||
uint8_t sp;
|
||||
if (*type < 7) (*type)++;
|
||||
sp = leveldoor_close_speeds[*type - 3];
|
||||
if (sp >= *m) { *m = 0; *type = -1; }
|
||||
else *m = (uint8_t)(*m - sp);
|
||||
} else {
|
||||
if (*m < 43) (*m)++;
|
||||
if (*m >= 43) { *m = 43; *type = -1; }
|
||||
}
|
||||
}
|
||||
|
||||
/* animate_door (seg007:0522): openness по типу анимации. *m — modif ворот,
|
||||
* *type — 0 закрытие / 1 открытие / 2 насовсем / 3..8 быстрое закрытие. */
|
||||
static const uint8_t gate_close_speeds[9] = {0, 0, 0, 20, 40, 60, 80, 100, 120};
|
||||
@@ -327,6 +388,10 @@ void pop_process_trobs(uint8_t cur_room)
|
||||
}
|
||||
rest_pending = left;
|
||||
}
|
||||
if (ldoor_rest) { /* доложить финальный кадр створки на 2-ю страницу */
|
||||
pop_leveldoor_redraw(ldoor_pos / 10, ldoor_pos % 10);
|
||||
ldoor_rest--;
|
||||
}
|
||||
|
||||
if (trobs_count == 0) return;
|
||||
|
||||
@@ -360,6 +425,9 @@ void pop_process_trobs(uint8_t cur_room)
|
||||
case TILE_GATE:
|
||||
animate_door(&mod[tp], &type);
|
||||
break;
|
||||
case TILE_LEVELDOOR:
|
||||
animate_leveldoor(&mod[tp], &type);
|
||||
break;
|
||||
case TILE_TORCH:
|
||||
case TILE_TORCH_DEBRIS:
|
||||
/* animate_torch (seg007:03C1): кадр пламени; анимация вечная —
|
||||
@@ -375,6 +443,15 @@ void pop_process_trobs(uint8_t cur_room)
|
||||
if (room == cur_room)
|
||||
mod[tp] = (uint8_t)((mod[tp] & 0xF8) | bubble_next_frame(mod[tp] & 7));
|
||||
break;
|
||||
case TILE_SWORD:
|
||||
/* animate_sword (seg007:0425): счётчик вниз; на 0 — новый период
|
||||
* 0x28..0x67. Блеск рисуется РОВНО на modif==1 (draw_tile берёт
|
||||
* кадр (modif==1)+10), т.е. одиночная вспышка раз в 40..103 тика. */
|
||||
if (room == cur_room) {
|
||||
if (--mod[tp] == 0)
|
||||
mod[tp] = (uint8_t)((trob_prandom(255) & 0x3F) + 0x28);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
type = -1;
|
||||
break;
|
||||
@@ -406,6 +483,32 @@ void pop_process_trobs(uint8_t cur_room)
|
||||
* его уже никто не положит: ставим отложенный редрой. */
|
||||
if (type < 0) { spike_rest[tp] = 1; rest_pending++; }
|
||||
}
|
||||
if (room == cur_room && code == TILE_LEVELDOOR) {
|
||||
/* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там
|
||||
* её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе
|
||||
* kid_heal вернул бы из фона закрытую створку. Куски двери
|
||||
* непрозрачны, поэтому wipe не нужен. */
|
||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
||||
if (tcol + 1 < 10) {
|
||||
pop_leveldoor_redraw(trow, tcol + 1);
|
||||
/* Анимация кончилась — этот кадр лёг только на ОДНУ страницу;
|
||||
* повторить его на второй, иначе дверь дрожит через кадр. */
|
||||
if (type < 0) { ldoor_pos = (uint8_t)(tp + 1); ldoor_rest = 1; }
|
||||
}
|
||||
}
|
||||
if (room == cur_room && code == TILE_SWORD) {
|
||||
/* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1):
|
||||
* спрайт запечён в фон (draw_tile), поэтому перерисовываем тайл
|
||||
* той же схемой, что и кнопку — текущая страница сразу, вторая
|
||||
* следующим кадром через rest-цикл. */
|
||||
uint8_t sig = (uint8_t)(mod[tp] == 1);
|
||||
if (sig != trob_drawn[tp]) {
|
||||
uint8_t trow = tp / 10, tcol = tp % 10;
|
||||
pop_button_redraw(trow, tcol);
|
||||
button_rest[tp] = 1; rest_pending++;
|
||||
trob_drawn[tp] = sig;
|
||||
}
|
||||
}
|
||||
if (room == cur_room && (code == TILE_OPENER || code == TILE_CLOSER)) {
|
||||
/* КНОПКА — редрой только при смене видимого состояния (нажата =
|
||||
* таймер связи >1): пока Кид её держит, картинка стабильна.
|
||||
|
||||
@@ -85,9 +85,24 @@ static uint8_t room_label_redraw;
|
||||
* в docs/gates_spikes_plan.md, P0.) */
|
||||
#define TILE_DEBRIS 0x0E
|
||||
#define TILE_EMPTY 0x00
|
||||
#define TILE_FLOOR 0x01
|
||||
#define OVR_MAX 8
|
||||
static uint8_t ovr_room[OVR_MAX], ovr_pos[OVR_MAX], ovr_tile[OVR_MAX], ovr_cnt;
|
||||
|
||||
/* Поднятый предмет (меч): tilepos и счётчик «запечь пол» по страницам.
|
||||
* flash_on — вспышка фона активна (нулевая запись палитры подменена). */
|
||||
static uint8_t item_pos, item_bake, flash_on;
|
||||
|
||||
/* Подмена НУЛЕВОЙ записи палитры на обеих страницах (do_flash/set_bg_attr).
|
||||
* Через функцию с параметрами, а не пятью литералами в месте вызова: на
|
||||
* gfx_pal_set(0,0,0,0,0) SDCC 4.5 выдаёт невалидный `ld hl, a`
|
||||
* (см. .sprinter-cc-roomtest/roomtest.asm) и ассемблер падает. */
|
||||
static void flash_bg(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
gfx_pal_set(0, 0, r, g, b);
|
||||
gfx_pal_set(1, 0, r, g, b);
|
||||
}
|
||||
|
||||
static void add_tile_override(uint8_t room, uint8_t pos, uint8_t tile)
|
||||
{
|
||||
uint8_t i;
|
||||
@@ -187,6 +202,7 @@ int main(void)
|
||||
puts("pop_bg_load failed");
|
||||
return 1;
|
||||
}
|
||||
pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */
|
||||
if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
|
||||
puts("kid atlas load failed");
|
||||
return 1;
|
||||
@@ -378,6 +394,33 @@ int main(void)
|
||||
add_tile_override(cur_room, (uint8_t)(pop_debris_at - 1), TILE_DEBRIS);
|
||||
pop_debris_at = 0;
|
||||
}
|
||||
if (pop_item_taken) { /* предмет поднят → тайл стал полом */
|
||||
item_pos = (uint8_t)(pop_item_taken - 1);
|
||||
item_bake = 2; /* запечь на ОБЕИХ страницах */
|
||||
add_tile_override(cur_room, item_pos, TILE_FLOOR);
|
||||
pop_add_trob(cur_room, item_pos, -1); /* меч убран — блеска больше нет */
|
||||
pop_item_taken = 0;
|
||||
}
|
||||
if (item_bake) { /* стереть спрайт меча из фона страницы */
|
||||
pop_floor_bake(item_pos / 10, item_pos % 10);
|
||||
item_bake--;
|
||||
}
|
||||
/* Вспышка фона — порт flash_if_hurt/remove_flash_if_hurt (seg003:0AFC):
|
||||
* do_flash(color) = set_bg_attr(0, color), т.е. подмена НУЛЕВОЙ записи
|
||||
* палитры (вспыхивает всё чёрное поле). Оригинал ставит цвет и СНИМАЕТ
|
||||
* его В ТОМ ЖЕ кадре, поэтому пока идёт flash_time видно СТРОБ
|
||||
* «жёлтый/чёрный», а не одну длинную заливку. Снятие — после первого
|
||||
* gfx_wait_vsync (ниже): цвет держится ~1 растровый кадр из трёх.
|
||||
* Приоритет как в оригинале: активный flash_time > кадр урона
|
||||
* (hitp_delta<0 -> ярко-красный, ровно на один кадр). */
|
||||
if (pop_flash_time) {
|
||||
flash_bg(255, 255, 85); /* color_14_brightyellow */
|
||||
pop_flash_time--;
|
||||
flash_on = 1;
|
||||
} else if (pop_kid_hurt) {
|
||||
flash_bg(255, 85, 85); /* color_12_brightred */
|
||||
flash_on = 1;
|
||||
}
|
||||
io_border = 4; /* зеленый */
|
||||
update_kid_render_dx(); /* straddle: смещение спрайта, если kid_room≠cur_room */
|
||||
kid_draw(); /* нарисовать новый кадр (детект выхода за поле) */
|
||||
@@ -425,6 +468,10 @@ int main(void)
|
||||
#endif
|
||||
|
||||
gfx_wait_vsync();
|
||||
if (flash_on) { /* строб: цвет держался один кадр растра */
|
||||
flash_bg(0, 0, 0);
|
||||
flash_on = 0;
|
||||
}
|
||||
gfx_wait_vsync();
|
||||
gfx_wait_vsync();
|
||||
if (dbuf) {
|
||||
|
||||
@@ -163,7 +163,15 @@ def collect_used():
|
||||
# поверх того, что сзади, а у нас 0 -> 0xFF делал её прозрачной, и сквозь неё
|
||||
# просвечивал Kid (спуск/подъём у кромки кнопки). 43/36 полностью непрозрачны —
|
||||
# для них флаг ничего не меняет, оставлены для полноты списка.
|
||||
NO_TRANSP_ENV_IDS = {36, 43, 73, 74, 96, 149}
|
||||
# Дверь уровня (draw_leveldoor, seg008:1D29): решётка створки (33 — слайс 4px,
|
||||
# 34 — верх), лестница за ней (99 — низ, 144 — марш). render_room.py
|
||||
# draw_leveldoor не реализует, поэтому в автосбор эти id не попадают.
|
||||
LEVELDOOR_ENV_IDS = {33, 34, 99, 144}
|
||||
|
||||
# Куски двери уровня (draw_leveldoor) оригинал рисует blitters_0_no_transp:
|
||||
# марш лестницы 144 обязан ЗАКРАШИВАТЬ поднявшуюся створку, иначе слева от
|
||||
# лестницы сквозь него видна решётка (0 -> 0xFF делал фон спрайта прозрачным).
|
||||
NO_TRANSP_ENV_IDS = {36, 43, 73, 74, 96, 149} | LEVELDOOR_ENV_IDS
|
||||
|
||||
|
||||
def load_indexed(chtab, id, opaque=False):
|
||||
@@ -355,7 +363,8 @@ def main():
|
||||
| GATE_ANIM_ENV_IDS # + кадры открытия ворот
|
||||
| TORCH_ENV_IDS # + основание факела
|
||||
| STUCK_ENV_IDS # + нажатая кнопка-closer
|
||||
| OPENER_NOFLOOR_ENV_IDS) # + opener без пола слева
|
||||
| OPENER_NOFLOOR_ENV_IDS # + opener без пола слева
|
||||
| LEVELDOOR_ENV_IDS) # + дверь уровня (створка/лестница)
|
||||
wall_ids = sorted(id for ch, id in used if ch == R.CHTAB_WALL)
|
||||
|
||||
env_bg = [i for i in env_ids if i not in FORE_ENV_IDS]
|
||||
|
||||
@@ -24,6 +24,8 @@ import pop_pack_bg as B # pack_atlas, blob, scale6to8, ENV_SHIFT
|
||||
KID_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/KID"
|
||||
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid")
|
||||
PAL_BASE = 0x70 # слоты палитры Sprinter под Kid
|
||||
SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F
|
||||
SWORD_IDS = tuple(range(20, 28)) # sword_tbl для кадров 229..236 (найти-убрать)
|
||||
SHIFT = 3 # kid[img>>3], idx img&7 (Kid-кадры крупные: 8/стр ≤16КБ)
|
||||
MASK = (1 << SHIFT) - 1
|
||||
|
||||
@@ -62,6 +64,22 @@ def load_kid(aidx):
|
||||
return im.width, im.height, transpose_cols(im.width, im.height, rowmajor)
|
||||
|
||||
|
||||
def load_sword(id):
|
||||
"""chtab_0 (меч в руке): sword_tbl[].id = N -> res(700+N+1).png в data/PRINCE
|
||||
(в оригинале add_midtable(chtab, obj_id+1) -> get_image(chtab, obj_id)).
|
||||
Пиксели, как у Kid, COLUMN-MAJOR (gfx_blit_cols + бесплатный флип)."""
|
||||
path = os.path.join(os.path.dirname(B.R.VDUNGEON_DIR), B.POT_DIR_NAME,
|
||||
f"res{700 + id + 1}.png")
|
||||
if not os.path.exists(path):
|
||||
B.die(f"нет файла {path}")
|
||||
im = Image.open(path)
|
||||
if im.mode != "P":
|
||||
B.die(f"res{700+id+1}: mode {im.mode}, ожидался P")
|
||||
src = im.tobytes()
|
||||
rowmajor = bytes((0xFF if v == 0 else SWORD_PAL_BASE + v) for v in src)
|
||||
return im.width, im.height, transpose_cols(im.width, im.height, rowmajor)
|
||||
|
||||
|
||||
def _read16(path):
|
||||
b = open(path, "rb").read()
|
||||
return [(b[4 + i * 3], b[4 + i * 3 + 1], b[4 + i * 3 + 2]) for i in range(16)]
|
||||
@@ -79,6 +97,8 @@ def build_palette(out_path):
|
||||
(PAL_BASE, os.path.join(KID_DIR, "res400.pal")), # kid 0x70
|
||||
(B.POT_PAL_BASE, os.path.join(os.path.dirname(B.R.VDUNGEON_DIR),
|
||||
B.POT_DIR_NAME, "res150.pal")), # chtab_1 0x40
|
||||
(SWORD_PAL_BASE, os.path.join(os.path.dirname(B.R.VDUNGEON_DIR),
|
||||
B.POT_DIR_NAME, "res700.pal")), # chtab_0 0x80
|
||||
]
|
||||
# 16 базовых цветов экрана (mono-блит красит ими: пламя, пузырьки зелья)
|
||||
for i, (r, g, bl) in enumerate(B.VGA16):
|
||||
@@ -121,6 +141,17 @@ def main():
|
||||
names.append(None)
|
||||
print(f" (kid стр {pg}: пусто)")
|
||||
|
||||
# chtab_0: меч в руке. Пакуем ТОЛЬКО кадры подъёма/ножен (sword_tbl
|
||||
# 35..42 -> id 20..27); индекс в атласе = id - 20 (бой не портирован).
|
||||
sw = {}
|
||||
for i, id in enumerate(SWORD_IDS):
|
||||
w, h, pix = load_sword(id)
|
||||
sw[i] = (w, h, pix)
|
||||
cnt, sz = B.pack_atlas(os.path.join(OUT_DIR, "sword.atl"), sw)
|
||||
total += sz
|
||||
print(f" sword.atl (chtab_0, id {SWORD_IDS[0]}..{SWORD_IDS[-1]}): "
|
||||
f"{len(sw)} спрайтов, каталог {cnt}, {sz} Б")
|
||||
|
||||
build_palette(os.path.join(OUT_DIR, "kid.pal"))
|
||||
|
||||
# C-заголовок
|
||||
@@ -133,7 +164,10 @@ def main():
|
||||
f.write("static const char *const kid_atl[KID_PAGES] = {\n")
|
||||
for nm in names:
|
||||
f.write(" %s,\n" % ('"%s"' % nm if nm else "0"))
|
||||
f.write('};\n#define KID_PAL_FILE "kid.pal"\n#endif\n')
|
||||
f.write('};\n#define KID_PAL_FILE "kid.pal"\n')
|
||||
f.write('#define KID_SWORD_ATL "sword.atl" /* chtab_0: меч в руке */\n')
|
||||
f.write(f"#define KID_SWORD_ID0 {SWORD_IDS[0]} /* индекс в атласе = sword_tbl.id - ID0 */\n")
|
||||
f.write("#endif\n")
|
||||
|
||||
print(f"ИТОГО: {len(ids)} спрайтов Kid, {len(names)} EMM-страниц, "
|
||||
f"{total} Б диск (+1КБ палитра) -> {OUT_DIR}")
|
||||
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 718 B |
|
After Width: | Height: | Size: 788 B |
|
After Width: | Height: | Size: 526 B |
|
After Width: | Height: | Size: 509 B |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 307 B |
|
After Width: | Height: | Size: 498 B |
|
After Width: | Height: | Size: 150 B |
|
After Width: | Height: | Size: 225 B |
|
After Width: | Height: | Size: 390 B |
|
After Width: | Height: | Size: 674 B |
|
After Width: | Height: | Size: 879 B |
|
After Width: | Height: | Size: 427 B |
|
After Width: | Height: | Size: 113 B |
|
After Width: | Height: | Size: 219 B |
|
After Width: | Height: | Size: 252 B |
|
After Width: | Height: | Size: 717 B |
|
After Width: | Height: | Size: 89 B |
|
After Width: | Height: | Size: 89 B |
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 552 B |
|
After Width: | Height: | Size: 700 B |
|
After Width: | Height: | Size: 640 B |
|
After Width: | Height: | Size: 113 B |
|
After Width: | Height: | Size: 422 B |
|
After Width: | Height: | Size: 578 B |
|
After Width: | Height: | Size: 562 B |
|
After Width: | Height: | Size: 163 B |
|
After Width: | Height: | Size: 195 B |
|
After Width: | Height: | Size: 509 B |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 89 B |
|
After Width: | Height: | Size: 89 B |
|
After Width: | Height: | Size: 195 B |
|
After Width: | Height: | Size: 509 B |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 548 B |
|
After Width: | Height: | Size: 296 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 223 B |
|
After Width: | Height: | Size: 296 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 223 B |
|
After Width: | Height: | Size: 390 B |
|
After Width: | Height: | Size: 596 B |
|
After Width: | Height: | Size: 643 B |
|
After Width: | Height: | Size: 591 B |
|
After Width: | Height: | Size: 639 B |
|
After Width: | Height: | Size: 589 B |
|
After Width: | Height: | Size: 660 B |
|
After Width: | Height: | Size: 748 B |
|
After Width: | Height: | Size: 793 B |
|
After Width: | Height: | Size: 625 B |
|
After Width: | Height: | Size: 585 B |