cd8d566d82
Порт PoP на Sprinter. Текущий PoC — applications/PoP/roomtest/: комната 1 (фон-композиция тайлов) + Kid с управлением на raw-клавиатуре и коллизией с картой. - roomtest — pop_bg (фон), pop_kid (спрайты Kid, column-major флип, seqtbl-анимация), pop_ctrl (порт control() PoP на held-state kbd_raw), pop_map (коллизия seg004/005: бег/стоп у стены, падение/приземление, отскок seq_47, вертикальный прыжок K4.1). MEMORY=small (DATA сразу за CODE, ~23КБ кода не лезет в huge). - toolchain (PoP) — pop_pack_kid/pop_pack_bg/render_room/extract — распаковка res-графики MSDOS в атласы + композиция комнат. - toolchain/ (корень) — make_hdd.sh (быстрый HDD-тест вместо FDD), png_strip.py / room_compose.py (ассет-пайплайн). - docs — PORT_PLAN, KID_PLAN, форматы ресурсов (Apple II / MSDOS / DAT). - bgtest/coltest/poc — ранние PoC (фон, коллизия, первый прототип). .gitignore: build-артефакты applications/*/*/*; исключены внешние референс-репозитории (SDLPoP/mininim/PR/Apple-II — свои git-клоны) и оригинальные game-данные MSDOS/ (копирайт, только для реверса форматов). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
154 lines
7.1 KiB
C
154 lines
7.1 KiB
C
/*
|
|
* level.h — структуры уровня PoP под наш движок.
|
|
*
|
|
* Формат — POP-DAT-FormatSpecifications.pdf §3.4 (DAT 1.0): комната =
|
|
* 30 тайлов (10 колонок x 3 ряда), foretable даёт тип тайла,
|
|
* backtable — модификатор/состояние (семантика зависит от типа).
|
|
* Адресация тайла: tile = (room-1)*30 + tileOffset, tileOffset 0-9 =
|
|
* верхний ряд, 10-19 = средний, 20-29 = нижний, слева направо.
|
|
*
|
|
* Фаза 1 (applications/PoP/docs/PORT_PLAN.md §5, сузили объём):
|
|
* только геометрия (пол/стены/провалы) для коллизий — двери, факелы,
|
|
* ловушки, гарды НЕ используются (структуры под них здесь тоже
|
|
* упрощены/оставлены как заготовка на потом, см. §7 плана Фаза 2).
|
|
*/
|
|
#ifndef LEVEL_H
|
|
#define LEVEL_H
|
|
|
|
#include <stdint.h>
|
|
|
|
/* 1 (не 24) — PoC грузит и рисует только комнату 1; расширить, когда
|
|
* появится настоящий level_load() на несколько комнат. */
|
|
#define LEVEL_ROOMS 1 /* комнаты нумеруются 1..24 в файле,
|
|
* здесь индекс 0..23 = комната N+1 */
|
|
#define ROOM_COLS 10
|
|
#define ROOM_ROWS 3
|
|
#define ROOM_TILES (ROOM_COLS * ROOM_ROWS) /* 30 */
|
|
|
|
/* --- Типы тайлов (Table 7 спецификации) --- */
|
|
#define TILE_TYPE(byte) ((uint8_t)((byte) & 0x1F))
|
|
#define TILE_MODIFIER(byte) ((uint8_t)(((byte) >> 5) & 1))
|
|
|
|
enum {
|
|
TILE_EMPTY = 0x00,
|
|
TILE_FLOOR = 0x01,
|
|
TILE_SPIKES = 0x02,
|
|
TILE_PILLAR = 0x03,
|
|
TILE_GATE = 0x04,
|
|
TILE_STUCK_BUTTON = 0x05,
|
|
TILE_DROP_BUTTON = 0x06,
|
|
TILE_TAPESTRY = 0x07,
|
|
TILE_PILLAR_BOTTOM = 0x08,
|
|
TILE_PILLAR_TOP = 0x09,
|
|
TILE_POTION = 0x0A,
|
|
TILE_LOOSE = 0x0B,
|
|
TILE_TAPESTRY_TOP = 0x0C,
|
|
TILE_MIRROR = 0x0D,
|
|
TILE_DEBRIS = 0x0E,
|
|
TILE_RAISE_BUTTON = 0x0F,
|
|
TILE_EXIT_LEFT = 0x10,
|
|
TILE_EXIT_RIGHT = 0x11,
|
|
TILE_CHOPPER = 0x12,
|
|
TILE_TORCH = 0x13,
|
|
TILE_WALL = 0x14,
|
|
TILE_SKELETON = 0x15,
|
|
TILE_SWORD = 0x16,
|
|
TILE_BALCONY_LEFT = 0x17,
|
|
TILE_BALCONY_RIGHT = 0x18,
|
|
TILE_LATTICE_PILLAR = 0x19,
|
|
TILE_LATTICE_SUPPORT= 0x1A,
|
|
TILE_LATTICE_SMALL = 0x1B,
|
|
TILE_LATTICE_LEFT = 0x1C,
|
|
TILE_LATTICE_RIGHT = 0x1D,
|
|
TILE_TORCH_DEBRIS = 0x1E,
|
|
TILE_NULL = 0x1F
|
|
};
|
|
|
|
/* Твёрдые тайлы — Фаза 1 (только геометрия); классификация наша, для
|
|
* коллизий движка, не часть исходного формата. Двери/шипы/дробилки и
|
|
* т.п. сознательно исключены из объёма Фазы 1 (см. §5 плана) — при
|
|
* встрече в реальных данных трактовать как проходимые до Фазы 2.
|
|
*
|
|
* tile_is_solid() — "есть опора сверху" (вертикальный смысл: можно
|
|
* стоять НА этом тайле) — Floor ТОЖЕ solid в этом смысле! Для
|
|
* горизонтальной коллизии (можно ли ВОЙТИ в эту клетку сбоку) нужен
|
|
* ОТДЕЛЬНЫЙ предикат — см. tile_blocks_side ниже. Баг 2026-07-16:
|
|
* col_blocked() в poc.c ошибочно звал tile_is_solid() для бокового
|
|
* упора — Floor блокировал сам себя, Кид не мог сдвинуться с места
|
|
* стоя на полу. */
|
|
static inline uint8_t tile_is_solid(uint8_t byte)
|
|
{
|
|
switch (TILE_TYPE(byte)) {
|
|
case TILE_FLOOR:
|
|
case TILE_PILLAR:
|
|
case TILE_PILLAR_BOTTOM:
|
|
case TILE_PILLAR_TOP:
|
|
case TILE_WALL:
|
|
case TILE_BALCONY_LEFT:
|
|
case TILE_BALCONY_RIGHT:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* tile_blocks_side() — настоящая преграда СБОКУ (нельзя войти в
|
|
* клетку по горизонтали): Wall/Pillar-семейство. Floor/Balcony НЕ
|
|
* блокируют — по ним идёшь (тайл под ногами, не впереди). Lattice-
|
|
* колонны (0x19-0x1D) — узкие, Кид физически проходит мимо (см.
|
|
* gen_tile_placeholders.py draw_lattice_like) — тоже НЕ блокируют. */
|
|
static inline uint8_t tile_blocks_side(uint8_t byte)
|
|
{
|
|
switch (TILE_TYPE(byte)) {
|
|
case TILE_PILLAR:
|
|
case TILE_PILLAR_BOTTOM:
|
|
case TILE_PILLAR_TOP:
|
|
case TILE_WALL:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* --- Тайл и комната --- */
|
|
typedef struct {
|
|
uint8_t type; /* foretable byte (rrmccccc — см. TILE_TYPE/MODIFIER) */
|
|
uint8_t state; /* backtable byte — модификатор/состояние */
|
|
} tile_t;
|
|
|
|
typedef struct {
|
|
tile_t tiles[ROOM_TILES]; /* индекс = tileOffset 0..29 */
|
|
uint8_t link_left, link_right; /* links-блок: 0 = нет соседа */
|
|
uint8_t link_up, link_down;
|
|
uint8_t guard_location; /* 0..29; 30 (и выше) = нет гарда */
|
|
int8_t guard_direction; /* 0 = вправо, -1 = влево */
|
|
uint8_t guard_skill; /* 0..9 */
|
|
uint8_t guard_colour; /* индекс палитры, Table 11 */
|
|
/* door I/II (событийные цепочки) — Фаза 2, не здесь */
|
|
} room_t;
|
|
|
|
typedef struct {
|
|
room_t rooms[LEVEL_ROOMS]; /* индекс 0 = комната 1 (файл 1-based) */
|
|
uint8_t start_room; /* 1..24 */
|
|
uint8_t start_location; /* 0..29 */
|
|
int8_t start_direction; /* 0 = вправо, -1 = влево */
|
|
} level_t;
|
|
|
|
/* Тайл по (room 1-based, col 0-9, row 0-2). */
|
|
static inline tile_t *level_tile(level_t *lv, uint8_t room, uint8_t col, uint8_t row)
|
|
{
|
|
return &lv->rooms[room - 1].tiles[row * ROOM_COLS + col];
|
|
}
|
|
|
|
/* Загружает ОДНУ комнату + стартовую позицию уровня из файла в формате
|
|
* tools/extract_room.py (63 Б: foretable[30]+backtable[30] той комнаты
|
|
* + start_room+start_pos+start_dir, вырезанные из res20NN.bin — layout
|
|
* подтверждён декодом байт 2026-07-15/16: файл начинается СРАЗУ с
|
|
* foretable[720], потом backtable[720], без заголовка; start_position
|
|
* — смещение 2112, сверено со структурой level_type в SDLPoP/src/
|
|
* types.h). Заполняет lv->start_room/start_location/start_direction.
|
|
* 0 — OK, -1 — файл не найден/короче 63 Б. */
|
|
int level_load_room(level_t *lv, uint8_t room, const char *path);
|
|
|
|
#endif
|