/* * pop_guard_cold.c — переходы стража МЕЖДУ КОМНАТАМИ (банк 8): страж идёт * за Кидом (follow_guard, seg002:039E) и вход стража в комнату * (enter_guard, seg002:0112 + pos_guards, seg003). * * Обе зовутся РАЗ НА КОМНАТУ и обе — из roomtest_cold.c, который лежит в * том же банке 8: значит в резиденте им делать нечего, а трамплин на * вызове не появляется (объявления в pop_guard.h без __banked; условие * корректности — оба модуля в одном банке, см. _pop_kdraw.h про ту же * связку). Состояние стража (Guard, guard_skill, …) живёт в pop_guard.c * и видно отсюда как обычные глобалы: данные банков — в общем _DATA. */ #include #include "pop_guard.h" #include "pop_geom.h" #include "pop_level.h" #include "pop_kid.h" /* Char/play_seq — интерпретатор общий */ #include "pop_map.h" #include "pop_state.h" #include "pop_tile.h" /* POP_TILE_DIV — колонка по X без деления */ #include "kid_data.h" static int8_t guard_col_from_x(uint8_t xpos); /* определена ниже */ /* ---- Страж ИДЁТ ЗА КИДОМ в соседнюю комнату ------------------------- * * Порт ветки стража из exit_room (seg002:0340..0382) + follow_guard * (seg002:039E). Своим ходом персонаж слота Guard из комнаты не уходит * (его физика гейтится x ∈ [44,211), см. pop_guard_phys_tick), но когда * комнату меняет КИД, движок может перенести стража следом — именно так в * оригинале страж из комнаты 3 оказывается в комнате 2 за отступающим * Кидом. * * Условия оригинала, все обязательные: * - страж есть, ЖИВ и с ВЫНУТЫМ МЕЧОМ (то есть идёт бой); * - переход ВБОК: вверх и вниз оригинал не пускает никогда (там условия * `curr_row >= 0` и `curr_row < 3` истинны всегда → leave_guard); * - в целевой комнате НЕТ своего ЖИВОГО стража (иначе их стало бы два: * проверка `guards_tile >= 30 || guards_seq_hi != 0` — «пусто или * труп»); * - страж прижат к тому краю, в который ушёл Кид: x < 91 при уходе * влево, x >= 165 при уходе вправо. * * side — куда ушёл КИД (0 = left, 1 = right, 2 = up, 3 = down), то есть * roomleave_result оригинала. Возврат: 1 — страж перенесён в new_room * (вызывающий НЕ должен звать ни pop_guard_leave, ни pop_guard_enter), * 0 — остаётся, всё как раньше. */ uint8_t pop_guard_follow(uint8_t new_room, uint8_t side) { uint8_t tile, color, skill; int8_t dir; if (Guard.charid == 0) return 0; /* dir_56_none */ if (Guard.alive >= 0 || Guard.sword != SWORD_2_DRAWN) return 0; if (side > 1) return 0; /* только вбок */ /* «В целевой комнате уже есть живой страж» = страж по данным есть И это * не запомненный труп (у трупа сохранена последовательность). */ if (pop_level_guard(new_room, &tile, &dir, &color, &skill) && pop_guard_state_seq(new_room) == 0) return 0; if (side == 0) { if (Guard.x >= 91) return 0; } /* Кид ушёл влево */ else { if (Guard.x < 165) return 0; } /* Кид ушёл вправо */ /* follow_guard: сначала «стража нет» в ОБЕИХ комнатах (в оригинале * guards_tile = 0xFF). Без этого он раздвоится: в старой комнате его * поднимут из данных при возврате, а в новой — при следующем входе. */ pop_guard_state_save(Guard.room, 30, 0, 0, 0, 0, 0); pop_guard_state_save(new_room, 30, 0, 0, 0, 0, 0); /* goto_other_room (seg002:0486) для стража: комната + перенос X через * шов (влево +140, вправо −140). Ряд при боковом переходе не меняется; * колонку пересчитываем из новой X, как enter_guard. */ Guard.room = new_room; Guard.x = (uint8_t)(side == 0 ? Guard.x + 140 : Guard.x - 140); Guard.curr_col = guard_col_from_x(Guard.x); return 1; } /* get_tile_div_mod_m7 (seg006:697): колонка комнаты по X персонажа — * floor((xpos − 7 − 58) / 14), где 58 = pop_x_bump[FIRST_ONSCREEN_COLUMN]. */ static int8_t guard_col_from_x(uint8_t xpos) { /* Таблицей (резидентная POP_TILE_DIV: −58 уже внутри значений) — тот же * приём, что в tile_at_kid/get_tile_div_mod. Здесь он кроет ПОЧТИ всё: * xpos — байт, так что медленный хвост остаётся только для xpos < 7. */ if (xpos >= 7) return POP_TILE_DIV[xpos - 7]; { int16_t x = (int16_t)xpos - 7 - pop_x_bump[FIRST_ONSCREEN_COLUMN]; int16_t col = x / TILE_SIZEX; if (x % TILE_SIZEX < 0) --col; return (int8_t)col; } } void pop_guard_enter(uint8_t room) { uint8_t tile, color, skill; int8_t dir; pop_guard_reset(); /* load_lev_spr (seg000:1112): tbl_guard_type == −1 — стражей на уровне * НЕТ вообще (14), их графика даже не грузится. Без этой проверки * на таком уровне полезли бы стражи из данных комнат. */ if (pop_level_guard_type(pop_current_level) < 0) return; if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return; /* Цвет — ДО отрисовки комнаты (как redraw_screen, seg003:255): слоты * палитры общие на экран, и смена посреди кадра дала бы стража в новой * палитре с полосой HP в старой. * * Цвет из данных комнаты берётся ТОЛЬКО у обычного стража (enter_guard, * seg002:183: `curr_guard_color = 0`, если tbl_guard_type != 0). У * скелета и Джафара палитра своя, из файла набора, и подменять её * нельзя — иначе визирь на уровне 13 покрасился бы в цвет стража, * записанный в его комнате. */ pop_guard_set_palette(pop_level_guard_type(pop_current_level) == 0 ? color : 0); /* enter_guard (seg002:0112). Из ТАЙЛА берётся только РЯД — колонки в нём * нет: leave_guard кладёт туда get_tilepos(0, row), то есть обнуляет её * намеренно. Носитель колонки — guards_x, её пишет тот же leave_guard, а * при загрузке уровня заполняет pos_guards (см. pop_gstate_init). * * Считать X из `tile % 10`, как мы делали, значит после ПЕРВОГО же выхода * из комнаты телепортировать живого стража в КОЛОНКУ 0 — потому что там * с этого момента ноль. Уровень 8, комната 24: страж стоит в (0,6), ряд 0 * в колонках 1..3 ПУСТОЙ, и после возврата Кида страж оказывался над * дырой (x = 72 — это колонка 1 по таблице деления), падал с ряда 0 на * ряд 2 и разбивался. Наблюдения пользователя — «страж был на 0,0», * «страж падает на 2,1 и разбивается» — это ровно оно. * * Y всегда по ряду: enter_guard ставит стража НА ПОЛ (y_land[row+1]), а не * туда, где он был по вертикали. Поэтому «дорисовать падение» при * возврате в комнату оригинал не умеет — и не должен. */ Guard.room = room; Guard.curr_row = (int8_t)(tile / 10); Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1]; Guard.direction = dir; /* seg002:196: на уровне со скелетами слот поднимает СКЕЛЕТА, и он * встаёт сразу активным — с вынутым мечом (seq_63), а не в стойке * покоя (seq_77). Стойку ставит ветка ниже, вместе с seq трупа. */ Guard.charid = (pop_level_guard_type(pop_current_level) == 2) ? CHARID_4_SKELETON : CHARID_2_GUARD; Guard.sword = (Guard.charid == CHARID_4_SKELETON) ? SWORD_2_DRAWN : 0; /* sword_0_sheathed */ Guard.alive = -1; Guard.action = 1; /* actions_1_run_jump */ Guard.fall_x = Guard.fall_y = 0; /* 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[уровень]. * extrastrength (seg002:38) ненулевой только у skill 4 (=1). */ guardhp_max = guardhp_curr = (uint8_t)(pop_level_guard_hp(pop_current_level) + (guard_skill == 4 ? 1 : 0)); guardhp_delta = 0; /* enter_guard (seg002:0208). Живого поднимаем стандартной стойкой; у * ЗАПОМНЕННОГО МЁРТВОГО берём сохранённую последовательность — он лежит * ровно там, где лёг (порт ветки guards_seq_hi != 0). */ { uint16_t saved_seq = pop_guard_state_seq(room); /* X — ВСЕГДА из запомненной (enter_guard, seg002:179: * `Char.x = level.guards_x[room_minus_1]`), и живого стража это * касается ровно так же, как трупа: страж, с которым Кид не стал * драться, обязан остаться СТОЯТЬ ТАМ ЖЕ. Иначе он не просто * «дёргается» — он может встретить Кида прямо на входе в комнату, * не дав тому достать меч. */ Guard.x = pop_guard_state_x(room); /* КОЛОНКА — из X (enter_guard, seg002:180: * `Char.curr_col = get_tile_div_mod_m7(Char.x)`). Из тайла её брать * нельзя, там всегда 0 (см. выше). Колонка нужна не только * отрисовке: на неё смотрят коллизия и check_can_guard_see_kid, а у * ЗАПОМНЕННОГО ТРУПА ещё и порядок обхода тайлов — иначе Кид * рисовался поверх тела вместо того, чтобы пройти за ним * (BUG-DRAWORDER-1). */ Guard.curr_col = guard_col_from_x(Guard.x); pop_loadshad(); if (saved_seq) Char.curr_seq = saved_seq; else pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE); pop_saveshad(); pop_guard_play(); /* до первого кадра */ /* enter_guard (seg002:218): кадр смерти -> страж поднят ТРУПОМ. */ if (Guard.frame == 185 || Guard.frame == 177 || Guard.frame == 178) { Guard.alive = 1; guardhp_curr = 0; } } }