/* * 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 /* 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