PoP roomtest: HP-учёт, индикаторы HP, чит бессмертия; фикс кэша кадра
Боёвка (порт 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>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
#include "pop_map.h"
|
||||
#include "pop_geom.h"
|
||||
#include "pop_trob.h"
|
||||
#include "pop_state.h" /* pop_immortal — чит бессмертия */
|
||||
|
||||
#define TILE_MIDX 7 /* середина тайла в координатах персонажа */
|
||||
#define SCREENSPACE_X 58 /* = pop_x_bump[FIRST_ONSCREEN_COLUMN] */
|
||||
@@ -342,9 +343,142 @@ static void autocontrol_opponent(void)
|
||||
else autocontrol_guard_active();
|
||||
}
|
||||
|
||||
/* play_guard (seg006): ИИ + общий диспетчер. */
|
||||
/* ---- Урон мечом (seg002:0CD4..0E1F) -------------------------------- */
|
||||
#define ACTION_99_HURT 99
|
||||
#define ACTION_1_RUN 1
|
||||
#define FRAME_154_POKING 154
|
||||
#define SEQ_69_ATTACK_WAS_PARRIED 69
|
||||
#define SEQ_74_HIT_BY_SWORD 74
|
||||
#define SEQ_85_STABBED_TO_DEATH 85
|
||||
|
||||
/* refractimer (seg002:36) — «отдышка» стража после того, как его ранили. */
|
||||
static const uint8_t REFRACTIMER[NUM_GUARD_SKILLS] =
|
||||
{ 16, 16, 16, 16, 8, 8, 8, 8, 0, 8, 0, 0};
|
||||
|
||||
/* take_hp (seg006): отнять count HP у АКТИВНОГО персонажа через дельту.
|
||||
* Возврат 1 — HP кончились. Чит бессмертия ловим здесь: анимацию удара
|
||||
* Кид получает, а HP не теряет. */
|
||||
static uint8_t take_hp(uint8_t count)
|
||||
{
|
||||
if (Char.charid == CHARID_0_KID) {
|
||||
if (pop_immortal) 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;
|
||||
}
|
||||
|
||||
/* hurt_by_sword (seg002): применить попадание к АКТИВНОМУ персонажу.
|
||||
* Без меча в руке любое попадание смертельно; с мечом — минус 1 HP и кадр
|
||||
* «получил удар».
|
||||
* УПРОЩЕНИЕ: ветку «сбит с уступа» (seq_81, когда сзади пусто и до кромки
|
||||
* меньше 4) не портируем — ей нужны тайловые запросы ОТ Char, а pop_map
|
||||
* пока умеет только от Kid. На ровном полу (тайл сзади не пустой) оригинал
|
||||
* идёт ровно нашей веткой. */
|
||||
static void hurt_by_sword(void)
|
||||
{
|
||||
if (Char.alive >= 0) return;
|
||||
/* Чит бессмертия перекрывает и «безоружного закалывают насмерть»:
|
||||
* без этого Кид гибнет с одного удара мимо стойки, и чит бесполезен
|
||||
* ровно там, где нужен. Анимацию удара оставляем. */
|
||||
if (Char.charid == CHARID_0_KID && pop_immortal) {
|
||||
pop_char_set_seq(SEQ_74_HIT_BY_SWORD);
|
||||
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
|
||||
Char.fall_y = 0;
|
||||
play_seq();
|
||||
return;
|
||||
}
|
||||
if (Char.sword != SWORD_2_DRAWN) {
|
||||
take_hp(100);
|
||||
pop_char_set_seq(SEQ_85_STABBED_TO_DEATH);
|
||||
} else if (Char.charid != CHARID_4_SKELETON && take_hp(1)) {
|
||||
pop_char_set_seq(SEQ_85_STABBED_TO_DEATH); /* HP кончились */
|
||||
} else {
|
||||
pop_char_set_seq(SEQ_74_HIT_BY_SWORD);
|
||||
}
|
||||
Char.y = (uint8_t)pop_y_land[Char.curr_row + 1];
|
||||
Char.fall_y = 0;
|
||||
play_seq(); /* TODO: звук 12/13 */
|
||||
}
|
||||
|
||||
/* check_hurting (seg002:0D56): достал ли АКТИВНЫЙ персонаж соперника.
|
||||
* Бьют кадры 153/154; если соперник в этот момент парирует — удар отбит. */
|
||||
static void check_hurting(void)
|
||||
{
|
||||
int16_t distance;
|
||||
uint8_t cf, of, min_range;
|
||||
|
||||
if (Char.sword != SWORD_2_DRAWN) return;
|
||||
if (Char.curr_row != Opp.curr_row) return;
|
||||
cf = Char.frame;
|
||||
if (cf != FRAME_153_STRIKE_3 && cf != FRAME_154_POKING) return;
|
||||
|
||||
distance = pop_char_opp_dist();
|
||||
of = Opp.frame;
|
||||
if (distance < 0 || distance >= 29 ||
|
||||
(of != FRAME_161_PARRY && of != FRAME_150_PARRY)) {
|
||||
/* Соперник НЕ парирует. */
|
||||
if (cf == FRAME_154_POKING) {
|
||||
min_range = (uint8_t)(Opp.sword < SWORD_2_DRAWN ? 8 : 12);
|
||||
distance = pop_char_opp_dist();
|
||||
if (distance >= (int16_t)min_range && distance < 29)
|
||||
Opp.action = ACTION_99_HURT;
|
||||
}
|
||||
} else {
|
||||
/* Парировано. */
|
||||
Opp.frame = FRAME_161_PARRY;
|
||||
if (Char.charid != CHARID_0_KID) justblocked = 4;
|
||||
pop_char_set_seq(SEQ_69_ATTACK_WAS_PARRIED);
|
||||
play_seq();
|
||||
}
|
||||
}
|
||||
|
||||
/* check_sword_hurting (seg002:0D1A): прогнать проверку с ОБЕИХ сторон. */
|
||||
void pop_check_sword_hurting(void) __banked
|
||||
{
|
||||
uint8_t kf = Kid.frame;
|
||||
if (kf == 0 || (kf >= 219 && kf < 229)) return; /* подъём по лестнице */
|
||||
pop_loadshad_and_opp();
|
||||
check_hurting();
|
||||
pop_saveshad_and_opp();
|
||||
pop_loadkid_and_opp();
|
||||
check_hurting();
|
||||
pop_savekid_and_opp();
|
||||
}
|
||||
|
||||
/* check_sword_hurt (seg002:0CD4): применить помеченные попадания. Если
|
||||
* ранен страж — Киду в этом кадре урон уже не проходит (в оригинале так же:
|
||||
* его action сбрасывается ДО проверки). */
|
||||
void pop_check_sword_hurt(void) __banked
|
||||
{
|
||||
if (Guard.action == ACTION_99_HURT) {
|
||||
if (Kid.action == ACTION_99_HURT) Kid.action = ACTION_1_RUN;
|
||||
pop_loadshad();
|
||||
hurt_by_sword();
|
||||
pop_saveshad();
|
||||
guard_refrac = (int8_t)REFRACTIMER[guard_skill];
|
||||
} else if (Kid.action == ACTION_99_HURT) {
|
||||
pop_loadkid();
|
||||
hurt_by_sword();
|
||||
pop_savekid();
|
||||
}
|
||||
}
|
||||
|
||||
/* play_guard (seg006): ИИ + общий диспетчер. Отдельная ветка «HP кончились»:
|
||||
* пока страж жив (alive < 0) и HP есть — обычный ход; как только HP дошли до
|
||||
* нуля, он переводится в «умирает» (alive = 0), и дальше тот же control()
|
||||
* ставит ему seq_71_dying. Уже умирающий сюда попадает с alive >= 0 и просто
|
||||
* доигрывает последовательность. */
|
||||
static void play_guard(void)
|
||||
{
|
||||
if (Char.alive < 0 && guardhp_curr == 0) {
|
||||
Char.alive = 0;
|
||||
/* on_guard_killed (seg006:1927) — у нас пока только звук победы,
|
||||
* которого нет; для уровня 1 других эффектов там нет. */
|
||||
}
|
||||
autocontrol_opponent();
|
||||
pop_control();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user