/* * pop_ctrl.c — управление Kid (Фаза K2b). Порт SDLPoP: * вход — read_user_control/user_control/rest_ctrl_1/save_ctrl_1 * (seg006.c) поверх held-state ; * диспетчер — 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 #include #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_78_DRINK 78 /* выпить зелье */ #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; } if (a == 3) { /* зелье — выпить (get_item) */ control_shift2 = CONTROL_IGNORE; seqtbl_offset_char(SEQ_78_DRINK); 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(); }