3983fa4513
Боёвка (порт seg002/seg006): - check_sword_hurting / check_hurting / check_sword_hurt / hurt_by_sword / take_hp через дельты; do_delta_hp сводит их раз в кадр; - парирование (justblocked), refractimer после ранения стража; - смерть по seq_71_dying — через неё же теперь работает чит K: страж действительно погибает, а не замирает на месте; - парные окна Char/Opp: loadkid_and_opp / savekid_and_opp / saveshad_and_opp. Индикаторы HP (порт draw_kid_hp / draw_guard_hp): Кид слева, страж справа. Перерисовка ТОЛЬКО при изменении числа и тогда на ОБЕИХ страницах дабл-буфера (счётчик hp_todo, иначе на второй странице осталось бы старое значение и мерцало через кадр); pop_hp_invalidate при входе в комнату, где фон перерисован целиком. Чит I — бессмертие Кида (нашего изобретения, в оригинале его нет). Перекрывает и путь «безоружного закалывают насмерть»: тот идёт мимо HP, и без этого чит бесполезен ровно там, где нужен. ДВА НАЙДЕННЫХ БАГА: 1. Полосу HP блитил row-major примитивом, а атласы Кида и стража хранятся COLUMN-major (ради бесплатного флипа) — марки выходили транспонированными. Теперь колоночный блит, стрелки как в оригинале. 2. Кэш кадра для ОТРИСОВКИ заполняли pop_savekid/pop_saveshad. Любое окно Char БЕЗ play_seq — а это оба окна боёвки — записывало Киду кадр, который принадлежал СТРАЖУ, и kid_draw искал этот image в атласе Кида, рисуя произвольную позу. Владельцем кэша стал load_frame: он один знает, чей кадр загружен (по Char.charid). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
137 lines
5.9 KiB
C
137 lines
5.9 KiB
C
/*
|
||
* 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);
|
||
}
|
||
|
||
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): seqtbl_offset_char(seq_77_guard_stand_inactive).
|
||
* Последовательность крутит общий интерпретатор (pop_guard_play), а
|
||
* таблицу кадров он выбирает по Char.charid — у стража своя. */
|
||
pop_loadshad();
|
||
pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE);
|
||
pop_saveshad();
|
||
pop_guard_play(); /* до первого кадра */
|
||
}
|