/* * pop_guard.c — СОСТОЯНИЕ и ЛОГИКА стража (W1/W2). См. pop_guard.h. * * Здесь только то, что должно оставаться достижимым из банка (guards.c): * поля Guard, HP, вход в комнату, чит-убийство, чтение кадра из таблицы. * ОТРИСОВКА — в pop_cdraw.c (банк 4), общая на всех Char. */ #include #include "pop_guard.h" #include "pop_geom.h" #include "pop_level.h" #include "pop_kid.h" /* Char/play_seq — интерпретатор общий */ #include "pop_map.h" /* hitp_* — HP Кида */ #include "pop_state.h" /* pop_immortal — чит бессмертия */ #include "pop_tile.h" /* POP_TILE_DIV — колонка по X без деления */ #include "kid_data.h" pop_char_t Guard; uint8_t guardhp_curr, guardhp_max; int8_t guardhp_delta; uint8_t pop_guard_hurt; int8_t can_guard_see_kid; int8_t is_guard_notice; uint8_t guard_skill; int8_t guard_refrac, justblocked, kid_sword_strike; int8_t offguard, holding_sword; int8_t pop_united_shadow; /* см. pop_guard.h: 0 / счётчик / −1 */ uint8_t pop_shadow_init; int8_t pop_guard_notice_timer; /* фора Джафара (уровень 13) */ /* Сид бросков боёвки. В оригинале генератор один на всё, у нас * последовательности разведены по доменам (кладка / анимация тайлов / бой), * см. pop_geom.h — иначе перерисовка стены сбивала бы броски стража. */ pop_rnd_t pop_fight_seed; /* Кадр стража (порт cur_frame для Char). Глобальный, а не статик: его * заполняет логика (W1/W2), а читает отрисовка из резидента W3. */ kframe pop_gframe; uint8_t pop_guard_present(void) { return (uint8_t)(Guard.charid != 0 && Guard.alive < 0 && guardhp_curr != 0); } void pop_guard_reset(void) { Guard.charid = 0; Guard.alive = 0; Guard.direction = 0; guardhp_curr = 0; guardhp_delta = 0; can_guard_see_kid = 0; is_guard_notice = 0; guard_refrac = justblocked = kid_sword_strike = 0; offguard = holding_sword = 0; } /* kill guard cheat (seg000:786): * if (Guard.charid != charid_4_skeleton) { * guardhp_delta = -guardhp_curr; * Guard.alive = 0; * } * Скелета чит не убивает — он и в оригинале бессмертен. */ void pop_guard_kill(void) { if (Guard.charid == CHARID_4_SKELETON) return; if (!pop_guard_present()) return; /* HP не обнуляем — ставим дельту, её применит pop_do_delta_hp. Дальше * play_guard увидит «жив, но HP ноль», сам переведёт в alive = 0 и * вызовет on_guard_killed: брызги, вспышка, а на 13-м уровне — флаг * «выход разрешён». * * ОСОЗНАННОЕ РАСХОЖДЕНИЕ (решение пользователя 2026-08-13, ради скорости * прогонов): оригинал (seg000:786) ставит здесь ещё и `Guard.alive = 0`, * а гейт в play_guard (seg006:1490) требует `alive < 0` — то есть у него * чит убивает стража В ОБХОД события смерти, и победа над Джафаром по * читу выхода на 14-й уровень НЕ открывает. Для отладки это дорого: * каждый прогон приходится честно выигрывать бой. Убрав одну строку, * получаем ровно «как будто убил Кид». На игру не влияет — читы в * релизной сборке выключены (pop_cheats). */ guardhp_delta = (int8_t)-guardhp_curr; } /* take_hp (seg006:06BD) — общий для боёвки и физики; см. pop_guard.h. */ uint8_t pop_take_hp(uint8_t count) { if (Char.charid == CHARID_0_KID) { /* ЧИТ БЕССМЕРТИЯ, уровень 2 (pop_state.h): гасим ТОЛЬКО мелкий урон * — падение с двух этажей, придавливание плитой. Мгновенная смерть * (пики, чомпер, падение с трёх этажей, удар вне боевой стойки) * приходит с count = 100 и проходит всегда: у читa нет задачи * отменять смерть, у него задача отменить «минус одно деление». * Уровень 1 сюда не вмешивается вовсе — он живёт в hurt_by_sword и * гасит только удары мечом. */ if (pop_immortal >= 2 && count < 100) return 0; if (count >= hitp_curr) { hitp_delta = (int8_t)-hitp_curr; return 1; } hitp_delta = (int8_t)-(int8_t)count; } else { if (count >= guardhp_curr) { guardhp_delta = (int8_t)-guardhp_curr; return 1; } guardhp_delta = (int8_t)-(int8_t)count; } return 0; } /* timers (seg003:0735) — кадровые счётчики спецсобытий. * * Вспышка слияния с тенью досчитывает до нуля и УХОДИТ В −1: это «уже * слились», состояние до конца уровня. Именно на −1 смотрят check_shadow * (больше не поднимать) и «появление плит» в комнатах 2/13 (pop_map). * Ноль как конечное значение не годится — он неотличим от «не было». */ void pop_frame_timers(void) { if (pop_united_shadow > 0) { --pop_united_shadow; if (pop_united_shadow == 0) pop_united_shadow = -1; } if (pop_guard_notice_timer > 0) --pop_guard_notice_timer; } /* do_delta_hp (seg003): применить накопленную за кадр дельту HP и обнулить * её. Звать раз в кадр ПОСЛЕ тиков персонажей, как в play_frame. */ void pop_do_delta_hp(void) { int16_t hp; /* Спецсобытие уровня 12 (seg000:1518): ранил ТЕНЬ — ранил себя. Урон * не делится, а КОПИРУЕТСЯ: тень теряет 1 HP и Кид теряет 1 HP. Без * этого бой с тенью теряет смысл — её можно было бы просто зарубить. * Проверка идёт по слоту соперника (у оригинала — по Opp, но в момент * do_delta_hp там всегда пара Кид/соперник). */ if (pop_current_level == SHADOW_FIGHT_LEVEL && Guard.charid == CHARID_1_SHADOW && guardhp_delta != 0) hitp_delta = guardhp_delta; if (hitp_delta) { hp = (int16_t)hitp_curr + hitp_delta; if (hp < 0) hp = 0; if (hp > (int16_t)hitp_max) hp = (int16_t)hitp_max; hitp_curr = (uint8_t)hp; /* Брызги и красная вспышка — только на УРОН: и draw_kid (seg008:1643), * и flash_if_hurt (seg003:785) смотрят именно `hitp_delta < 0`. С * условием «дельта не ноль» их получало и зелье здоровья (+1). */ if (hitp_delta < 0) pop_kid_hurt = 1; hitp_delta = 0; } if (guardhp_delta) { hp = (int16_t)guardhp_curr + guardhp_delta; if (hp < 0) hp = 0; if (hp > (int16_t)guardhp_max) hp = (int16_t)guardhp_max; guardhp_curr = (uint8_t)hp; if (guardhp_delta < 0) pop_guard_hurt = 1; /* -> брызги поверх стража */ guardhp_delta = 0; } } /* check_guard_fallout (seg002:0241): персонаж слота Guard провалился НИЖЕ * комнаты. Уйти в комнату снизу он не может (у стража нет перехода), и * оригинал его просто убирает: guards_tile комнаты становится −1, то есть * стража там больше нет и после возврата. У нас тот же смысл — запомнить * состояние с tile = 30 («стража в комнате больше нет»). * * Ветка ТЕНИ (seg002:0241): она исчезает, только если РЕАЛЬНО падает * (`action == actions_4_in_freefall`) — иначе тень, зашедшую низко по * своим делам (уровень 5 идёт по нижнему ряду за зельем), убрало бы с * экрана на ровном месте. Ветка скелета — ниже, он не гибнет, а * возрождается. * * Полосу HP перерисовывать отдельно не надо: pop_hp_draw сравнивает числа * и сам увидит, что стража не стало. */ /* Куда и как возрождается упавший СКЕЛЕТ (seg002:252): если под комнатой, * из которой он выпал, лежит комната 3 — он появляется там снова. Убить * его нельзя вовсе, падение лишь переносит. */ #define SKEL_REAPPEAR_ROOM 3 #define SKEL_REAPPEAR_X 133 #define SKEL_REAPPEAR_ROW 1 /* actions_4_in_freefall (seg005) — тот же номер, что ACT_FREEFALL в * pop_map.c; здесь свой define, потому что тот приватный для карты. */ #define ACT_4_IN_FREEFALL 4 void pop_guard_fallout(void) { if (Guard.charid == 0 || Guard.y < 211) return; if (Guard.charid == CHARID_1_SHADOW) { /* Тень исчезает только в свободном падении; иначе — не наше дело. */ if (Guard.action != ACT_4_IN_FREEFALL) return; Guard.direction = DIR_56_NONE; Guard.alive = 0; Guard.action = 0; Guard.charid = 0; guardhp_curr = 0; return; } if (Guard.charid == CHARID_4_SKELETON) { uint8_t down = pop_room_link(Guard.room, 3 /* вниз */); if (down == SKEL_REAPPEAR_ROOM) { Guard.room = down; Guard.x = SKEL_REAPPEAR_X; Guard.curr_row = SKEL_REAPPEAR_ROW; Guard.direction = 0; /* dir_0_right */ Guard.alive = -1; pop_guard_leave(); /* запомнить его в новой комнате */ return; } } pop_guard_state_save(Guard.room, 30, 0, 0, 0, 0, 0); Guard.charid = 0; Guard.direction = 0; Guard.alive = 0; guardhp_curr = 0; } /* load_frame для стража (seg006:0293 ветка charid_2_guard): таблица у * стражей СВОЯ (frame_tbl_guard), индекс = frame + add_frame − 149, где * add_frame = 70 для кадров 102..106 (seg006:535). */ void pop_guard_load_frame(void) { int16_t idx = (int16_t)Guard.frame; if (idx >= 102 && idx < 107) idx += 70; /* add_frame */ idx -= 149; if (idx < 0 || idx >= KID_NGFRAMES) { pop_gframe.image = 255; return; } pop_kid_data_frame(KID_BIN_GFRAMES_OFF, (uint8_t)idx, (uint8_t *)&pop_gframe); } /* leave_guard (seg002:02F5): вызывать ПЕРЕД сменой комнаты, пока Guard ещё * держит стража уходящей комнаты. Мёртвого запоминаем вместе с * последовательностью — тогда при возврате он лежит там же, а не встаёт * живым. */ void pop_guard_leave(void) { if (Guard.charid == 0) return; /* стража не было */ /* Тень и МЫШЬ в данные уровня не записываются (seg002:02F5): они не * «стражи комнаты», а спецсобытия — вернувшись, их надо получить заново * от check_shadow / check_mouse, а не поднять из guards_tile. */ if (Guard.charid == CHARID_1_SHADOW || Guard.charid == CHARID_24_MOUSE) return; pop_guard_state_save(Guard.room, (uint8_t)(Guard.curr_row * 10), /* get_tilepos(0,row) */ Guard.direction, Guard.x, guard_skill, Guard.curr_seq, (uint8_t)(Guard.alive >= 0)); }