diff --git a/applications/PoP/roomtest/guards.c b/applications/PoP/roomtest/guards.c index 99fe69b..552f10f 100644 --- a/applications/PoP/roomtest/guards.c +++ b/applications/PoP/roomtest/guards.c @@ -113,19 +113,6 @@ void pop_check_can_guard_see_kid(void) __banked } } -/* char_opp_dist (seg006:2135): >0 — соперник ПЕРЕД Char, <0 — за спиной. - * +13 при встречных направлениях: у стоящих лицом друг к другу «ноль» - * приходится на ширину тайла между ними. */ -static int16_t char_opp_dist(void) -{ - int16_t distance; - if (Char.room != Opp.room) return 999; - distance = (int16_t)Opp.x - (int16_t)Char.x; - if (Char.direction < 0) distance = -distance; - if (distance >= 0 && Char.direction != Opp.direction) distance += 13; - return distance; -} - /* ---- Синтетический ввод (seg002:0706..) --------------------------- */ static void move_0_nothing(void) { @@ -137,11 +124,31 @@ static void move_1_forward(void) control_x = POP_CONTROL_HELD_FORWARD; control_forward = POP_CONTROL_HELD; } +static void move_2_backward(void) +{ + control_backward = POP_CONTROL_HELD; + control_x = POP_CONTROL_HELD_BACKWARD; +} +static void move_3_up(void) +{ + control_y = POP_CONTROL_HELD_UP; + control_up = POP_CONTROL_HELD; +} static void move_4_down(void) { control_down = POP_CONTROL_HELD; control_y = POP_CONTROL_HELD_DOWN; } +static void move_6_shift(void) +{ + control_shift = POP_CONTROL_HELD; + control_shift2 = POP_CONTROL_HELD; +} +static void move_down_back(void) +{ + control_down = POP_CONTROL_HELD; + move_2_backward(); +} static void move_down_forw(void) { control_down = POP_CONTROL_HELD; @@ -155,7 +162,7 @@ static void autocontrol_guard_inactive(void) { int16_t distance; if (Kid.alive >= 0) return; - distance = char_opp_dist(); + distance = pop_char_opp_dist(); /* В оригинале условие записано как (word)distance < (word)-8 — * беззнаковое сравнение, истинное для ВСЕГО, кроме диапазона −8..−1. * То есть: Кид не «вплотную за спиной». */ @@ -176,13 +183,163 @@ static void autocontrol_guard_inactive(void) } } -/* autocontrol_guard (seg002:701) + autocontrol_opponent (seg002:628) для - * стража. Активная ветка (меч обнажён) — следующим шагом. */ +/* ---- Активный страж (меч обнажён), seg002:737.. -------------------- * + * Таблицы вероятностей по 12 градациям мастерства (seg002:26..38). Бросок + * — prandom(255) с СОБСТВЕННЫМ сидом: у нас последовательности разведены + * по доменам (кладка / анимация тайлов / бой), см. pop_geom.h. */ +static const uint8_t STRIKEPROB[NUM_GUARD_SKILLS] = { 61,100, 61, 61, 61, 40,100,220, 0, 48, 32, 48}; +static const uint8_t RESTRIKEPROB[NUM_GUARD_SKILLS] = { 0, 0, 0, 5, 5,175, 16, 8, 0,255,255,150}; +static const uint8_t BLOCKPROB[NUM_GUARD_SKILLS] = { 0,150,150,200,200,255,200,250, 0,255,255,255}; +static const uint8_t IMPBLOCKPROB[NUM_GUARD_SKILLS] = { 0, 61, 61,100,100,145,100,250, 0,145,255,175}; +static const uint8_t ADVPROB[NUM_GUARD_SKILLS] = {255,200,200,200,255,255,200, 0, 0,255,100,100}; + +/* Бросок «сработало ли»: в оригинале prob > prandom(255). */ +static uint8_t roll(uint8_t prob) +{ + return (uint8_t)(prob > (uint8_t)pop_prandom(&pop_fight_seed, 255)); +} + +/* guard_advance (seg002:0AF5): наступать. Мастерство 0 наступает всегда, + * остальные — только пока Кид не замахнулся. */ +static void guard_advance(void) +{ + if (guard_skill == 0 || kid_sword_strike == 0) { + if (roll(ADVPROB[guard_skill])) move_1_forward(); + } +} + +/* guard_block (seg002:0B1D): блокировать замах Кида. Если Кид только что + * пробил блок (justblocked) — своя, «импровизационная» вероятность. */ +static void guard_block(void) +{ + uint8_t of = Opp.frame; + if (of == FRAME_152_STRIKE_2 || of == FRAME_153_STRIKE_3 || + of == FRAME_162_BLOCK_TO_STRIKE) { + if (roll(justblocked ? IMPBLOCKPROB[guard_skill] + : BLOCKPROB[guard_skill])) + move_3_up(); + } +} + +/* guard_strike (seg002:0B73): бить. По замаху Кида не бьём (кадры 169/151); + * из своего парирования — по отдельной вероятности «добить». */ +static void guard_strike(void) +{ + uint8_t of = Opp.frame, cf = Char.frame; + if (of == FRAME_169_BEGIN_BLOCK || of == FRAME_151_STRIKE_1) return; + if (cf == FRAME_161_PARRY || cf == FRAME_150_PARRY) { + if (roll(RESTRIKEPROB[guard_skill])) move_6_shift(); + } else { + if (roll(STRIKEPROB[guard_skill])) move_6_shift(); + } +} + +/* autocontrol_guard_kid_armed (seg002:0AC1): Кид с мечом. Вплотную и + * далеко — наступаем; в «рабочей» дистанции 10..28 сначала пробуем блок, и + * только отдышавшись бьём. */ +static void autocontrol_guard_kid_armed(int16_t distance) +{ + if (distance < 10 || distance >= 29) { + guard_advance(); + } else { + guard_block(); + if (guard_refrac == 0) { + if (distance < 12 || distance >= 29) guard_advance(); + else guard_strike(); + } + } +} + +/* autocontrol_guard_kid_in_sight (seg002:0A93). */ +static void autocontrol_guard_kid_in_sight(int16_t distance) +{ + if (Opp.sword == SWORD_2_DRAWN) { + autocontrol_guard_kid_armed(distance); + } else if (guard_refrac == 0) { + /* Кид безоружен: подойти и рубить. */ + if (distance < 29) move_6_shift(); + else move_1_forward(); + } +} + +/* Тайл перед стражем / через один (порт get_tile_infrontof_char и + * get_tile_infrontof2_char, seg004) — но для Char, а не для Kid. */ +static uint8_t tile_infront(int8_t step) +{ + int8_t d = (int8_t)(Char.direction < 0 ? -step : step); + return pop_tile_at((int8_t)(Char.curr_col + d), Char.curr_row); +} + +/* autocontrol_guard_kid_far (seg002:09CB): идём, если впереди есть пол хотя + * бы через тайл; иначе пятимся (не шагать в пропасть). */ +static void autocontrol_guard_kid_far(void) +{ + if (tile_is_floor(tile_infront(1)) || tile_is_floor(tile_infront(2))) + move_1_forward(); + else + move_2_backward(); +} + +/* autocontrol_guard_active (seg002:737). */ +static void autocontrol_guard_active(void) +{ + uint8_t cf = Char.frame; + uint8_t of; + int16_t distance; + + /* Кадр 166 (неактивная стойка) и всё, что ниже 150, — не боевые позы. */ + if (cf == FRAME_166_STAND_INACTIVE || cf < 150 || can_guard_see_kid == 1) + return; + + if (can_guard_see_kid == 0) { + /* TODO: droppedout -> guard_follows_kid_down (seg002:09F8) — вместе + * с физикой стража; скелета (charid 4) ветка тоже не берёт. */ + if (Char.charid != CHARID_4_SKELETON) move_down_back(); + return; + } + + /* can_guard_see_kid == 2 */ + of = Opp.frame; + distance = pop_char_opp_dist(); + /* Кид падает/приземляется в отскоке — не мешаем ему, ждём. */ + if (distance >= 12 && of >= 102 && of < 118 && Opp.action == 5) + return; + + if (distance < 35) { + if ((Char.sword < SWORD_2_DRAWN && distance < 8) || distance < 12) { + /* Слишком близко: если смотрим в одну сторону — развернуться. */ + if (Char.direction == Opp.direction) move_2_backward(); + else move_1_forward(); + } else { + autocontrol_guard_kid_in_sight(distance); + } + return; + } + + if (guard_refrac != 0) return; + if (Char.direction != Opp.direction) { + /* Кид разбежался — встречаем ударом, а не шагом. */ + if (of >= 7 && of < 15) { /* бег */ + if (distance < 40) move_6_shift(); + return; + } else if (of >= 34 && of < 44) { /* разбег в прыжок */ + if (distance < 50) move_6_shift(); + return; + } + } + autocontrol_guard_kid_far(); +} + +/* autocontrol_guard (seg002:701) + autocontrol_opponent (seg002:628). */ static void autocontrol_opponent(void) { move_0_nothing(); - if (Char.sword < SWORD_2_DRAWN) - autocontrol_guard_inactive(); + /* Таймеры боя убывают раз в кадр — здесь, как в оригинале. */ + if (justblocked) --justblocked; + if (kid_sword_strike) --kid_sword_strike; + if (guard_refrac) --guard_refrac; + if (Char.sword < SWORD_2_DRAWN) autocontrol_guard_inactive(); + else autocontrol_guard_active(); } /* play_guard (seg006): ИИ + общий диспетчер. */ diff --git a/applications/PoP/roomtest/pop_ctrl.c b/applications/PoP/roomtest/pop_ctrl.c index edc5da4..35b9369 100644 --- a/applications/PoP/roomtest/pop_ctrl.c +++ b/applications/PoP/roomtest/pop_ctrl.c @@ -355,10 +355,150 @@ static void control_guard_inactive(void) } } -/* control_with_sword (seg005:964) — боёвка. Порт следующим шагом; пока - * персонаж с обнажённым мечом просто держит стойку. */ +/* ---- Боёвка (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) { + /* TODO: offguard/holding_sword — вместе с боёвкой Кида. */ + guard_refrac = 9; + 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) для АКТИВНОГО персонажа. */ diff --git a/applications/PoP/roomtest/pop_guard.c b/applications/PoP/roomtest/pop_guard.c index c37af4e..06cf56c 100644 --- a/applications/PoP/roomtest/pop_guard.c +++ b/applications/PoP/roomtest/pop_guard.c @@ -17,6 +17,13 @@ uint8_t guardhp_curr, guardhp_max; int8_t guardhp_delta; int8_t can_guard_see_kid; int8_t is_guard_notice; +uint8_t guard_skill; +int8_t guard_refrac, justblocked, kid_sword_strike; + +/* Сид бросков боёвки. В оригинале генератор один на всё, у нас + * последовательности разведены по доменам (кладка / анимация тайлов / бой), + * см. pop_geom.h — иначе перерисовка стены сбивала бы броски стража. */ +pop_rnd_t pop_fight_seed; /* Кадр стража (порт cur_frame для Char). Глобальный, а не статик: его * заполняет логика (W1/W2), а читает отрисовка из резидента W3. */ @@ -36,6 +43,7 @@ void pop_guard_reset(void) guardhp_delta = 0; can_guard_see_kid = 0; is_guard_notice = 0; + guard_refrac = justblocked = kid_sword_strike = 0; } /* kill guard cheat (seg000:786): @@ -84,7 +92,12 @@ void pop_guard_enter(uint8_t room) Guard.alive = -1; Guard.action = 1; /* actions_1_run_jump */ Guard.fall_x = Guard.fall_y = 0; - guardhp_max = guardhp_curr = 3; /* get_guard_hp: skill 0, уровень 1 */ + /* enter_guard (seg002:214): мастерство из данных уровня, 12 градаций; + * всё, что вне диапазона, оригинал сводит к 3. */ + guard_skill = (uint8_t)(skill < NUM_GUARD_SKILLS ? skill : 3); + /* get_guard_hp (seg002:54): extrastrength[skill] + tbl_guard_hp[уровень]. + * Уровень 1 -> 3; extrastrength ненулевой только у skill 4. */ + guardhp_max = guardhp_curr = (uint8_t)(3 + (guard_skill == 4 ? 1 : 0)); guardhp_delta = 0; /* enter_guard (seg002:0208): seqtbl_offset_char(seq_77_guard_stand_inactive). * Последовательность крутит общий интерпретатор (pop_guard_play), а diff --git a/applications/PoP/roomtest/pop_guard.h b/applications/PoP/roomtest/pop_guard.h index a243a80..a10445b 100644 --- a/applications/PoP/roomtest/pop_guard.h +++ b/applications/PoP/roomtest/pop_guard.h @@ -20,15 +20,42 @@ #include #include "kid_data.h" /* kframe */ #include "pop_char.h" +#include "pop_geom.h" /* pop_rnd_t — сид бросков боёвки */ -/* seqids (types.h:1052+) — последовательности стража. */ +/* seqids (types.h:1052+) — последовательности боя и стойки. */ #define SEQ_55_DRAW_SWORD 55 +#define SEQ_56_GUARD_FORWARD 56 /* шаг вперёд с мечом (страж) */ +#define SEQ_57_BACK_WITH_SWORD 57 +#define SEQ_58_GUARD_STRIKE 58 +#define SEQ_60_TURN_WITH_SWORD 60 +#define SEQ_61_PARRY_AFTER_STRIKE 61 +#define SEQ_62_PARRY 62 +#define SEQ_66_STRIKE_AFTER_PARRY 66 +#define SEQ_75_STRIKE 75 /* удар (Кид) */ #define SEQ_77_GUARD_STAND_INACTIVE 77 #define SEQ_80_STAND_FLIPPED 80 +#define SEQ_86_FORWARD_WITH_SWORD 86 /* шаг вперёд с мечом (Кид) */ +#define SEQ_87_GUARD_BECOME_INACT 87 #define SEQ_90_EN_GARDE 90 +#define SEQ_92_PUT_SWORD_AWAY 92 +#define SEQ_93_PUT_SWORD_AWAY_FAST 93 -/* frameids (types.h:927) и состояния меча (types.h:332). */ +/* frameids (types.h:911+) и состояния меча (types.h:332). */ +#define FRAME_150_PARRY 150 +#define FRAME_151_STRIKE_1 151 +#define FRAME_152_STRIKE_2 152 +#define FRAME_153_STRIKE_3 153 +#define FRAME_157_WALK_WITH_SWORD 157 +#define FRAME_158_STAND_WITH_SWORD 158 +#define FRAME_161_PARRY 161 +#define FRAME_162_BLOCK_TO_STRIKE 162 +#define FRAME_165_WALK_WITH_SWORD 165 #define FRAME_166_STAND_INACTIVE 166 +#define FRAME_167_BLOCKED 167 +#define FRAME_168_BACK 168 +#define FRAME_169_BEGIN_BLOCK 169 +#define FRAME_170_STAND_WITH_SWORD 170 +#define FRAME_171_STAND_WITH_SWORD 171 #define SWORD_0_SHEATHED 0 #define SWORD_2_DRAWN 2 @@ -39,6 +66,9 @@ #define CHARID_4_SKELETON 4 #define CHARID_5_PRINCESS 5 +/* Градаций мастерства стража (seg002: таблицы вероятностей по 12 записей). */ +#define NUM_GUARD_SKILLS 12 + /* Тип персонажа — в pop_char.h (общий с Кидом и Char). */ extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты (как в оригинале) */ @@ -56,6 +86,18 @@ extern int8_t can_guard_see_kid; * снимает сам ИИ. */ extern int8_t is_guard_notice; +/* Боевые таймеры/уровень мастерства (seg000:295..311). Все убывают раз в + * кадр в autocontrol_opponent — как в оригинале. + * guard_skill — 0..11, из данных уровня; индекс в таблицы вероятностей; + * guard_refrac — «отдышка» стража: пока >0, он не атакует и не наступает; + * justblocked — Кид только что парировал (влияет на вероятность блока); + * kid_sword_strike — Кид только что бил (стража это придерживает). */ +extern uint8_t guard_skill; +extern int8_t guard_refrac, justblocked, kid_sword_strike; + +/* Сид бросков боёвки (свой домен последовательности, см. pop_geom.h). */ +extern pop_rnd_t pop_fight_seed; + /* Луч видимости стража — раз в кадр, ДО тиков персонажей (в оригинале * check_can_guard_see_kid стоит в play_frame перед play_kid_frame). */ void pop_check_can_guard_see_kid(void) __banked; diff --git a/applications/PoP/roomtest/pop_kid.c b/applications/PoP/roomtest/pop_kid.c index 935ef52..8fa1d0a 100644 --- a/applications/PoP/roomtest/pop_kid.c +++ b/applications/PoP/roomtest/pop_kid.c @@ -192,6 +192,21 @@ void pop_loadshad(void) { Char = Guard; } void pop_saveshad(void) { Guard = Char; pop_gframe = cur_frame; } void pop_loadshad_and_opp(void) { Char = Guard; Opp = Kid; } +/* char_opp_dist (seg006:2135): >0 — соперник ПЕРЕД Char, <0 — за спиной. + * +13 при встречных направлениях: у стоящих лицом друг к другу «ноль» + * приходится на ширину тайла между ними. Живёт здесь, а не в guards.c, + * потому что нужен по обе стороны банковой границы — и ИИ, и диспетчеру + * боёвки (pop_ctrl). */ +int16_t pop_char_opp_dist(void) +{ + int16_t distance; + if (Char.room != Opp.room) return 999; + distance = (int16_t)Opp.x - (int16_t)Char.x; + if (Char.direction < 0) distance = -distance; + if (distance >= 0 && Char.direction != Opp.direction) distance += 13; + return distance; +} + int pop_kid_data_load(const char *path) { uint8_t *pg = (uint8_t *)0xC000; /* страница мапится в W3 на время чтения */ diff --git a/applications/PoP/roomtest/pop_kid.h b/applications/PoP/roomtest/pop_kid.h index b26ff30..c02b03b 100644 --- a/applications/PoP/roomtest/pop_kid.h +++ b/applications/PoP/roomtest/pop_kid.h @@ -79,6 +79,10 @@ void pop_loadshad(void); void pop_saveshad(void); void pop_loadshad_and_opp(void); /* Char = Guard, Opp = Kid (seg006:841) */ +/* Дистанция до соперника в системе Char: >0 впереди, <0 за спиной, + * 999 — разные комнаты (порт char_opp_dist, seg006:2135). */ +int16_t pop_char_opp_dist(void); + /* Прокрутить последовательность персонажа целиком (load + play_seq + * save). Звать вместо голого play_seq. */ void pop_char_set_seq(uint8_t seq_id); /* seq активного персонажа */