Files
Sprinter-SDCC/applications/PoP/roomtest/pop_ctrl.c
T
snark13 3983fa4513 PoP roomtest: HP-учёт, индикаторы HP, чит бессмертия; фикс кэша кадра
Боёвка (порт seg002/seg006):
- check_sword_hurting / check_hurting / check_sword_hurt / hurt_by_sword /
  take_hp через дельты; do_delta_hp сводит их раз в кадр;
- парирование (justblocked), refractimer после ранения стража;
- смерть по seq_71_dying — через неё же теперь работает чит K: страж
  действительно погибает, а не замирает на месте;
- парные окна Char/Opp: loadkid_and_opp / savekid_and_opp /
  saveshad_and_opp.

Индикаторы HP (порт draw_kid_hp / draw_guard_hp): Кид слева, страж справа.
Перерисовка ТОЛЬКО при изменении числа и тогда на ОБЕИХ страницах
дабл-буфера (счётчик hp_todo, иначе на второй странице осталось бы старое
значение и мерцало через кадр); pop_hp_invalidate при входе в комнату, где
фон перерисован целиком.

Чит I — бессмертие Кида (нашего изобретения, в оригинале его нет).
Перекрывает и путь «безоружного закалывают насмерть»: тот идёт мимо HP, и
без этого чит бесполезен ровно там, где нужен.

ДВА НАЙДЕННЫХ БАГА:
1. Полосу HP блитил row-major примитивом, а атласы Кида и стража хранятся
   COLUMN-major (ради бесплатного флипа) — марки выходили транспонированными.
   Теперь колоночный блит, стрелки как в оригинале.
2. Кэш кадра для ОТРИСОВКИ заполняли pop_savekid/pop_saveshad.  Любое окно
   Char БЕЗ play_seq — а это оба окна боёвки — записывало Киду кадр, который
   принадлежал СТРАЖУ, и kid_draw искал этот image в атласе Кида, рисуя
   произвольную позу.  Владельцем кэша стал load_frame: он один знает, чей
   кадр загружен (по Char.charid).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 18:43:39 +03:00

692 lines
30 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"
#include "pop_guard.h" /* charid, состояние меча, seq стража */
/* Отладочные клавиши осторожного шага (эмуляция 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_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
#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). */
int8_t control_x, control_y, control_shift;
/* НЕ static: в оригинале это глобалы, через которые ИИ стража подаёт
* СИНТЕТИЧЕСКИЙ ввод (autocontrol_*, seg002), а дальше работает тот же
* control(), что и у игрока. Поэтому шина общая. */
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). */
/* В оригинале seqtbl_offset_char работает с АКТИВНЫМ персонажем, и
* control() вызывается внутри окна Char. Раньше макрос вёл на
* kid_set_seq (пишет прямо в Kid) — после введения окна это ломало ввод:
* savekid затирал Kid копией Char со старой curr_seq. */
#define seqtbl_offset_char(seq) pop_char_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);
}
/* 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-запросы) — это
* полировка K3; K2b просто запускает run-jump. */
if (Char.frame >= FRAME_7_RUN) {
control_up = release_arrows();
seqtbl_offset_char(SEQ_4_RUN_JUMP);
}
}
static void safe_step(void); /* fwd */
static void draw_sword(void); /* fwd: нужен в control_standing */
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) {
Char.repeat = 1;
seqtbl_offset_char((uint8_t)(distance + SEQ_STEP_BASE));
} else if (pop_edge_type() == EDGE_WALL) {
/* вплотную к стене: маленький шаг-1 — check_bumped отскочит назад
* (шаг назад со звуком, как оригинал), НЕ seq_39 (шаг за край=падение). */
Char.repeat = 1;
seqtbl_offset_char((uint8_t)(1 + SEQ_STEP_BASE));
} else if (Char.repeat != 0) {
Char.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)
{
/* 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) {
control_forward = CONTROL_IGNORE;
seqtbl_offset_char(SEQ_79_CROUCH_HOP);
}
}
static void control_standing(void)
{
/* Shift в стойке над предметом — присесть к нему (seg005:0358, первая
* строка control_standing). Идёт ДО остальных shift-веток. */
if (control_shift2 == CONTROL_HELD && control_shift == CONTROL_HELD &&
get_item_action())
return;
/* Ветку «charid != kid && вниз+вперёд -> draw_sword» не портируем: у нас
* соперник до control_standing не доходит, его перехватывает
* control_guard_inactive в самом диспетчере (порядок как в seg005). */
/* seg005:352 — Кид с мечом ВИДИТ активного стража: достаёт клинок сам.
* Без этого игрок стоит с мечом в ножнах, пока его рубят. */
if (pop_have_sword) {
if (offguard != 0 && control_shift >= CONTROL_RELEASED) {
/* Меч убран намеренно (down в бою) — не доставать, пока не
* отпустят Shift; в оригинале это переход на loc_6213. */
} else if (can_guard_see_kid >= 2) {
int16_t d = pop_char_opp_dist();
if (d >= -10 && d < 90) {
holding_sword = 1;
/* (word)d < (word)-6 — беззнаковое: истинно для ВСЕГО, кроме
* −6..−1. То есть «страж вплотную за спиной» -> отступить,
* иначе достать меч. */
if (d >= -6 && d < 0) { back_pressed(); return; }
draw_sword();
return;
}
} else {
offguard = 0;
}
}
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 &&
(Char.frame == FRAME_7_RUN || Char.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();
}
/* draw_sword (seg005:945): достать меч. Кид переходит в seq_55 (подъём
* клинка со звуком), соперник — сразу в стойку en garde (seq_90). Стрелки
* гасятся, чтобы «вперёд», которым меч достали, не сработало второй раз
* уже как выпад. */
static void draw_sword(void)
{
uint8_t seq_id = SEQ_55_DRAW_SWORD;
control_forward = control_shift2 = release_arrows();
if (Char.charid == CHARID_0_KID) {
offguard = 0; /* TODO: play_sound(19) — звука пока нет */
} else if (Char.charid != CHARID_1_SHADOW) {
seq_id = SEQ_90_EN_GARDE; /* соперник встаёт сразу в стойку */
}
Char.sword = SWORD_2_DRAWN;
seqtbl_offset_char(seq_id);
}
/* control_guard_inactive (seg006:2123): неактивный страж реагирует ТОЛЬКО в
* кадре 166 (стойка) и только на «вниз»: вниз+вперёд — достать меч,
* вниз без «вперёд» — развернуться (stand flipped). */
static void control_guard_inactive(void)
{
if (Char.frame == FRAME_166_STAND_INACTIVE && control_down == CONTROL_HELD) {
if (control_forward == CONTROL_HELD) {
draw_sword();
} else {
control_down = CONTROL_IGNORE;
seqtbl_offset_char(SEQ_80_STAND_FLIPPED);
}
}
}
/* ---- Боёвка (seg005:964..) ---------------------------------------- *
* Всё это ветки ОБЩЕГО диспетчера: у стража тот же control(), просто
* control_* ему выставляет ИИ (autocontrol_*, guards.c), а не клавиатура. */
/* back_with_sword (seg005): шаг назад — только из стоек. */
static void back_with_sword(void)
{
uint8_t f = Char.frame;
if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
f == FRAME_171_STAND_WITH_SWORD) {
control_backward = CONTROL_IGNORE; /* без авто-повтора */
seqtbl_offset_char(SEQ_57_BACK_WITH_SWORD);
}
}
/* forward_with_sword (seg005): шаг вперёд; у стража своя последовательность. */
static void forward_with_sword(void)
{
uint8_t f = Char.frame;
if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
f == FRAME_171_STAND_WITH_SWORD) {
control_forward = CONTROL_IGNORE;
seqtbl_offset_char(Char.charid != CHARID_0_KID ? SEQ_56_GUARD_FORWARD
: SEQ_86_FORWARD_WITH_SWORD);
}
}
/* sword_strike (seg005): выпад из стойки/шага либо добивание после парирования. */
static void sword_strike(void)
{
uint8_t f = Char.frame;
uint8_t seq_id;
if (f == FRAME_157_WALK_WITH_SWORD || f == FRAME_158_STAND_WITH_SWORD ||
f == FRAME_170_STAND_WITH_SWORD || f == FRAME_171_STAND_WITH_SWORD ||
f == FRAME_165_WALK_WITH_SWORD) {
seq_id = (Char.charid == CHARID_0_KID) ? SEQ_75_STRIKE : SEQ_58_GUARD_STRIKE;
} else if (f == FRAME_150_PARRY || f == FRAME_161_PARRY) {
seq_id = SEQ_66_STRIKE_AFTER_PARRY;
} else {
return;
}
control_shift2 = CONTROL_IGNORE;
seqtbl_offset_char(seq_id);
}
/* parry (seg005): блок. Условие сильно разное у Кида и у соперника —
* соперник блокирует ТОЛЬКО на кадре 152 (второй кадр удара), а Кид ещё и
* по 153 (и тогда последовательность прокручивается сразу). */
static void parry(void)
{
uint8_t cf = Char.frame, of = Opp.frame;
uint8_t seq_id = SEQ_62_PARRY;
uint8_t do_play_seq = 0;
if (cf == FRAME_158_STAND_WITH_SWORD || cf == FRAME_170_STAND_WITH_SWORD ||
cf == FRAME_171_STAND_WITH_SWORD || cf == FRAME_168_BACK ||
cf == FRAME_165_WALK_WITH_SWORD) {
if (pop_char_opp_dist() >= 32 && Char.charid != CHARID_0_KID) {
back_with_sword();
return;
} else if (Char.charid == CHARID_0_KID) {
if (of == FRAME_168_BACK) return;
if (of != FRAME_151_STRIKE_1 && of != FRAME_152_STRIKE_2 &&
of != FRAME_162_BLOCK_TO_STRIKE) {
if (of == FRAME_153_STRIKE_3) do_play_seq = 1;
}
} else {
if (of != FRAME_152_STRIKE_2) return;
}
} else {
if (cf != FRAME_167_BLOCKED) return;
seq_id = SEQ_61_PARRY_AFTER_STRIKE;
}
control_up = CONTROL_IGNORE;
seqtbl_offset_char(seq_id);
if (do_play_seq) play_seq();
}
/* swordfight (seg005:0CDB) — разбор ввода в бою. Порядок веток важен:
* удар старше «убрать меч», тот старше блока, блок старше шагов. */
static void swordfight(void)
{
uint8_t f = Char.frame;
uint8_t seq_id;
if (f == FRAME_161_PARRY && control_shift2 >= CONTROL_RELEASED) {
seqtbl_offset_char(SEQ_57_BACK_WITH_SWORD); /* отскок после парирования */
return;
} else if (control_shift2 == CONTROL_HELD) {
if (Char.charid == CHARID_0_KID) kid_sword_strike = 15;
sword_strike();
if (control_shift2 == CONTROL_IGNORE) return;
}
if (control_down == CONTROL_HELD) {
if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
f == FRAME_171_STAND_WITH_SWORD) {
control_down = CONTROL_IGNORE;
Char.sword = SWORD_0_SHEATHED;
if (Char.charid == CHARID_0_KID) {
offguard = 1; /* убрал намеренно — сразу не доставать */
guard_refrac = 9; /* и страж на это время придержан */
holding_sword = 0;
seq_id = SEQ_93_PUT_SWORD_AWAY_FAST;
} else if (Char.charid == CHARID_1_SHADOW) {
seq_id = SEQ_92_PUT_SWORD_AWAY;
} else {
seq_id = SEQ_87_GUARD_BECOME_INACT; /* Кид ушёл из виду */
}
seqtbl_offset_char(seq_id);
}
} else if (control_up == CONTROL_HELD) {
parry();
} else if (control_forward == CONTROL_HELD) {
forward_with_sword();
} else if (control_backward == CONTROL_HELD) {
back_with_sword();
}
}
/* control_with_sword (seg005:964): пока соперник виден и близко — бой;
* иначе страж убирает меч, а Кид просто стоит. */
static void control_with_sword(void)
{
if (Char.action >= 2) return; /* actions_2_hang_climb и выше */
if (pop_tile_at(Char.curr_col, Char.curr_row) == 11 /* loose */ ||
can_guard_see_kid >= 2) {
int16_t d = pop_char_opp_dist();
if (d < 90) {
if (d >= 0) { swordfight(); return; }
/* Соперник за спиной: далеко — развернуться, вплотную — драться. */
if (d < -4) { seqtbl_offset_char(SEQ_60_TURN_WITH_SWORD); return; }
swordfight();
return;
}
}
/* Не видит / далеко. */
if (Char.charid < CHARID_2_GUARD) {
if (Char.frame == FRAME_171_STAND_WITH_SWORD) {
Char.sword = SWORD_0_SHEATHED;
seqtbl_offset_char(SEQ_92_PUT_SWORD_AWAY); /* страж убит */
}
} else {
swordfight();
}
}
/* control() — диспетчер по кадру (seg005.c:252) для АКТИВНОГО персонажа. */
void pop_control(void)
{
uint8_t f = Char.frame;
/* seg005 control(), самая первая ветка: персонаж УМИРАЕТ (alive >= 0).
* Из любой стойки он валится в seq_71_dying; остальное управление в
* этом состоянии не обрабатывается. Через это же место проходит чит K
* (pop_guard_kill ставит Guard.alive = 0). */
if (Char.alive >= 0) {
if (f == FRAME_15_STAND || f == FRAME_166_STAND_INACTIVE ||
f == FRAME_158_STAND_WITH_SWORD || f == FRAME_171_STAND_WITH_SWORD)
seqtbl_offset_char(SEQ_71_DYING);
return;
}
/* seg005 control(): в отскоке (bumped) и в свободном падении управление
* НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок
* прерывает присед после удара (medland/плита по голове) нажатием ↑, и
* последовательность не доигрывает свои dy(1)+dy(1), компенсирующие
* стартовый dy(-2) → Kid навсегда остаётся на 2px выше пола (баг «Y на
* пару пикселей меньше, чем должен»). */
if (Char.action == 5 || Char.action == 4) { /* bumped / in_freefall */
release_arrows();
return;
}
/* Порядок веток — как в seg005: меч старше charid, charid старше кадров.
* Пока Kid безоружен, обе ветки — про стража. */
if (Char.sword == SWORD_2_DRAWN)
control_with_sword();
else if (Char.charid >= CHARID_2_GUARD)
control_guard_inactive();
else 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 (Char.direction >= DIR_RIGHT) {
flip_control_x();
pop_control();
flip_control_x();
} else {
pop_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();
/* Окно Char вокруг диспетчера: control() работает с АКТИВНЫМ
* персонажем (как в оригинале), поэтому ввод игрока применяется к
* Киду только внутри loadkid/savekid. Кадр не перезагружаем —
* play_seq в kid_tick сделает это следующим шагом. */
pop_loadkid();
user_control(); /* -> pop_control(): может сменить Char.curr_seq */
pop_savekid_state();
save_ctrl_1();
}