PoP roomtest: страж замечает Кида и достаёт меч (ИИ, шаг 1)
Первый самостоятельный кусок ИИ стража (план, пункт 2). В оригинале у соперника нет своего диспетчера: autocontrol_* (seg002) выставляет те же глобалы control_*, что и ввод игрока, а дальше исполняется общий control(). Инфраструктура под это встала прошлым коммитом, здесь — сама логика. Порт: - check_can_guard_see_kid (seg003:688) — луч видимости по ряду: стены и верхи дверей рвут его совсем, loose/чомпер/дыра/неподнятые ворота дают «вижу, но не пойду». В guards.c (банк 1); - Opp + loadshad_and_opp (seg006:841) и char_opp_dist (seg006:2135); - autocontrol_guard_inactive (seg002:710) + move_* (seg002:0706..); - ветки control(): control_guard_inactive (seg006:2123) и draw_sword (seg005:945) — соперник уходит сразу в seq_90 en garde; - pop_guard_tick перестроен по play_guard_frame (seg000:1246): окно Char/Opp вокруг ИИ, диспетчера и play_seq. По дороге: - Kid.alive не выставлялся (= 0 = «мёртв» в семантике оригинала), из-за чего луч видимости не мог сработать в принципе — ставим -1 в kid_init; - Kid.room не выставлялась вовсе; условие Kid.room == Guard.room всегда было ложным. Ставим в enter_room (полная модель Kid.room != drawn_room у шва по-прежнему впереди); - pop_tile_at — тайл текущей комнаты наружу из pop_map (луч видимости); - control_x/y/shift открыты в шину: ИИ заполняет оси как есть, без flip_control_x (его «вперёд» уже в системе персонажа). Проверено в MAME (комната 3, страж на tile 17): страж переходит из стойки 166 в 171 stand_with_sword, sword=2, can_guard_see_kid=2; ввод игрока не пострадал. Клинок отдельным спрайтом пока не рисуется — sword.atl содержит только кадры 229..237 (подъём меча Кидом), остальные строки sword_tbl приедут с боёвкой. Бюджет (225 кадров, комната 3): 384 168 – 384 636 тактов, 0.893–0.895 кадра, запас 45 364. Прошлый замер 382 584 – 383 064 — шаг стоил ~1 570. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,17 +10,199 @@
|
||||
* пока исполняется банк, резидентной страницы в адресном пространстве
|
||||
* нет. Поэтому фон/окклюзию за стража рисует главный цикл.
|
||||
*
|
||||
* Сейчас — каркас: тик есть, ИИ и боёвка (порт seg005 control_guard*,
|
||||
* seg004 fight/collision) добавляются сюда.
|
||||
* Порт: seg003 check_can_guard_see_kid, seg002 autocontrol_*, seg000
|
||||
* play_guard_frame, seg006 char_opp_dist. ДИСПЕТЧЕР у соперника не свой:
|
||||
* ИИ выставляет те же глобалы control_* (pop_ctrl.h), что и ввод игрока, а
|
||||
* дальше работает общий pop_control().
|
||||
*/
|
||||
#include "pop_guard.h"
|
||||
#include "pop_kid.h"
|
||||
#include "pop_ctrl.h"
|
||||
#include "pop_map.h"
|
||||
#include "pop_geom.h"
|
||||
#include "pop_trob.h"
|
||||
|
||||
#define TILE_MIDX 7 /* середина тайла в координатах персонажа */
|
||||
#define SCREENSPACE_X 58 /* = pop_x_bump[FIRST_ONSCREEN_COLUMN] */
|
||||
|
||||
#define TILE_GATE 4
|
||||
#define TILE_DOORTOP_FLOOR 7
|
||||
#define TILE_LOOSE 11
|
||||
#define TILE_DOORTOP 12
|
||||
#define TILE_CHOMPER 18
|
||||
#define TILE_WALL 20
|
||||
|
||||
/* tile_is_floor (seg004): не-пол — пустота, ковёр, верх двери, стена и
|
||||
* «половинки» 26..29. Дубликат крошечного switch'а из pop_map: тянуть его
|
||||
* через границу банка дороже, чем повторить. */
|
||||
static uint8_t tile_is_floor(uint8_t t)
|
||||
{
|
||||
switch (t) {
|
||||
case 0: case 9: case 12: case 20:
|
||||
case 26: case 27: case 28: case 29:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Тайл в ряду Кида по X-координате персонажа (порт get_tile_at_kid,
|
||||
* seg003:761): xpos − 7 делится на ширину тайла с округлением вниз. */
|
||||
static int8_t tile_col; /* колонка последнего tile_at_kid */
|
||||
|
||||
static uint8_t tile_at_kid(int16_t xpos)
|
||||
{
|
||||
/* get_tile_div_mod_m7 (seg006:697): колонка = floor((xpos − 7 − 58)/14),
|
||||
* СРАЗУ в координатах комнаты (58 = x_bump[FIRST_ONSCREEN_COLUMN]).
|
||||
* Оригинал берёт её из tile_div_tbl, у нас — делением с округлением
|
||||
* вниз, как в pop_map. */
|
||||
int16_t x = xpos - 7 - SCREENSPACE_X;
|
||||
int16_t col = x / TILE_SIZEX;
|
||||
if (x % TILE_SIZEX < 0) --col;
|
||||
tile_col = (int8_t)col;
|
||||
return pop_tile_at(tile_col, Kid.curr_row);
|
||||
}
|
||||
|
||||
/* check_can_guard_see_kid (seg003:688) — луч видимости по ряду.
|
||||
* Результат в can_guard_see_kid: 0 не видит / 1 видит, но не пойдёт /
|
||||
* 2 видит и пойдёт. */
|
||||
void pop_check_can_guard_see_kid(void) __banked
|
||||
{
|
||||
uint8_t kid_frame = Kid.frame;
|
||||
int16_t left_pos, right_pos;
|
||||
uint8_t tile;
|
||||
|
||||
if (!(Guard.charid != 0 &&
|
||||
/* кадры 219..228 — подъём по лестнице: там страж Кида не видит */
|
||||
kid_frame != 0 && (kid_frame < 219 || kid_frame >= 229) &&
|
||||
Kid.alive < 0 && Guard.alive < 0 &&
|
||||
Kid.room == Guard.room && Kid.curr_row == Guard.curr_row)) {
|
||||
can_guard_see_kid = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
can_guard_see_kid = 2;
|
||||
left_pos = pop_x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||
right_pos = pop_x_bump[Guard.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||
if (left_pos > right_pos) {
|
||||
int16_t t = left_pos; left_pos = right_pos; right_pos = t;
|
||||
}
|
||||
/* Чомпер стоит у ЛЕВОГО края тайла — свой тайл он не перекрывает. */
|
||||
if (tile_at_kid(left_pos) == TILE_CHOMPER)
|
||||
left_pos += TILE_SIZEX;
|
||||
/* Ворота — у ПРАВОГО края, поэтому крайний правый тайл не считается. */
|
||||
if (tile_at_kid(right_pos) == TILE_GATE)
|
||||
right_pos -= TILE_SIZEX;
|
||||
|
||||
while (left_pos <= right_pos) {
|
||||
tile = tile_at_kid(left_pos);
|
||||
/* Сквозь это не видно вовсе. */
|
||||
if (tile == TILE_WALL || tile == TILE_DOORTOP_FLOOR || tile == TILE_DOORTOP) {
|
||||
can_guard_see_kid = 0;
|
||||
return;
|
||||
}
|
||||
/* Видно, но страж не пойдёт: провалится / попадёт под чомпер /
|
||||
* упрётся в неоткрытые ворота / шагнёт в дыру. */
|
||||
if (tile == TILE_LOOSE || tile == TILE_CHOMPER || !tile_is_floor(tile)) {
|
||||
can_guard_see_kid = 1;
|
||||
} else if (tile == TILE_GATE &&
|
||||
pop_trob_modif(Kid.room)[Kid.curr_row * 10 + tile_col] < 112) {
|
||||
can_guard_see_kid = 1; /* ворота подняты не до конца */
|
||||
}
|
||||
left_pos += TILE_SIZEX;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
control_shift = control_y = control_x = control_shift2 = 0;
|
||||
control_down = control_up = control_backward = control_forward = 0;
|
||||
}
|
||||
static void move_1_forward(void)
|
||||
{
|
||||
control_x = POP_CONTROL_HELD_FORWARD;
|
||||
control_forward = POP_CONTROL_HELD;
|
||||
}
|
||||
static void move_4_down(void)
|
||||
{
|
||||
control_down = POP_CONTROL_HELD;
|
||||
control_y = POP_CONTROL_HELD_DOWN;
|
||||
}
|
||||
static void move_down_forw(void)
|
||||
{
|
||||
control_down = POP_CONTROL_HELD;
|
||||
move_1_forward();
|
||||
}
|
||||
|
||||
/* autocontrol_guard_inactive (seg002:710): страж с убранным мечом.
|
||||
* Пока Кид за спиной — страж не реагирует; исключение — «шум» Кида
|
||||
* (is_guard_notice), от которого страж оборачивается. */
|
||||
static void autocontrol_guard_inactive(void)
|
||||
{
|
||||
int16_t distance;
|
||||
if (Kid.alive >= 0) return;
|
||||
distance = char_opp_dist();
|
||||
/* В оригинале условие записано как (word)distance < (word)-8 —
|
||||
* беззнаковое сравнение, истинное для ВСЕГО, кроме диапазона −8..−1.
|
||||
* То есть: Кид не «вплотную за спиной». */
|
||||
if (Opp.curr_row != Char.curr_row || distance >= 0 || distance < -8) {
|
||||
if (is_guard_notice) {
|
||||
is_guard_notice = 0;
|
||||
if (distance < 0) {
|
||||
if (distance < -4) move_4_down(); /* обернуться на шум */
|
||||
return;
|
||||
}
|
||||
} else if (distance < 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (can_guard_see_kid) {
|
||||
/* Увидел — переходит в боевую стойку (вниз+вперёд = достать меч). */
|
||||
move_down_forw();
|
||||
}
|
||||
}
|
||||
|
||||
/* 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();
|
||||
}
|
||||
|
||||
/* play_guard (seg006): ИИ + общий диспетчер. */
|
||||
static void play_guard(void)
|
||||
{
|
||||
autocontrol_opponent();
|
||||
pop_control();
|
||||
}
|
||||
|
||||
/* pop_guard_tick — порт play_guard_frame (seg000:1246) в объёме, который у
|
||||
* нас уже есть: окно Char/Opp вокруг ИИ, диспетчера и интерпретатора
|
||||
* последовательностей. Физика стража (fall_accel/коллизия/бамп/пики) —
|
||||
* отдельным шагом. */
|
||||
void pop_guard_tick(void) __banked
|
||||
{
|
||||
if (Guard.charid == 0) return; /* стража в комнате нет */
|
||||
pop_loadshad_and_opp(); /* Char = Guard, Opp = Kid */
|
||||
play_guard();
|
||||
/* Анимация: та же seqtbl, что у Кида, но своя таблица кадров —
|
||||
* выбирается по Char.charid внутри load_frame (seg006:0293). */
|
||||
pop_guard_play();
|
||||
/* TODO: порт control_guard/fight (seg005/seg004) — состояние в Guard. */
|
||||
play_seq();
|
||||
pop_saveshad();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user