Files
Sprinter-SDCC/applications/PoP/roomtest/pop_guard.c
T
Александр Петров b5d2a81ee3 L1: фиксы прогона уровня 1 — уровень мутабелен, стражи, loose-плиты, порядок
Одиннадцать наблюдений первого прогона свелись к шести корням, четыре
наблюдения второго — ещё к четырём.  Разбор каждого — bug_closed.md.

Первая волна:
- BUG-LVLSTATE-1: уровень стал мутабельным (эталонная копия foretable для
  рестарта, pop_level_set_tile вместо таблицы оверрайдов);
- BUG-RESPAWN-1: рестарт = load_level, тайлы возвращаются из эталона;
- BUG-DEATH-1: смерть от меча доигрывается (порт control_kid, seg006:0CD1);
- BUG-GATE-ANIM-1: ворота в отрисованной комнате перерисовываются
  (POP_RD_GATE, порт draw_trob seg007:01E6);
- BUG-COLL-1: полный порт check_collisions/bumped (seg004) вместо поиска
  стены только в колонке переднего края;
- BUG-STANDUP-1: убран лишний guard в bumped_floor — вставание у стены
  роняло Кида сквозь пол.

Вторая волна:
- BUG-RESPAWN-2: рестарт возвращает и СТРАЖЕЙ (в оригинале play_level на
  каждой итерации делает load_level + pos_guards);
- BUG-LOOSE-2: падающий кусок привязан к своей комнате и долетает после
  ухода Кида (do_mobs крутит mobs[] независимо от drawn_room);
- BUG-DRAWORDER-1: порядок «Кид / страж» задаётся обходом тайлов
  (redraw_needed_tiles: ряды 2,1,0, колонки 0..9), а не ролью персонажа.

По BUG-DRAWORDER-1 понадобилось три захода, и два первых были неполны:
  1) сам порядок — но общее окно fore-клипа осталось стражьим, и Кид
     нарисовался поверх передних столбов (kid_fore_clip_restore);
  2) enter_guard брал curr_col из тайла, а leave_guard пишет туда
     get_tilepos(0,row) — у запомненного ТРУПА колонка была 0 при
     настоящей X.  Теперь колонка выводится из X, как в оригинале;
  3) ветка actions_1_run_jump в set_objtile_at_char оказалась не
     «упрощаемой»: в беге тайл берётся из нижнего ряда и ЛЕВОЙ колонки
     габарита, поэтому бегущий Кид уходит за объекты справа.  Считается
     для обоих персонажей — enter_guard ставит action=1 и стражу.

Проверено в MAME: зелья/меч/плиты переживают выход из комнаты и
восстанавливаются после смерти; кнопка room5 поднимает решётку; падение с
кнопки больше не роняет в комнату 6; убитый страж жив после respawn;
Кид проходит за телом стража.  make size-check — роста нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 22:35:41 +03:00

186 lines
9.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* pop_guard.c — СОСТОЯНИЕ и ЛОГИКА стража (W1/W2). См. pop_guard.h.
*
* Здесь только то, что должно оставаться достижимым из банка (guards.c):
* поля Guard, HP, вход в комнату, чит-убийство, чтение кадра из таблицы.
* ОТРИСОВКА — в pop_gdraw.c, резиденте W3 (её зовёт лишь главный цикл).
*/
#include <stdint.h>
#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 "kid_data.h"
pop_char_t Guard;
uint8_t guardhp_curr, guardhp_max;
int8_t guardhp_delta;
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;
/* Сид бросков боёвки. В оригинале генератор один на всё, у нас
* последовательности разведены по доменам (кладка / анимация тайлов / бой),
* см. 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. alive = 0 переводит стража в «умирает», а дальше
* общий control() ставит ему seq_71_dying. */
guardhp_delta = (int8_t)-guardhp_curr;
Guard.alive = 0;
}
/* do_delta_hp (seg003): применить накопленную за кадр дельту HP и обнулить
* её. Звать раз в кадр ПОСЛЕ тиков персонажей, как в play_frame. */
void pop_do_delta_hp(void)
{
int16_t hp;
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;
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;
guardhp_delta = 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; /* стража не было */
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));
}
/* 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)
{
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();
if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return;
/* enter_guard (seg002:0112) + pos_guards (seg003): X считается из
* колонки тайла, а не берётся из файла. */
Guard.room = room;
Guard.curr_row = (int8_t)(tile / 10);
Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1];
Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
Guard.curr_col = (int8_t)(tile % 10);
Guard.direction = dir;
Guard.charid = CHARID_2_GUARD;
Guard.sword = 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[уровень].
* Уровень 1 -> 3; extrastrength ненулевой только у skill 4. */
guardhp_max = guardhp_curr = (uint8_t)(3 + (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 берём сохранённую: pos_guards (seg003:620)
* пересчитывает её из колонки только при загрузке уровня, а дальше
* ею владеет leave_guard. Иначе тело при возврате прыгает в центр
* тайла. */
if (saved_seq) Guard.x = pop_guard_state_x(room);
/* ...и КОЛОНКУ пересчитываем из X (enter_guard, seg002:180:
* `Char.curr_col = get_tile_div_mod_m7(Char.x)`). Брать её из
* тайла нельзя: leave_guard сохраняет get_tilepos(0, row), то
* есть колонку 0 всегда. У живого стража это незаметно (X там
* сам считается из колонки), а у ЗАПОМНЕННОГО ТРУПА X
* настоящая, и curr_col оставался нулём при теле, лежащем в
* колонке 2. Тайл трупа шёл в обходе раньше тайла Кида, и Кид
* рисовался ПОВЕРХ тела вместо того, чтобы пройти за ним
* (BUG-DRAWORDER-1). Колонка нужна не только отрисовке — на
* неё смотрят коллизия и check_can_guard_see_kid. */
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;
}
}
}