86d7615841
Порт Prince of Persia (applications/PoP/roomtest): развитие уровня, объекты (loose-полы/обломки), переходы между комнатами, фон-упаковка; планы (room_model/size_optimization), bug_list, pop_trob. Арт third_party/16x16-RPG-characters. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
325 lines
14 KiB
C
325 lines
14 KiB
C
/*
|
||
* pop_trob.c — P0-каркас: room_modif + список trob + диспетчер анимаций.
|
||
* Порт SDLPoP seg007 (process_trobs/animate_tile/add_trob/animate_spike).
|
||
* См. pop_trob.h.
|
||
*/
|
||
#include "pop_trob.h"
|
||
#include "pop_level.h"
|
||
#include "pop_bg.h" /* pop_spike_redraw */
|
||
|
||
/* Коды тайлов (fg & 0x1F), которые анимируются. */
|
||
#define TILE_SPIKE 0x02
|
||
#define TILE_GATE 0x04
|
||
#define TILE_CLOSER 0x06
|
||
#define TILE_DEBRIS 0x0E /* button_type для «насовсем открыть» (died_on_button) */
|
||
#define TILE_OPENER 0x0F
|
||
|
||
/* ---- Персистентное per-room modifier-состояние -------------------- */
|
||
static uint8_t room_modif[POP_NROOMS][POP_ROOMTILES];
|
||
static uint8_t room_seen[POP_NROOMS]; /* modif проинициализирован из уровня */
|
||
|
||
/* ---- Список анимируемых тайлов ------------------------------------ */
|
||
typedef struct { uint8_t room, tilepos; int8_t type; } trob_t;
|
||
#define TROBS_MAX 24
|
||
static trob_t trobs[TROBS_MAX];
|
||
static uint8_t trobs_count;
|
||
static uint8_t trob_code[TROBS_MAX]; /* префетч кодов тайлов на кадр (1 map W0) */
|
||
|
||
/* Отложенная перерисовка «покоя» законченных пик/кнопок текущей комнаты
|
||
* (2 кадра = обе страницы дабл-буфера, как loose_rest). */
|
||
static uint8_t spike_rest[POP_ROOMTILES];
|
||
static uint8_t button_rest[POP_ROOMTILES];
|
||
|
||
/* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button:
|
||
* признак «нажата» = таймер связи >1). Редрой пики/кнопки — ТОЛЬКО при её
|
||
* изменении (порт redraw_11h-on-change из SDLPoP). Без этого кнопка под
|
||
* стоящим Кидом перерисовывалась КАЖДЫЙ кадр (2×draw_tile ≈ 50% кадра в
|
||
* статике), т.к. её trob висит, пока Кид стоит (это верно, как в SDLPoP —
|
||
* расхождение было именно в безусловном редрое). */
|
||
static uint8_t trob_drawn[POP_ROOMTILES];
|
||
|
||
void pop_trob_reset(void)
|
||
{
|
||
uint8_t i;
|
||
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; }
|
||
trobs_count = 0;
|
||
}
|
||
|
||
uint8_t *pop_trob_modif(uint8_t room)
|
||
{
|
||
if (room < 1 || room > POP_NROOMS) return 0;
|
||
if (!room_seen[room - 1]) {
|
||
pop_level_room_bg(room, room_modif[room - 1]);
|
||
room_seen[room - 1] = 1;
|
||
}
|
||
return room_modif[room - 1];
|
||
}
|
||
|
||
static int find_trob(uint8_t room, uint8_t tilepos)
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < trobs_count; i++)
|
||
if (trobs[i].room == room && trobs[i].tilepos == tilepos) return i;
|
||
return -1;
|
||
}
|
||
|
||
void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
|
||
{
|
||
int f = find_trob(room, tilepos);
|
||
if (f >= 0) { trobs[f].type = type; return; }
|
||
if (trobs_count >= TROBS_MAX) return;
|
||
trobs[trobs_count].room = room;
|
||
trobs[trobs_count].tilepos = tilepos;
|
||
trobs[trobs_count].type = type;
|
||
trobs_count++;
|
||
}
|
||
|
||
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; }
|
||
}
|
||
|
||
/* animate_spike (seg007:04D3): автомат по modifier. *m — modifier тайла,
|
||
* *type — тип trob (>=0 активен, <0 = удалить). Выдвиг: ++modif (1..4),
|
||
* на 5 → 0x8F (полностью вышли, безвредны), на 9 → 0 + конец. Убирание
|
||
* (флаг 0x80): --modif; когда 0x7F-часть обнулилась → modif=6 (начать
|
||
* визуальный уход 6..8). 0xFF = disabled. */
|
||
static void animate_spike(uint8_t *m, int8_t *type)
|
||
{
|
||
uint8_t mod = *m;
|
||
if (*type >= 0) {
|
||
if (mod == 0xFF) return; /* disabled — не двигать */
|
||
if (mod & 0x80) {
|
||
--mod;
|
||
if (mod & 0x7F) { *m = mod; return; }
|
||
mod = 6;
|
||
} else {
|
||
++mod;
|
||
if (mod == 5) mod = 0x8F;
|
||
else if (mod == 9) { mod = 0; *type = -1; }
|
||
}
|
||
}
|
||
*m = mod;
|
||
}
|
||
|
||
/* ---- Кнопки/ворота (seg007) --------------------------------------- *
|
||
* Таблицы связей LINKLOC/LINKMAP — в pop_level (мутабельная копия в W2).
|
||
* doorlink1 (d1) = LINKLOC, doorlink2 (d2) = LINKMAP. */
|
||
static uint8_t get_doorlink_timer(uint8_t i) { return (uint8_t)(pop_doorlink2(i) & 0x1F); }
|
||
static void set_doorlink_timer(uint8_t i, uint8_t v)
|
||
{
|
||
pop_doorlink2_set(i, (uint8_t)((pop_doorlink2(i) & 0xE0) | (v & 0x1F)));
|
||
}
|
||
static uint8_t get_doorlink_tile(uint8_t i) { return (uint8_t)(pop_doorlink1(i) & 0x1F); }
|
||
static uint8_t get_doorlink_next(uint8_t i) { return (uint8_t)(!(pop_doorlink1(i) & 0x80)); }
|
||
static uint8_t get_doorlink_room(uint8_t i)
|
||
{
|
||
return (uint8_t)(((pop_doorlink1(i) & 0x60) >> 5) + ((pop_doorlink2(i) & 0xE0) >> 3));
|
||
}
|
||
|
||
/* trigger_gate (seg007:092C): изменить openness ворот по типу кнопки.
|
||
* Возвращает тип анимации для add_trob (или -1 = ничего). */
|
||
static int8_t trigger_gate(uint8_t room, uint8_t tilepos, uint8_t button_type)
|
||
{
|
||
uint8_t *mod = pop_trob_modif(room);
|
||
uint8_t modifier = mod[tilepos];
|
||
if (button_type == TILE_OPENER) {
|
||
if (modifier == 0xFF) return -1; /* уже насовсем открыт */
|
||
if (modifier >= 188) { mod[tilepos] = 238; return -1; } /* открыт → держать */
|
||
mod[tilepos] = (uint8_t)((modifier + 3) & 0xFC);
|
||
return 1; /* обычное открытие */
|
||
} else if (button_type == TILE_DEBRIS) { /* насовсем (died_on_button) */
|
||
if (modifier < 188) return 2;
|
||
mod[tilepos] = 0xFF;
|
||
return -1;
|
||
} else { /* closer */
|
||
if (modifier != 0) return 3; /* быстро закрыть */
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
/* trigger_1 (seg007:0999): цель = ворота → trigger_gate; иначе -1 (leveldoor
|
||
* и прочее — отложено). */
|
||
static int8_t trigger_1(uint8_t target_type, uint8_t room, uint8_t tilepos, uint8_t button_type)
|
||
{
|
||
if (target_type == TILE_GATE)
|
||
return trigger_gate(room, tilepos, button_type);
|
||
return -1;
|
||
}
|
||
|
||
/* do_trigger_list (seg007:09E5): обойти цепочку LINKLOC от index, для каждой
|
||
* цели добавить trob (если trigger_1 >= 0). */
|
||
static void do_trigger_list(uint8_t index, uint8_t button_type)
|
||
{
|
||
for (;;) {
|
||
uint8_t room = get_doorlink_room(index);
|
||
uint8_t tilepos = get_doorlink_tile(index);
|
||
uint8_t target_type = pop_level_tile(room, tilepos);
|
||
int8_t result = trigger_1(target_type, room, tilepos, button_type);
|
||
if (result >= 0) pop_add_trob(room, tilepos, result);
|
||
if (get_doorlink_next(index) == 0) break;
|
||
index++;
|
||
}
|
||
}
|
||
|
||
void pop_trigger_button(uint8_t room, uint8_t tilepos,
|
||
uint8_t button_type, uint8_t modifier)
|
||
{
|
||
uint8_t link_timer = get_doorlink_timer(modifier);
|
||
if (link_timer != 0x1F) { /* не заклинено */
|
||
set_doorlink_timer(modifier, 5);
|
||
if (link_timer < 2)
|
||
pop_add_trob(room, tilepos, 1); /* кнопка нажимается (+ TODO sound) */
|
||
do_trigger_list(modifier, button_type);
|
||
}
|
||
}
|
||
|
||
/* animate_button (seg007:0D3A): отсчитать таймер связи; <2 → кнопка
|
||
* отжимается (trob кончился). idx = модификатор кнопки (индекс LINKLOC). */
|
||
static void animate_button(uint8_t idx, int8_t *type)
|
||
{
|
||
if (*type >= 0) {
|
||
uint8_t timer = get_doorlink_timer(idx);
|
||
timer = (uint8_t)(timer ? timer - 1 : 0);
|
||
set_doorlink_timer(idx, timer);
|
||
if (timer < 2) *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};
|
||
static const int8_t door_delta[3] = {-1, 4, 4};
|
||
|
||
static void animate_door(uint8_t *m, int8_t *type)
|
||
{
|
||
int8_t anim_type = *type;
|
||
uint8_t mod = *m;
|
||
if (anim_type >= 0) {
|
||
if (anim_type >= 3) { /* быстрое закрытие */
|
||
if (anim_type < 8) { anim_type++; *type = anim_type; }
|
||
{
|
||
int new_mod = (int)mod - gate_close_speeds[anim_type];
|
||
if (new_mod < 0) { mod = 0; *type = -1; }
|
||
else mod = (uint8_t)new_mod;
|
||
}
|
||
} else if (mod != 0xFF) {
|
||
mod = (uint8_t)(mod + door_delta[anim_type]);
|
||
if (anim_type == 0) { /* закрытие */
|
||
if (mod == 0) *type = -1; /* gate_stop */
|
||
} else { /* открытие */
|
||
if (mod >= 188) {
|
||
if (anim_type < 2) { mod = 238; *type = 0; } /* открыт → потом закрывать */
|
||
else { mod = 0xFF; *type = -1; } /* насовсем */
|
||
}
|
||
}
|
||
} else {
|
||
*type = -1;
|
||
}
|
||
}
|
||
*m = mod;
|
||
}
|
||
|
||
uint8_t pop_trob_room_active(uint8_t room)
|
||
{
|
||
uint8_t i;
|
||
for (i = 0; i < trobs_count; i++)
|
||
if (trobs[i].room == room) return 1;
|
||
return 0;
|
||
}
|
||
|
||
void pop_process_trobs(uint8_t cur_room)
|
||
{
|
||
uint8_t i, w = 0;
|
||
|
||
/* Перерисовка «покоя» законченных пик/кнопок (обе страницы).
|
||
* row/col ведём бегущими счётчиками, а НЕ через i/10, i%10: иначе SDCC
|
||
* выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее
|
||
* подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет
|
||
* активных пик/кнопок (тот же случай, что был в pop_loose_tick). */
|
||
{
|
||
uint8_t row = 0, col = 0;
|
||
for (i = 0; i < POP_ROOMTILES; i++) {
|
||
if (spike_rest[i]) {
|
||
pop_spike_redraw(row, col);
|
||
spike_rest[i]--;
|
||
}
|
||
if (button_rest[i]) {
|
||
pop_button_redraw(row, col);
|
||
button_rest[i]--;
|
||
}
|
||
if (++col == 10) { col = 0; row++; }
|
||
}
|
||
}
|
||
|
||
if (trobs_count == 0) return;
|
||
|
||
/* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не
|
||
* pop_level_tile() на каждый trob (тот свапал W0 каждый вызов = N свапов
|
||
* за кадр даже без анимации). Дальше цикл (с редроями) идёт с
|
||
* размапленным W0 — как раньше. */
|
||
{
|
||
uint8_t k;
|
||
pop_level_access_begin();
|
||
for (k = 0; k < trobs_count; k++)
|
||
trob_code[k] = pop_level_tile_raw(trobs[k].room, trobs[k].tilepos);
|
||
pop_level_access_end();
|
||
}
|
||
|
||
for (i = 0; i < trobs_count; i++) {
|
||
uint8_t room = trobs[i].room;
|
||
uint8_t tp = trobs[i].tilepos;
|
||
uint8_t code = trob_code[i];
|
||
uint8_t *mod = pop_trob_modif(room);
|
||
int8_t type = trobs[i].type;
|
||
|
||
switch (code) {
|
||
case TILE_SPIKE:
|
||
animate_spike(&mod[tp], &type);
|
||
break;
|
||
case TILE_OPENER:
|
||
case TILE_CLOSER:
|
||
animate_button(mod[tp], &type); /* mod[tp] = индекс LINKLOC кнопки */
|
||
break;
|
||
case TILE_GATE:
|
||
animate_door(&mod[tp], &type);
|
||
break;
|
||
default:
|
||
type = -1;
|
||
break;
|
||
}
|
||
trobs[i].type = type;
|
||
|
||
if (room == cur_room &&
|
||
(code == TILE_SPIKE || code == TILE_OPENER || code == TILE_CLOSER)) {
|
||
/* Редрой ТОЛЬКО при изменении видимого состояния (redraw_11h из
|
||
* SDLPoP). spike: draw-сигнатура = modifier; button: = «нажата»
|
||
* (таймер связи >1). Пока Кид держит кнопку — сигнатура стабильна
|
||
* → редроя нет. При изменении рисуем ТЕКУЩУЮ страницу сразу +
|
||
* <rest>=1 (вторая страница дабл-буфера следующим кадром через
|
||
* rest-цикл). tp/10,tp%10 считаем только в момент редроя (редко),
|
||
* а не каждый кадр. */
|
||
/* Сигнатура = ВИДИМЫЙ кадр, а не сырой modifier. Для пики кадр =
|
||
* (mod&0x80)?5:mod (как в draw_tile): весь ретракт-hold 0x80..0x8F
|
||
* (16 значений modifier) рисуется одним кадром 5 → сырой modifier
|
||
* дал бы 16 ложных редроёв «выдвинутой» неподвижной пики. */
|
||
uint8_t sig = (code == TILE_SPIKE)
|
||
? (uint8_t)((mod[tp] & 0x80) ? 5 : mod[tp])
|
||
: (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
|
||
if (sig != trob_drawn[tp]) {
|
||
uint8_t trow = tp / 10, tcol = tp % 10;
|
||
if (code == TILE_SPIKE) { pop_spike_redraw(trow, tcol); spike_rest[tp] = 1; }
|
||
else { pop_button_redraw(trow, tcol); button_rest[tp] = 1; }
|
||
trob_drawn[tp] = sig;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* compact: удалить завершённые (type < 0). */
|
||
for (i = 0; i < trobs_count; i++)
|
||
if (trobs[i].type >= 0) trobs[w++] = trobs[i];
|
||
trobs_count = w;
|
||
}
|