Files
Sprinter-SDCC/applications/PoP/roomtest/pop_ctrl.c
T
snark13 63a3b60524 PoP roomtest: плита-потолок, порядок слоёв по SDLPoP, фиксы окклюзии и Y
Фича — плита-ПОТОЛОК (loose ряда 2 комнаты сверху, комн.5 (2,5) над комн.6):
- check_press (seg006:1683) портирован целиком: кадры виса 87..99 и подъёма
  135..140 «нажимают» тайл, за который Kid держится; кадр 79 — удар снизу
  (с проверкой action, как в оригинале); стояние — тайл под ним;
- ряд −1 в pop_map.get_tile = ряд 2 комнаты сверху (порт find_room_of_tile),
  состояние тряски/падения — pop_ceil_modif[10]; do_knock(-1) трясёт потолок;
- падение: mob переведён на координаты оригинала (y_loose_land/y_something),
  спавнится в ряду −1, по move_loose находит пол → loose_land: debris +
  do_knock; heal коридора — по прошлой позиции ДЛЯ КАЖДОЙ страницы;
- урон: check_loose_fall_on_kid + fell_on_your_head (seq_52/seq_22, −1 HP)
  и «брызги» draw_hurt_splash (kid image 218);
- колодец наверх: leave_room «вверх» проверяется ПЕРВЫМ (иначе y≈248 при
  подтягивании ловил check_leave_below и ронял Kid из уровня);
- персистентность: общий tile-override (щебень внизу, пустота в комнате
  сверху) вместо debris-only.

Окклюзия — приведена к оригиналу (найдено трассировкой собранного SDLPoP):
- draw_tile_right/anim_right/draw_loose кладут спрайты в add_backtable
  НАПРЯМУЮ, минуя ptr_add_table → правая грань соседа («шахматка» столба 93),
  blueline и грани loose/пик соседа ВСЕГДА под персонажем.  draw_other_overlay
  поверх Kid даёт только: 42 при левом соседе-поле, base, свои пики, bottom
  (overlay_mid_tile);
- порядок объектов: тайл Kid (set_objtile_at_char) и порядок обхода
  (ряды 2→0, колонки 0→9), внутри тайла — сортировка по y (sort_curr_objs);
  то же правило для падающей плиты (pop_loose_mob_draw_over);
- fore-слой рисуется ПОСЛЕ оверлеев (foretable после midtable), иначе тёмный
  скос floor_left_overlay ложился поверх колонны;
- fore_tile больше не рисует bottom_id (в оригинале он в backtable) — минус
  2..4 блита за кадр;
- полоса потолка поверх Kid = draw_tile_bottom(1)+draw_loose(1)+draw_tile_fore
  (arg_0=1 добавляет в foretable), остальное — под ним;
- следы оверлея восстанавливаются на ОБЕИХ страницах (pop_fore_heal).

Прочее:
- control(): при action bumped/in_freefall управление игнорируется целиком
  (seg005) — иначе прерванный присед терял dy(1)+dy(1) из medland и Kid
  навсегда оставался на 2px выше пола;
- check_bumped: стена ищется по колонке переднего края (а не curr_col) +
  гард разворота — Kid больше не проходит сквозь опущенную решётку шва;
- pop_floor_bake для смены floor→debris (bake_empty стирал верх стены ряда
  ниже — чёрный прямоугольник);
- в pop_room_redraw_seam_left восстанавливается полоса потолка над решёткой.

Проверено в MAME (bridge); эталон — собранный SDLPoP из applications/PoP/SDLPoP.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:35:25 +03:00

433 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pop_ctrl.c — управление Kid (Фаза K2b). Порт SDLPoP:
* вход — read_user_control/user_control/rest_ctrl_1/save_ctrl_1
* (seg006.c) поверх held-state <kbd_raw.h>;
* диспетчер — control() + control_standing/turning/startrun/running/
* crouched (seg005.c), Kid-only ветки (без меча/стражи);
* действия — forward/back/up/down_pressed, crouch, standing_jump,
* run_jump, safe_step (seg005.c).
*
* Map-запросы (стена/край/захват) ЗАСТАБЛЕНЫ «открытый пол» (map_* ниже) —
* реальная коллизия в K3/K4. Поэтому: forward -> всегда бег, down -> присед,
* back -> разворот, up+forward -> прыжок с места; osторожный шаг и climb
* помечены TODO(K3/K4).
*
* Значения CONTROL_* (types.h): RELEASED=0, IGNORE=1, HELD=-1;
* X: LEFT/FORWARD=-1, RIGHT/BACKWARD=+1; Y: UP=-1, DOWN=+1.
*/
#include <stdint.h>
#include <kbd_raw.h>
#include "pop_kid.h"
#include "pop_map.h"
/* Отладочные клавиши осторожного шага (эмуляция Shift+стрелка) для тестов в
* MAME, где Shift трудно подать: J = шаг влево, L = шаг вправо. PS/2 set2
* scancodes. TODO: убрать после отладки. */
#define KBD_DBG_STEPL 0x3B /* J */
#define KBD_DBG_STEPR 0x4B /* L */
/* ---- CONTROL_* и seq/frame id (подмножество types.h) --------------- */
#define CONTROL_RELEASED 0
#define CONTROL_IGNORE 1
#define CONTROL_HELD (-1)
#define CONTROL_HELD_LEFT (-1)
#define CONTROL_HELD_RIGHT 1
#define CONTROL_HELD_FORWARD (-1)
#define CONTROL_HELD_BACKWARD 1
#define CONTROL_HELD_UP (-1)
#define CONTROL_HELD_DOWN 1
#define DIR_RIGHT 0 /* dir_0_right */
#define SEQ_1_START_RUN 1
#define SEQ_2_STAND 2
#define SEQ_3_STANDING_JUMP 3
#define SEQ_4_RUN_JUMP 4
#define SEQ_5_TURN 5
#define SEQ_6_RUN_TURN 6
#define SEQ_13_STOP_RUN 13
#define SEQ_26_CROUCH_WHILE_RUNNING 26
#define SEQ_43_START_RUN_AFTER_TURN 43
#define SEQ_49_STAND_UP_FROM_CROUCH 49
#define SEQ_50_CROUCH 50
#define SEQ_79_CROUCH_HOP 79
#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
#define SEQ_14_JUMP_INTO_CEILING 14 /* прыжок вверх: сверху стена/пол */
#define SEQ_28_JUMP_NOTHING_ABOVE 28 /* прыжок вверх: сверху пусто */
#define SEQ_25_HANG_AGAINST_WALL 25 /* вис у стены (Shift в висе) */
#define FRAME_7_RUN 7
#define FRAME_11_RUN 11
#define FRAME_15_STAND 15
#define FRAME_48_TURN 48
#define FRAME_50_TURN 50
#define FRAME_67_JUMPUP1 67
#define FRAME_70_JUMPHANG 70
#define FRAME_87_HANG1 87
#define FRAME_109_CROUCH 109
/* ---- Состояние управления (seg006 globals) ------------------------ *
* control_x/y/shift — «сырое» состояние осей ЭТОГО кадра (абсолютно:
* X LEFT/-1..RIGHT/+1); *_forward/backward/up/down/shift2 — конечный
* автомат held/released/ignore (авто-повтор); ctrl1_* — их память между
* кадрами (rest/save). */
static int8_t control_x, control_y, control_shift;
static int8_t control_forward, control_backward, control_up, control_down, control_shift2;
static int8_t ctrl1_forward, ctrl1_backward, ctrl1_up, ctrl1_down, ctrl1_shift2;
/* ---- seqtbl_offset_char: сменить последовательность Kid ------------ *
* Обёртка над kid_set_seq (единая точка в pop_kid — kid_seq_off/kid_seqtbl
* не дублируются в этот TU). */
#define seqtbl_offset_char(seq) kid_set_seq(seq)
static int8_t release_arrows(void)
{
control_backward = control_forward = control_up = control_down = CONTROL_RELEASED;
return CONTROL_IGNORE; /* seg006: return 1 */
}
/* Край/стена впереди — из pop_map (get_edge_distance): pop_edge_distance()
* возвращает дистанцию и ставит pop_edge_type() = EDGE_WALL/FLOOR/CLOSER. */
/* ---- Действия (seg005) -------------------------------------------- */
static void crouch(void)
{
seqtbl_offset_char(SEQ_50_CROUCH);
control_down = release_arrows();
}
static void standing_jump(void)
{
control_up = control_forward = CONTROL_IGNORE;
seqtbl_offset_char(SEQ_3_STANDING_JUMP);
}
static void run_jump(void)
{
/* Оригинал выравнивает Kid по краю пола (нужны tile-запросы) — это
* полировка K3; K2b просто запускает run-jump. */
if (Kid.frame >= FRAME_7_RUN) {
control_up = release_arrows();
seqtbl_offset_char(SEQ_4_RUN_JUMP);
}
}
static void safe_step(void); /* fwd */
static void forward_pressed(void)
{
int distance = pop_edge_distance();
if (pop_edge_type() == EDGE_WALL && distance < 8) {
/* рядом стена — вместо бега осторожный шаг (в стену не разбежится) */
if (control_forward == CONTROL_HELD)
safe_step();
} else {
seqtbl_offset_char(SEQ_1_START_RUN);
}
}
static void back_pressed(void)
{
control_backward = release_arrows();
/* Kid без меча -> просто разворот. */
seqtbl_offset_char(SEQ_5_TURN);
}
static void down_pressed(void)
{
control_down = CONTROL_IGNORE; /* без авто-повтора */
/* У края пола (край сзади) со зацепом снизу — спуск на этаж (seq_68);
* у передней кромки — отступ; иначе присед. Решает pop_down_action. */
if (!pop_down_action())
crouch();
}
/* Прыжок вверх в стойке (seg005 check_jump_up): чистый вертикальный прыжок
* ЛИБО прыжок-с-зацепом за уступ выше (запрыгнуть на этаж вверх). Выбор seq
* + выравнивание Kid.x — в pop_jump_up_seq (pop_map). */
static void jump_up(void)
{
control_up = release_arrows();
seqtbl_offset_char(pop_jump_up_seq());
}
static void up_pressed(void)
{
/* (дверь уровня/телепорт — нет в тесте.) ↑+forward = прыжок с места;
* иначе вертикальный прыжок вверх. */
if (control_x == CONTROL_HELD_FORWARD)
standing_jump();
else
jump_up();
}
/* Осторожный шаг (Shift+forward): шаг РОВНО до кромки/стены. distance>0 —
* шаг длины distance (seq 29..42 = step1..step14); ==0 у края — шаг на
* кромку (seq_44) либо неосторожный шаг за край (seq_39). Порт seg005. */
static void safe_step(void)
{
int distance;
control_shift2 = CONTROL_IGNORE; /* без авто-повтора */
control_forward = CONTROL_IGNORE;
distance = pop_edge_distance();
if (distance) {
Kid.repeat = 1;
seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE));
} else if (pop_edge_type() == EDGE_WALL) {
/* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад
* (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */
Kid.repeat = 1;
seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE));
} else if (Kid.repeat != 0) {
Kid.repeat = 0;
seqtbl_offset_char(SEQ_44_STEP_ON_EDGE);
} else {
seqtbl_offset_char(SEQ_39_UNSAFE_STEP); /* реальный обрыв (edge=CLOSER) */
}
}
/* ---- control_* обработчики состояний ------------------------------ */
static void control_crouched(void)
{
if (control_y != CONTROL_HELD_DOWN) {
seqtbl_offset_char(SEQ_49_STAND_UP_FROM_CROUCH);
} else if (control_forward == CONTROL_HELD) {
control_forward = CONTROL_IGNORE;
seqtbl_offset_char(SEQ_79_CROUCH_HOP);
}
}
static void control_standing(void)
{
/* Kid charid_0, без меча: остаётся нижняя часть control_standing. */
if (control_shift == CONTROL_HELD) {
if (control_backward == CONTROL_HELD) back_pressed();
else if (control_up == CONTROL_HELD) up_pressed();
else if (control_down == CONTROL_HELD) down_pressed();
else if (control_x == CONTROL_HELD_FORWARD && control_forward == CONTROL_HELD)
safe_step();
} else if (control_forward == CONTROL_HELD) {
if (control_up == CONTROL_HELD) standing_jump(); /* keyboard mode */
else forward_pressed();
} else if (control_backward == CONTROL_HELD) {
back_pressed();
} else if (control_up == CONTROL_HELD) {
if (control_forward == CONTROL_HELD) standing_jump();
else up_pressed();
} else if (control_down == CONTROL_HELD) {
down_pressed();
} else if (control_x == CONTROL_HELD_FORWARD) {
forward_pressed();
}
}
static void control_turning(void)
{
if (control_shift >= CONTROL_RELEASED &&
control_x == CONTROL_HELD_FORWARD && control_y >= CONTROL_RELEASED) {
seqtbl_offset_char(SEQ_43_START_RUN_AFTER_TURN);
}
}
static void control_startrun(void)
{
if (control_y == CONTROL_HELD_UP && control_x == CONTROL_HELD_FORWARD)
standing_jump();
}
static void control_running(void)
{
/* Стену НЕ обрабатываем тут — check_bumped (pop_phys_tick) даёт отскок
* seq_47 при беге в стену (как оригинал), с корректной позицией. */
if (control_x == CONTROL_RELEASED &&
(Kid.frame == FRAME_7_RUN || Kid.frame == FRAME_11_RUN)) {
control_forward = release_arrows();
seqtbl_offset_char(SEQ_13_STOP_RUN);
} else if (control_x == CONTROL_HELD_BACKWARD) {
control_backward = release_arrows();
seqtbl_offset_char(SEQ_6_RUN_TURN);
} else if (control_y == CONTROL_HELD_UP && control_up == CONTROL_HELD) {
run_jump();
} else if (control_down == CONTROL_HELD) {
control_down = CONTROL_IGNORE;
seqtbl_offset_char(SEQ_26_CROUCH_WHILE_RUNNING);
}
}
/* ---- Вис (seg005 control_hanging/can_climb_up/hang_fall) ---------- *
* Тайловые решения (можно ли подтянуться, куда сорваться) — в pop_map;
* здесь управление осями control_*. */
static void hang_fall(void)
{
control_down = release_arrows();
seqtbl_offset_char(pop_hang_fall_seq()); /* seq_23/seq_11 (+ выравнивание x) */
}
static void can_climb_up(void)
{
control_up = control_shift2 = release_arrows();
seqtbl_offset_char(pop_hang_climb_up_seq()); /* seq_10 / seq_73 */
}
static void control_hanging(void)
{
if (control_y == CONTROL_HELD && pop_hang_grab_ready()) {
can_climb_up(); /* ↑ — подтянуться */
} else if (control_shift == CONTROL_HELD) {
int8_t w = pop_hang_wall();
if (w > 0) seqtbl_offset_char(SEQ_25_HANG_AGAINST_WALL); /* у стены */
else if (w < 0) hang_fall(); /* сорваться */
/* w==0: сверху пол — держимся (ничего) */
} else {
hang_fall(); /* отпустил Shift — сорваться */
}
}
/* control_jumpup (seg005): в старте прыжка вверх ←/→ или forward → перевести
* в прыжок с места. */
static void control_jumpup(void)
{
if (control_x == CONTROL_HELD_FORWARD || control_forward == CONTROL_HELD)
standing_jump();
}
/* control() — диспетчер по кадру (seg005.c:252), Kid жив, без меча. */
static void control(void)
{
uint8_t f = Kid.frame;
/* seg005 control(): в отскоке (bumped) и в свободном падении управление
* НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок
* прерывает присед после удара (medland/плита по голове) нажатием ↑, и
* последовательность не доигрывает свои dy(1)+dy(1), компенсирующие
* стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на
* пару пикселей меньше, чем должен»). */
if (Kid.action == 5 || Kid.action == 4) { /* bumped / in_freefall */
release_arrows();
return;
}
if (f == FRAME_15_STAND || (f >= FRAME_50_TURN && f < 53))
control_standing();
else if (f == FRAME_48_TURN)
control_turning();
else if (f < 4)
control_startrun();
else if (f >= FRAME_67_JUMPUP1 && f < FRAME_70_JUMPHANG)
control_jumpup();
else if (f < FRAME_15_STAND)
control_running();
else if (f >= FRAME_87_HANG1 && f < 100)
control_hanging();
else if (f == FRAME_109_CROUCH)
control_crouched();
}
/* ---- Модель ввода (seg006) ---------------------------------------- */
static void read_input(void)
{
uint8_t l = kbd_raw_down(KBD_LEFT);
uint8_t r = kbd_raw_down(KBD_RIGHT);
uint8_t u = kbd_raw_down(KBD_UP);
uint8_t d = kbd_raw_down(KBD_DOWN);
/* Отладочный осторожный шаг: J/L = Shift+влево/вправо (см. KBD_DBG_STEP*). */
uint8_t sl = kbd_raw_down(KBD_DBG_STEPL);
uint8_t sr = kbd_raw_down(KBD_DBG_STEPR);
if (sl) l = 1;
if (sr) r = 1;
control_x = r ? CONTROL_HELD_RIGHT : (l ? CONTROL_HELD_LEFT : CONTROL_RELEASED);
control_y = d ? CONTROL_HELD_DOWN : (u ? CONTROL_HELD_UP : CONTROL_RELEASED);
control_shift = (sl || sr || kbd_raw_down(KBD_LSHIFT) || kbd_raw_down(KBD_RSHIFT))
? CONTROL_HELD : CONTROL_RELEASED;
}
/* read_user_control (seg006:1557): «свежее нажатие» (RELEASED->HELD) с
* подавлением авто-повтора (IGNORE держится, пока клавишу не отпустят). */
static void read_user_control(void)
{
if (control_forward >= CONTROL_RELEASED) {
if (control_x == CONTROL_HELD_FORWARD) {
if (control_forward == CONTROL_RELEASED) control_forward = CONTROL_HELD;
} else control_forward = CONTROL_RELEASED;
}
if (control_backward >= CONTROL_RELEASED) {
if (control_x == CONTROL_HELD_BACKWARD) {
if (control_backward == CONTROL_RELEASED) control_backward = CONTROL_HELD;
} else control_backward = CONTROL_RELEASED;
}
if (control_up >= CONTROL_RELEASED) {
if (control_y == CONTROL_HELD_UP) {
if (control_up == CONTROL_RELEASED) control_up = CONTROL_HELD;
} else control_up = CONTROL_RELEASED;
}
if (control_down >= CONTROL_RELEASED) {
if (control_y == CONTROL_HELD_DOWN) {
if (control_down == CONTROL_RELEASED) control_down = CONTROL_HELD;
} else control_down = CONTROL_RELEASED;
}
if (control_shift2 >= CONTROL_RELEASED) {
if (control_shift == CONTROL_HELD) {
if (control_shift2 == CONTROL_RELEASED) control_shift2 = CONTROL_HELD;
} else control_shift2 = CONTROL_RELEASED;
}
}
static void flip_control_x(void)
{
int8_t t;
control_x = (int8_t)-control_x;
t = control_forward; control_forward = control_backward; control_backward = t;
}
/* user_control (seg006:0DC0): «вперёд» относительно направления Kid. */
static void user_control(void)
{
if (Kid.direction >= DIR_RIGHT) {
flip_control_x();
control();
flip_control_x();
} else {
control();
}
}
static void rest_ctrl_1(void)
{
control_forward = ctrl1_forward; control_backward = ctrl1_backward;
control_up = ctrl1_up; control_down = ctrl1_down; control_shift2 = ctrl1_shift2;
}
static void save_ctrl_1(void)
{
ctrl1_forward = control_forward; ctrl1_backward = control_backward;
ctrl1_up = control_up; ctrl1_down = control_down; ctrl1_shift2 = control_shift2;
}
/* ---- Публичный API ------------------------------------------------ */
int pop_ctrl_init(void)
{
control_forward = control_backward = control_up = control_down = CONTROL_RELEASED;
control_shift2 = CONTROL_RELEASED;
ctrl1_forward = ctrl1_backward = ctrl1_up = ctrl1_down = ctrl1_shift2 = CONTROL_RELEASED;
return kbd_raw_open();
}
void pop_ctrl_close(void)
{
kbd_raw_close();
}
int8_t pop_ctrl_shift_held(void)
{
return (int8_t)(control_shift == CONTROL_HELD);
}
void pop_ctrl_tick(void)
{
kbd_raw_sync(); /* снять залипшее после Rx-overrun SIO */
rest_ctrl_1();
read_input();
read_user_control();
user_control(); /* -> control(): может сменить Kid.curr_seq */
save_ctrl_1();
}