/* * guards.c — ЛОГИКА стража, собирается в БАНК (--bank 1=guards.c, окно W3). * * Правила банка (проверено пробником tests/w3bankgfx): * - состояние держим в W1/W2 (pop_guard.c): статики банка линкуются в * страницу банка и снаружи не читаются; * - звать можно W1/W2-код (pop_map, pop_kid, pop_geom, libc, примитивы * libbgi) — скобка _bgi_begin/_bgi_end возвращает страницу БАНКА; * - звать НЕЛЬЗЯ резидент W3 (pop_bg) — ни напрямую, ни транзитивно: * пока исполняется банк, резидентной страницы в адресном пространстве * нет. Поэтому фон/окклюзию за стража рисует главный цикл. * * Порт: 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; } } /* ---- Синтетический ввод (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_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; 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 = pop_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(); } } /* ---- Активный страж (меч обнажён), 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 (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): ИИ + общий диспетчер. */ 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). */ play_seq(); pop_saveshad(); }