PoP roomtest: ИИ стража — подход к Киду и боевые ветки диспетчера
Пункт 2 плана закрыт: страж не только замечает Кида, но и идёт к нему и дерётся. Порт по SDLPoP, диспетчер общий — ИИ выставляет те же control_*, что и клавиатура игрока. guards.c (банк 1), порт seg002: - autocontrol_guard_active (737) + kid_in_sight (0A93) + kid_armed (0AC1) + kid_far (09CB); - guard_advance / guard_block / guard_strike с таблицами вероятностей по 12 градациям мастерства (seg002:26..38), бросок prob > prandom(255); - move_2_backward / move_3_up / move_6_shift / move_down_back; - таймеры justblocked / kid_sword_strike / guard_refrac убывают раз в кадр в autocontrol_opponent, как в оригинале. pop_ctrl.c, порт seg005: control_with_sword (964), swordfight (0CDB), sword_strike, parry, forward_with_sword, back_with_sword. Ветвление у Кида и у соперника разное — соперник блокирует только на кадре 152, Кид ещё и по 153 (и тогда последовательность прокручивается сразу). pop_guard.c: guard_skill из данных уровня (12 градаций, вне диапазона -> 3), HP по get_guard_hp (extrastrength[skill] + tbl_guard_hp[уровень]), собственный сид бросков pop_fight_seed — иначе перерисовка стены сбивала бы решения стража. char_opp_dist переехал из банка в pop_kid.c: он нужен по ОБЕ стороны банковой границы — и ИИ, и диспетчеру боёвки. Проверено в MAME (комната 3): страж проходит комнату, встаёт в дистанцию и машет мечом, позы меняются. Урона пока нет — HP-учёт и check_hurt следующим шагом, без них бой не заканчивается. Бюджет В БОЮ (175 кадров): 412 224 – 421 068 = 0.958–0.979 кадра. В покое было 400 800. Запас в худшем кадре ~9 000 — тесно, но в один кадр укладываемся; оптимизация отложена сознательно. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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): ИИ + общий диспетчер. */
|
||||
|
||||
Reference in New Issue
Block a user