Compare commits
13 Commits
cf06896dbd
...
2f3e854854
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f3e854854 | |||
| 75a51fb1db | |||
| a32b66700f | |||
| 59e51f7e83 | |||
| b22cee3456 | |||
| 2e90eaf7d7 | |||
| 3983fa4513 | |||
| d014a3f577 | |||
| 4f7d9c0596 | |||
| 37fc572cc3 | |||
| 99b430f2ed | |||
| d438a1d3da | |||
| 79ea473910 |
@@ -4,6 +4,15 @@
|
||||
гипотеза с причиной, по которой её стоит проверить, и с тем, что мешает
|
||||
сделать это прямо сейчас.
|
||||
|
||||
## Заменить генератор псевдослучайных чисел
|
||||
|
||||
Сейчас стоит LCG оригинала, шаг на ассемблере (~1 020 тактов), бит-в-бит
|
||||
совместимый с SDLPoP. Есть более дешёвые Z80-генераторы (86–148 тактов),
|
||||
но потолок выигрыша — 2 814 тактов за кадр, 0.65 %, и он растворяется в
|
||||
обёртках вызова. Тексты процедур, разбор качества и порядок действий —
|
||||
`prng_alternatives.md`. Первый шаг там не про генератор: слить приведение
|
||||
к диапазону в ту же asm-процедуру, чтобы на вызов был один `call`, а не три.
|
||||
|
||||
## Отключать мышь на время игры
|
||||
|
||||
**Гипотеза.** Мышь на Sprinter — источник прерываний (обёртки RST 30h,
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# Генераторы псевдослучайных чисел: запасные варианты
|
||||
|
||||
Что сейчас стоит в порте, какие есть альтернативы и сколько на них реально
|
||||
можно выиграть. Заготовка на случай, если упрёмся в бюджет кадра —
|
||||
**сейчас менять ничего не нужно**.
|
||||
|
||||
## Что стоит сейчас
|
||||
|
||||
`pop_geom.c`, ветка `POP_PRANDOM_EXACT=1` (по умолчанию) — LCG оригинала
|
||||
`s = s*214013 + 2531011`, шаг написан на Z80-ассемблере (единственное такое
|
||||
место в порте). Схема Горнера по разреженной записи константы:
|
||||
|
||||
```
|
||||
214013 = ((((1<<1)+1)<<2 + 1)<<4 + 1)<<10 - 3
|
||||
```
|
||||
|
||||
17 удвоений, три сложения, одно вычитание; величина `3*s`, нужная в конце,
|
||||
попадается по дороге на втором шаге. Тело — **≈1 020 тактов** по статическому
|
||||
подсчёту. Бит-в-бит совместим с SDLPoP, поэтому по картинке можно сверяться
|
||||
с эталоном.
|
||||
|
||||
Вторая ветка, `POP_PRANDOM_EXACT=0` — xorshift16 + шаг Вейля на C.
|
||||
Совместимость теряется.
|
||||
|
||||
Замер в MAME, комната 3, 175 кадров (медиана кадра):
|
||||
|
||||
| вариант | кадр | prandom → torch_draw |
|
||||
|---|---|---|
|
||||
| C, бит-в-бит (16-битные половины) | 403 632 | 10 933 |
|
||||
| C, xorshift16 + Вейль | 397 986 | 7 927 |
|
||||
| **asm, бит-в-бит (сейчас)** | **400 800** | **9 331** |
|
||||
|
||||
## Вариант A — комбинированный LFSR + LCG, ~148 тактов
|
||||
|
||||
Период > 4 млрд (lcm(65536, 65535) ≈ 4.29e9), младшие биты не вырождены.
|
||||
|
||||
```z80
|
||||
prng16:
|
||||
seed1=$+1
|
||||
ld hl, 9999
|
||||
ld b, h
|
||||
ld c, l
|
||||
add hl, hl
|
||||
add hl, hl
|
||||
inc l
|
||||
add hl, bc
|
||||
ld (seed1), hl
|
||||
seed2=$+1
|
||||
ld hl, 987
|
||||
add hl, hl
|
||||
sbc a, a
|
||||
and 101101b
|
||||
xor l
|
||||
ld l, a
|
||||
ld (seed2), hl
|
||||
add hl, bc
|
||||
ret
|
||||
```
|
||||
|
||||
Устройство: `seed1` — LCG `x = 5x + 1` (по модулю 2^16; `inc l` вместо
|
||||
`inc hl` — экономия байта, на период не влияет). `seed2` — 16-битный
|
||||
LFSR Галуа: сдвиг влево, и если выехала единица, XOR младшего байта с маской
|
||||
`0x2D` (примитивный многочлен `x^16 + x^5 + x^3 + x^2 + 1`). На выходе
|
||||
сумма обоих состояний — она и разрушает регулярность младших бит LCG.
|
||||
|
||||
**Что мешает взять как есть:** сиды зашиты в код (SMC), а нам нужны ДВЕ
|
||||
независимые последовательности — раскладка кладки и анимация тайлов.
|
||||
Пришлось бы передавать состояние через указатель, как сейчас у `pop_prandom`
|
||||
(это +20…40 тактов, не принципиально).
|
||||
|
||||
## Вариант B — xorshift(7,9,8), ~86 тактов
|
||||
|
||||
Самый быстрый, период 65535.
|
||||
|
||||
```z80
|
||||
xrnd:
|
||||
ld hl, 1 ; seed must not be 0
|
||||
ld a, h
|
||||
rra
|
||||
ld a, l
|
||||
rra
|
||||
xor h
|
||||
ld h, a
|
||||
ld a, l
|
||||
rra
|
||||
ld a, h
|
||||
rra
|
||||
xor l
|
||||
ld l, a
|
||||
xor h
|
||||
ld h, a
|
||||
ld (xrnd+1), hl
|
||||
ret
|
||||
```
|
||||
|
||||
**Две оговорки.** Ноль — неподвижная точка, а сид раскладки кладки у нас
|
||||
считается как `номер комнаты + смещение ряда + колонка` и вполне может
|
||||
оказаться нулём: нужен либо guard, либо шаг Вейля поверх. И тот же SMC-сид,
|
||||
что в варианте A.
|
||||
|
||||
## Чего НЕ брать: RND из Apple II
|
||||
|
||||
Оригинальный `Prince-of-Persia-Apple-II`:
|
||||
|
||||
```
|
||||
RNDseed := (5 * RNDseed + 23) mod 256
|
||||
```
|
||||
|
||||
```asm
|
||||
RND
|
||||
lda RNDseed
|
||||
asl
|
||||
asl
|
||||
clc
|
||||
adc RNDseed
|
||||
clc
|
||||
adc #23
|
||||
sta RNDseed
|
||||
rts
|
||||
```
|
||||
|
||||
Полный период 256 (`a ≡ 1 mod 4`, `c` нечётное), и для своего движка он
|
||||
работал. Нам не годится: у LCG по модулю 256 младшие биты вырождены — бит 0
|
||||
просто чередуется. Наши вызовы это увидят: раскладка кладки берёт
|
||||
`prandom(1)` (ОДИН бит) и `prandom(4)`, то есть вместо шума получилась бы
|
||||
аккуратная шахматка.
|
||||
|
||||
## Сколько реально можно выиграть
|
||||
|
||||
Меньше, чем кажется по числам 86/148 против 1 020. Тело генератора — уже не
|
||||
весь расход: остаются обёртка `pop_prandom`, приведение к диапазону
|
||||
`pop_rnd_fit` и ABI вызова. Верхняя граница выигрыша видна из замера выше:
|
||||
между нынешним asm-LCG и самым дешёвым из проверенных вариантов разница
|
||||
**2 814 тактов за кадр (0.65 %)** при двух вызовах за кадр, и это ПОТОЛОК —
|
||||
любой из вариантов A/B ниже него не опустится.
|
||||
|
||||
Порядок действий, если понадобится:
|
||||
|
||||
1. Сначала убрать обёртки: слить `pop_rnd_fit` в ту же asm-процедуру, чтобы
|
||||
на вызов приходился один `call`, а не три. Это ничего не ломает и не
|
||||
трогает совместимость с эталоном.
|
||||
2. И только если этого мало — менять генератор, начиная с варианта A
|
||||
(качество последовательности у него не хуже LCG, в отличие от B).
|
||||
|
||||
Важно помнить: число вызовов вырастет с боёвкой. Сейчас их два за кадр
|
||||
(факелы), а `guard_advance` / `guard_block` / `guard_strike` дёргают
|
||||
`prandom(255)` каждый по разу за кадр боя — то есть при драке станет 5–6, и
|
||||
цена вопроса вырастет во столько же раз.
|
||||
@@ -37,5 +37,5 @@ static const char *const kid_atl[KID_PAGES] = {
|
||||
};
|
||||
#define KID_PAL_FILE "kid.pal"
|
||||
#define KID_SWORD_ATL "sword.atl" /* chtab_0: меч в руке */
|
||||
#define KID_SWORD_ID0 20 /* индекс в атласе = sword_tbl.id - ID0 */
|
||||
#define KID_SWORD_ID0 0 /* индекс в атласе = sword_tbl.id - ID0 */
|
||||
#endif
|
||||
|
||||
Binary file not shown.
@@ -14,7 +14,7 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
||||
# банк1→банк2→банк1 в MAME 2026-07-30). Теперь в банки можно уводить и
|
||||
# логику, которая рисует. См. docs/layout_plan_v2.md §2, memory
|
||||
# sdcc_banked_call_rules.
|
||||
EXTRA_FLAGS ?= --gfx 256 --bank 2=pop_bg.c --bank 2=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c $(PROF_FLAGS)
|
||||
EXTRA_FLAGS ?= --gfx 256 --bank 2=pop_bg.c --bank 4=pop_gdraw.c --bank 3=pop_map.c --bank 1=guards.c $(PROF_FLAGS)
|
||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||
|
||||
@@ -10,17 +10,490 @@
|
||||
* пока исполняется банк, резидентной страницы в адресном пространстве
|
||||
* нет. Поэтому фон/окклюзию за стража рисует главный цикл.
|
||||
*
|
||||
* Сейчас — каркас: тик есть, ИИ и боёвка (порт seg005 control_guard*,
|
||||
* seg004 fight/collision) добавляются сюда.
|
||||
* Порт: seg003 check_can_guard_see_kid, seg002 autocontrol_*, seg000
|
||||
* play_guard_frame, seg006 char_opp_dist. ДИСПЕТЧЕР у соперника не свой:
|
||||
* ИИ выставляет те же глобалы control_* (pop_ctrl.h), что и ввод игрока, а
|
||||
* дальше работает общий pop_control().
|
||||
*/
|
||||
#include "pop_guard.h"
|
||||
#include "pop_kid.h"
|
||||
#include "pop_ctrl.h"
|
||||
#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] */
|
||||
|
||||
#define TILE_GATE 4
|
||||
#define TILE_DOORTOP_FLOOR 7
|
||||
#define TILE_LOOSE 11
|
||||
#define TILE_DOORTOP 12
|
||||
#define TILE_CHOMPER 18
|
||||
#define TILE_WALL 20
|
||||
|
||||
/* tile_is_floor (seg004): не-пол — пустота, ковёр, верх двери, стена и
|
||||
* «половинки» 26..29. Дубликат крошечного switch'а из pop_map: тянуть его
|
||||
* через границу банка дороже, чем повторить. */
|
||||
static uint8_t tile_is_floor(uint8_t t)
|
||||
{
|
||||
switch (t) {
|
||||
case 0: case 9: case 12: case 20:
|
||||
case 26: case 27: case 28: case 29:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Тайл в ряду Кида по X-координате персонажа (порт get_tile_at_kid,
|
||||
* seg003:761): xpos − 7 делится на ширину тайла с округлением вниз. */
|
||||
static int8_t tile_col; /* колонка последнего tile_at_kid */
|
||||
|
||||
static uint8_t tile_at_kid(int16_t xpos)
|
||||
{
|
||||
/* get_tile_div_mod_m7 (seg006:697): колонка = floor((xpos − 7 − 58)/14),
|
||||
* СРАЗУ в координатах комнаты (58 = x_bump[FIRST_ONSCREEN_COLUMN]).
|
||||
* Оригинал берёт её из tile_div_tbl, у нас — делением с округлением
|
||||
* вниз, как в pop_map. */
|
||||
int16_t x = xpos - 7 - SCREENSPACE_X;
|
||||
int16_t col = x / TILE_SIZEX;
|
||||
if (x % TILE_SIZEX < 0) --col;
|
||||
tile_col = (int8_t)col;
|
||||
return pop_tile_at(tile_col, Kid.curr_row);
|
||||
}
|
||||
|
||||
/* check_can_guard_see_kid (seg003:688) — луч видимости по ряду.
|
||||
* Результат в can_guard_see_kid: 0 не видит / 1 видит, но не пойдёт /
|
||||
* 2 видит и пойдёт. */
|
||||
void pop_check_can_guard_see_kid(void) __banked
|
||||
{
|
||||
uint8_t kid_frame = Kid.frame;
|
||||
int16_t left_pos, right_pos;
|
||||
uint8_t tile;
|
||||
|
||||
if (!(Guard.charid != 0 &&
|
||||
/* кадры 219..228 — подъём по лестнице: там страж Кида не видит */
|
||||
kid_frame != 0 && (kid_frame < 219 || kid_frame >= 229) &&
|
||||
Kid.alive < 0 && Guard.alive < 0 &&
|
||||
Kid.room == Guard.room && Kid.curr_row == Guard.curr_row)) {
|
||||
can_guard_see_kid = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
can_guard_see_kid = 2;
|
||||
left_pos = pop_x_bump[Kid.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||
right_pos = pop_x_bump[Guard.curr_col + FIRST_ONSCREEN_COLUMN] + TILE_MIDX;
|
||||
if (left_pos > right_pos) {
|
||||
int16_t t = left_pos; left_pos = right_pos; right_pos = t;
|
||||
}
|
||||
/* Чомпер стоит у ЛЕВОГО края тайла — свой тайл он не перекрывает. */
|
||||
if (tile_at_kid(left_pos) == TILE_CHOMPER)
|
||||
left_pos += TILE_SIZEX;
|
||||
/* Ворота — у ПРАВОГО края, поэтому крайний правый тайл не считается. */
|
||||
if (tile_at_kid(right_pos) == TILE_GATE)
|
||||
right_pos -= TILE_SIZEX;
|
||||
|
||||
while (left_pos <= right_pos) {
|
||||
tile = tile_at_kid(left_pos);
|
||||
/* Сквозь это не видно вовсе. */
|
||||
if (tile == TILE_WALL || tile == TILE_DOORTOP_FLOOR || tile == TILE_DOORTOP) {
|
||||
can_guard_see_kid = 0;
|
||||
return;
|
||||
}
|
||||
/* Видно, но страж не пойдёт: провалится / попадёт под чомпер /
|
||||
* упрётся в неоткрытые ворота / шагнёт в дыру. */
|
||||
if (tile == TILE_LOOSE || tile == TILE_CHOMPER || !tile_is_floor(tile)) {
|
||||
can_guard_see_kid = 1;
|
||||
} else if (tile == TILE_GATE &&
|
||||
pop_trob_modif(Kid.room)[Kid.curr_row * 10 + tile_col] < 112) {
|
||||
can_guard_see_kid = 1; /* ворота подняты не до конца */
|
||||
}
|
||||
left_pos += TILE_SIZEX;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Синтетический ввод (seg002:0706..) --------------------------- */
|
||||
static void move_0_nothing(void)
|
||||
{
|
||||
control_shift = control_y = control_x = control_shift2 = 0;
|
||||
control_down = control_up = control_backward = control_forward = 0;
|
||||
}
|
||||
static void move_1_forward(void)
|
||||
{
|
||||
control_x = POP_CONTROL_HELD_FORWARD;
|
||||
control_forward = POP_CONTROL_HELD;
|
||||
}
|
||||
static void move_2_backward(void)
|
||||
{
|
||||
control_backward = POP_CONTROL_HELD;
|
||||
control_x = POP_CONTROL_HELD_BACKWARD;
|
||||
}
|
||||
static void move_3_up(void)
|
||||
{
|
||||
control_y = POP_CONTROL_HELD_UP;
|
||||
control_up = POP_CONTROL_HELD;
|
||||
}
|
||||
static void move_4_down(void)
|
||||
{
|
||||
control_down = POP_CONTROL_HELD;
|
||||
control_y = POP_CONTROL_HELD_DOWN;
|
||||
}
|
||||
static void move_6_shift(void)
|
||||
{
|
||||
control_shift = POP_CONTROL_HELD;
|
||||
control_shift2 = POP_CONTROL_HELD;
|
||||
}
|
||||
static void move_down_back(void)
|
||||
{
|
||||
control_down = POP_CONTROL_HELD;
|
||||
move_2_backward();
|
||||
}
|
||||
static void move_down_forw(void)
|
||||
{
|
||||
control_down = POP_CONTROL_HELD;
|
||||
move_1_forward();
|
||||
}
|
||||
|
||||
/* autocontrol_guard_inactive (seg002:710): страж с убранным мечом.
|
||||
* Пока Кид за спиной — страж не реагирует; исключение — «шум» Кида
|
||||
* (is_guard_notice), от которого страж оборачивается. */
|
||||
static void autocontrol_guard_inactive(void)
|
||||
{
|
||||
int16_t distance;
|
||||
if (Kid.alive >= 0) return;
|
||||
distance = pop_char_opp_dist();
|
||||
/* В оригинале условие записано как (word)distance < (word)-8 —
|
||||
* беззнаковое сравнение, истинное для ВСЕГО, кроме диапазона −8..−1.
|
||||
* То есть: Кид не «вплотную за спиной». */
|
||||
if (Opp.curr_row != Char.curr_row || distance >= 0 || distance < -8) {
|
||||
if (is_guard_notice) {
|
||||
is_guard_notice = 0;
|
||||
if (distance < 0) {
|
||||
if (distance < -4) move_4_down(); /* обернуться на шум */
|
||||
return;
|
||||
}
|
||||
} else if (distance < 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (can_guard_see_kid) {
|
||||
/* Увидел — переходит в боевую стойку (вниз+вперёд = достать меч). */
|
||||
move_down_forw();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Активный страж (меч обнажён), seg002:737.. -------------------- *
|
||||
* Таблицы вероятностей по 12 градациям мастерства (seg002:26..38). Бросок
|
||||
* — prandom(255) с СОБСТВЕННЫМ сидом: у нас последовательности разведены
|
||||
* по доменам (кладка / анимация тайлов / бой), см. pop_geom.h. */
|
||||
static const uint8_t STRIKEPROB[NUM_GUARD_SKILLS] = { 61,100, 61, 61, 61, 40,100,220, 0, 48, 32, 48};
|
||||
static const uint8_t RESTRIKEPROB[NUM_GUARD_SKILLS] = { 0, 0, 0, 5, 5,175, 16, 8, 0,255,255,150};
|
||||
static const uint8_t BLOCKPROB[NUM_GUARD_SKILLS] = { 0,150,150,200,200,255,200,250, 0,255,255,255};
|
||||
static const uint8_t IMPBLOCKPROB[NUM_GUARD_SKILLS] = { 0, 61, 61,100,100,145,100,250, 0,145,255,175};
|
||||
static const uint8_t ADVPROB[NUM_GUARD_SKILLS] = {255,200,200,200,255,255,200, 0, 0,255,100,100};
|
||||
|
||||
/* Бросок «сработало ли»: в оригинале prob > prandom(255). */
|
||||
static uint8_t roll(uint8_t prob)
|
||||
{
|
||||
return (uint8_t)(prob > (uint8_t)pop_prandom(&pop_fight_seed, 255));
|
||||
}
|
||||
|
||||
/* guard_advance (seg002:0AF5): наступать. Мастерство 0 наступает всегда,
|
||||
* остальные — только пока Кид не замахнулся. */
|
||||
static void guard_advance(void)
|
||||
{
|
||||
if (guard_skill == 0 || kid_sword_strike == 0) {
|
||||
if (roll(ADVPROB[guard_skill])) move_1_forward();
|
||||
}
|
||||
}
|
||||
|
||||
/* guard_block (seg002:0B1D): блокировать замах Кида. Если Кид только что
|
||||
* пробил блок (justblocked) — своя, «импровизационная» вероятность. */
|
||||
static void guard_block(void)
|
||||
{
|
||||
uint8_t of = Opp.frame;
|
||||
if (of == FRAME_152_STRIKE_2 || of == FRAME_153_STRIKE_3 ||
|
||||
of == FRAME_162_BLOCK_TO_STRIKE) {
|
||||
if (roll(justblocked ? IMPBLOCKPROB[guard_skill]
|
||||
: BLOCKPROB[guard_skill]))
|
||||
move_3_up();
|
||||
}
|
||||
}
|
||||
|
||||
/* guard_strike (seg002:0B73): бить. По замаху Кида не бьём (кадры 169/151);
|
||||
* из своего парирования — по отдельной вероятности «добить». */
|
||||
static void guard_strike(void)
|
||||
{
|
||||
uint8_t of = Opp.frame, cf = Char.frame;
|
||||
if (of == FRAME_169_BEGIN_BLOCK || of == FRAME_151_STRIKE_1) return;
|
||||
if (cf == FRAME_161_PARRY || cf == FRAME_150_PARRY) {
|
||||
if (roll(RESTRIKEPROB[guard_skill])) move_6_shift();
|
||||
} else {
|
||||
if (roll(STRIKEPROB[guard_skill])) move_6_shift();
|
||||
}
|
||||
}
|
||||
|
||||
/* autocontrol_guard_kid_armed (seg002:0AC1): Кид с мечом. Вплотную и
|
||||
* далеко — наступаем; в «рабочей» дистанции 10..28 сначала пробуем блок, и
|
||||
* только отдышавшись бьём. */
|
||||
static void autocontrol_guard_kid_armed(int16_t distance)
|
||||
{
|
||||
if (distance < 10 || distance >= 29) {
|
||||
guard_advance();
|
||||
} else {
|
||||
guard_block();
|
||||
if (guard_refrac == 0) {
|
||||
if (distance < 12 || distance >= 29) guard_advance();
|
||||
else guard_strike();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* autocontrol_guard_kid_in_sight (seg002:0A93). */
|
||||
static void autocontrol_guard_kid_in_sight(int16_t distance)
|
||||
{
|
||||
if (Opp.sword == SWORD_2_DRAWN) {
|
||||
autocontrol_guard_kid_armed(distance);
|
||||
} else if (guard_refrac == 0) {
|
||||
/* Кид безоружен: подойти и рубить. */
|
||||
if (distance < 29) move_6_shift();
|
||||
else move_1_forward();
|
||||
}
|
||||
}
|
||||
|
||||
/* Тайл перед стражем / через один (порт get_tile_infrontof_char и
|
||||
* get_tile_infrontof2_char, seg004) — но для Char, а не для Kid. */
|
||||
static uint8_t tile_infront(int8_t step)
|
||||
{
|
||||
int8_t d = (int8_t)(Char.direction < 0 ? -step : step);
|
||||
return pop_tile_at((int8_t)(Char.curr_col + d), Char.curr_row);
|
||||
}
|
||||
|
||||
/* autocontrol_guard_kid_far (seg002:09CB): идём, если впереди есть пол хотя
|
||||
* бы через тайл; иначе пятимся (не шагать в пропасть). */
|
||||
static void autocontrol_guard_kid_far(void)
|
||||
{
|
||||
if (tile_is_floor(tile_infront(1)) || tile_is_floor(tile_infront(2)))
|
||||
move_1_forward();
|
||||
else
|
||||
move_2_backward();
|
||||
}
|
||||
|
||||
/* autocontrol_guard_active (seg002:737). */
|
||||
static void autocontrol_guard_active(void)
|
||||
{
|
||||
uint8_t cf = Char.frame;
|
||||
uint8_t of;
|
||||
int16_t distance;
|
||||
|
||||
/* Кадр 166 (неактивная стойка) и всё, что ниже 150, — не боевые позы. */
|
||||
if (cf == FRAME_166_STAND_INACTIVE || cf < 150 || can_guard_see_kid == 1)
|
||||
return;
|
||||
|
||||
if (can_guard_see_kid == 0) {
|
||||
/* TODO: droppedout -> guard_follows_kid_down (seg002:09F8) — вместе
|
||||
* с физикой стража; скелета (charid 4) ветка тоже не берёт. */
|
||||
if (Char.charid != CHARID_4_SKELETON) move_down_back();
|
||||
return;
|
||||
}
|
||||
|
||||
/* can_guard_see_kid == 2 */
|
||||
of = Opp.frame;
|
||||
distance = pop_char_opp_dist();
|
||||
/* Кид падает/приземляется в отскоке — не мешаем ему, ждём. */
|
||||
if (distance >= 12 && of >= 102 && of < 118 && Opp.action == 5)
|
||||
return;
|
||||
|
||||
if (distance < 35) {
|
||||
if ((Char.sword < SWORD_2_DRAWN && distance < 8) || distance < 12) {
|
||||
/* Слишком близко: если смотрим в одну сторону — развернуться. */
|
||||
if (Char.direction == Opp.direction) move_2_backward();
|
||||
else move_1_forward();
|
||||
} else {
|
||||
autocontrol_guard_kid_in_sight(distance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (guard_refrac != 0) return;
|
||||
if (Char.direction != Opp.direction) {
|
||||
/* Кид разбежался — встречаем ударом, а не шагом. */
|
||||
if (of >= 7 && of < 15) { /* бег */
|
||||
if (distance < 40) move_6_shift();
|
||||
return;
|
||||
} else if (of >= 34 && of < 44) { /* разбег в прыжок */
|
||||
if (distance < 50) move_6_shift();
|
||||
return;
|
||||
}
|
||||
}
|
||||
autocontrol_guard_kid_far();
|
||||
}
|
||||
|
||||
/* autocontrol_guard (seg002:701) + autocontrol_opponent (seg002:628). */
|
||||
static void autocontrol_opponent(void)
|
||||
{
|
||||
move_0_nothing();
|
||||
/* Таймеры боя убывают раз в кадр — здесь, как в оригинале. */
|
||||
if (justblocked) --justblocked;
|
||||
if (kid_sword_strike) --kid_sword_strike;
|
||||
if (guard_refrac) --guard_refrac;
|
||||
if (Char.sword < SWORD_2_DRAWN) autocontrol_guard_inactive();
|
||||
else autocontrol_guard_active();
|
||||
}
|
||||
|
||||
/* ---- Урон мечом (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();
|
||||
}
|
||||
|
||||
/* pop_guard_tick — порт play_guard_frame (seg000:1246) в объёме, который у
|
||||
* нас уже есть: окно Char/Opp вокруг ИИ, диспетчера и интерпретатора
|
||||
* последовательностей. Физика стража (fall_accel/коллизия/бамп/пики) —
|
||||
* отдельным шагом. */
|
||||
void pop_guard_tick(void) __banked
|
||||
{
|
||||
if (Guard.charid == 0) return; /* стража в комнате нет */
|
||||
pop_loadshad_and_opp(); /* Char = Guard, Opp = Kid */
|
||||
play_guard();
|
||||
/* Анимация: та же seqtbl, что у Кида, но своя таблица кадров —
|
||||
* выбирается по Char.charid внутри load_frame (seg006:0293). */
|
||||
pop_guard_play();
|
||||
/* TODO: порт control_guard/fight (seg005/seg004) — состояние в Guard. */
|
||||
play_seq();
|
||||
pop_saveshad();
|
||||
}
|
||||
|
||||
@@ -42,4 +42,31 @@ static const uint16_t kid_seq_off[KID_NSEQ] = {
|
||||
0x21FC,0x2240,0x2257,
|
||||
};
|
||||
|
||||
/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0.
|
||||
* Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет.
|
||||
* Смещения x/y — ЭКРАННЫЕ (применяются после calc_screen_x_coord),
|
||||
* x — «вперёд» по направлению персонажа. */
|
||||
#define KID_NSWORD 51
|
||||
#define KID_SWORD_ID_INIT { \
|
||||
0xFF,0x00,0x05,0x01,0x02,0x06,0x07,0x03,0x04,0x1E,0x08,0x09, \
|
||||
0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x13,0x11, \
|
||||
0x11,0x07,0x01,0x1C,0x08,0x04,0x04,0x0A,0x0D,0x0D,0x0D,0x14, \
|
||||
0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0xFF,0xFF,0x1D,0x08,0x1F, \
|
||||
0x20,0x21,0x08, \
|
||||
}
|
||||
#define KID_SWORD_DX_INIT { \
|
||||
0,0,-9,7,17,7,0,17,16,12,13,7, \
|
||||
10,10,22,28,13,0,0,21,14,21,22,7, \
|
||||
15,0,7,14,7,6,9,11,24,19,21,7, \
|
||||
14,14,14,28,28,21,14,14,21,0,8,14, \
|
||||
14,7,8, \
|
||||
}
|
||||
#define KID_SWORD_DY_INIT { \
|
||||
0,-9,-29,-25,-26,-14,-5,-16,-19,-9,-34,-25, \
|
||||
-16,-11,-21,-23,-35,-38,-29,-19,-23,-22,-23,-13, \
|
||||
-18,-8,-27,-28,-27,-23,-21,-18,-23,-23,-23,-32, \
|
||||
-32,-31,-29,-28,-28,-25,-22,-25,-25,-16,-37,-24, \
|
||||
-24,-14,-37, \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -354,7 +354,7 @@ static uint8_t wall_modifier(int row, int col)
|
||||
}
|
||||
|
||||
/* ---- PRNG (seg009.c:321, 32-бит LCG) — только на ВХОД в комнату ---- */
|
||||
static unsigned long rnd_seed;
|
||||
static pop_rnd_t rnd_seed;
|
||||
|
||||
#define prandom(maxv) pop_prandom(&rnd_seed, (maxv))
|
||||
|
||||
@@ -417,7 +417,7 @@ static void wall_rnd(int row, int col)
|
||||
* соседнего РЯДА и чужую раскладку (тихий баг вместо промаха). */
|
||||
int idx = (col >= 0 && col < 10) ? (row + 1) * 10 + col : -1;
|
||||
uint8_t a, b, bg;
|
||||
unsigned long saved;
|
||||
pop_rnd_t saved;
|
||||
if (idx >= 0 && idx < WP_N && wp_ok[idx]) {
|
||||
a = wp_a[idx]; b = wp_b[idx];
|
||||
wr_mid_div = (uint8_t)(a & 1); wr_mid_off = (uint8_t)((a >> 1) & 7);
|
||||
@@ -430,7 +430,7 @@ static void wall_rnd(int row, int col)
|
||||
saved = rnd_seed;
|
||||
/* Верхняя полоса (row<0): SDLPoP рисует её как ряд 2 комнаты сверху
|
||||
* (drawn_row=2 для seed, draw_main_y=-1 для позиции) — seed по row 2. */
|
||||
rnd_seed = (unsigned long)(g_room + TBL_LINE[row < 0 ? 2 : row] + col);
|
||||
pop_prandom_set(rnd_seed, g_room + TBL_LINE[row < 0 ? 2 : row] + col);
|
||||
prandom(1); /* сброс */
|
||||
wr_mid_div = (uint8_t)prandom(1);
|
||||
wr_mid_off = (uint8_t)prandom(4);
|
||||
@@ -1592,7 +1592,7 @@ void pop_fore_over_kid(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||
* (seq_77..seq_84). Появятся падающие/висящие стражи — тогда и общий
|
||||
* порт redraw_at_char2, а не догадки сейчас. */
|
||||
void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||
int8_t dir)
|
||||
int8_t dir) __banked
|
||||
{
|
||||
int8_t r, c;
|
||||
fclip_on = 1; /* окно = прямоугольник спрайта */
|
||||
|
||||
@@ -25,6 +25,19 @@
|
||||
/* Экранный y нижней границы поля (ниже — статус-полоса + борт → клип). */
|
||||
#define POP_CLIP_BOTTOM (POP_YOFF + POP_PLAYFIELD_H)
|
||||
|
||||
/* Спрайт целиком на экране И укладывается в 8-битные параметры noclip-
|
||||
* примитивов libbgi? Условие входа в gfx_blit_cols_part_noclip: клипающий
|
||||
* вариант платит ~5.6 К тактов подготовки на КАЖДЫЙ вызов, независимо от
|
||||
* того, вылезает край или нет.
|
||||
* static inline, а не чистый inline: SDCC оставляет мёртвое тело в каждом TU
|
||||
* (memory sdcc_inline_codegen_findings), но здесь это ~30 байт на два TU —
|
||||
* дешевле риска неразрешённой ссылки. */
|
||||
static inline uint8_t pop_onscreen_cols(int x, int y, uint16_t w, uint16_t h)
|
||||
{
|
||||
return (uint8_t)(x >= 0 && y >= 0 && w < 256 && h < 256 &&
|
||||
x + (int)w <= 320 && y + (int)h <= 256);
|
||||
}
|
||||
|
||||
/* Загрузить 7 атласов фона. Звать ДО initgraph (как poc.c — atlas_load
|
||||
* трогает W3; проверенный порядок). Палитру pop_bg.pal грузит ПРИЛОЖЕНИЕ
|
||||
* после initgraph (gfx_pal_fload). 0 — OK, -1 — ошибка. */
|
||||
@@ -69,8 +82,11 @@ void pop_fore_set_clip(int x, int y, int w, int h) __banked;
|
||||
/* То же для НЕ-Kid персонажа (страж): только слой fore + полоса потолка,
|
||||
* без оверлеев поз виса/полёта/подъёма. obj_x — ЛОГИЧЕСКАЯ координата
|
||||
* (до ×8/7), как у kid_fp_obj_x(). */
|
||||
/* __banked: отрисовка стража уехала в СВОЙ банк (pop_gdraw.c), поэтому
|
||||
* вызов стал кроссбанковым и обязан идти через трамплин. Зовётся раз в
|
||||
* кадр, других вызывающих нет — цена одного трамплина здесь не видна. */
|
||||
void pop_fore_over_char(int obj_x, int obj_y, uint16_t w, uint16_t h,
|
||||
int8_t dir);
|
||||
int8_t dir) __banked;
|
||||
|
||||
/* Восстановить фон там, где в прошлых кадрах рисовался ОВЕРЛЕЙ
|
||||
* (draw_other_overlay/floor_overlay/полоса потолка): он выходит за
|
||||
|
||||
@@ -17,5 +17,7 @@
|
||||
extern uint8_t pop_cheats; /* 0 = выключены, иначе включены */
|
||||
|
||||
#define KBD_CHEAT_KILL 0x42 /* K (PS/2 set 2) — kill guard */
|
||||
#define KBD_CHEAT_IMMO 0x43 /* I (PS/2 set 2) — бессмертие Кида (toggle) */
|
||||
#define KBD_CHEAT_SWORD 0x1B /* S (PS/2 set 2) — выдать Киду меч */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <kbd_raw.h>
|
||||
#include "pop_kid.h"
|
||||
#include "pop_map.h"
|
||||
#include "pop_guard.h" /* charid, состояние меча, seq стража */
|
||||
|
||||
/* Отладочные клавиши осторожного шага (эмуляция Shift+стрелка) для тестов в
|
||||
* MAME, где Shift трудно подать: J = шаг влево, L = шаг вправо. PS/2 set2
|
||||
@@ -51,6 +52,7 @@
|
||||
#define SEQ_49_STAND_UP_FROM_CROUCH 49
|
||||
#define SEQ_50_CROUCH 50
|
||||
#define SEQ_79_CROUCH_HOP 79
|
||||
#define SEQ_78_DRINK 78 /* выпить зелье */
|
||||
#define SEQ_91_PICKUP_SWORD 91 /* поднять меч (pickupsword) */
|
||||
#define SEQ_STEP_BASE 28 /* +distance(1..14) = seq 29..42 шаг-N */
|
||||
#define SEQ_39_UNSAFE_STEP 39 /* шаг за кромку (distance 11) */
|
||||
@@ -74,7 +76,7 @@
|
||||
* X LEFT/-1..RIGHT/+1); *_forward/backward/up/down/shift2 — конечный
|
||||
* автомат held/released/ignore (авто-повтор); ctrl1_* — их память между
|
||||
* кадрами (rest/save). */
|
||||
static int8_t control_x, control_y, control_shift;
|
||||
int8_t control_x, control_y, control_shift;
|
||||
/* НЕ static: в оригинале это глобалы, через которые ИИ стража подаёт
|
||||
* СИНТЕТИЧЕСКИЙ ввод (autocontrol_*, seg002), а дальше работает тот же
|
||||
* control(), что и у игрока. Поэтому шина общая. */
|
||||
@@ -125,6 +127,11 @@ static uint8_t get_item_action(void)
|
||||
seqtbl_offset_char(SEQ_91_PICKUP_SWORD);
|
||||
return 1;
|
||||
}
|
||||
if (a == 3) { /* зелье — выпить (get_item) */
|
||||
control_shift2 = CONTROL_IGNORE;
|
||||
seqtbl_offset_char(SEQ_78_DRINK);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -139,6 +146,7 @@ static void run_jump(void)
|
||||
}
|
||||
|
||||
static void safe_step(void); /* fwd */
|
||||
static void draw_sword(void); /* fwd: нужен в control_standing */
|
||||
|
||||
static void forward_pressed(void)
|
||||
{
|
||||
@@ -232,7 +240,32 @@ static void control_standing(void)
|
||||
if (control_shift2 == CONTROL_HELD && control_shift == CONTROL_HELD &&
|
||||
get_item_action())
|
||||
return;
|
||||
/* Kid charid_0, без меча: остаётся нижняя часть control_standing. */
|
||||
/* Ветку «charid != kid && вниз+вперёд -> draw_sword» не портируем: у нас
|
||||
* соперник до control_standing не доходит, его перехватывает
|
||||
* control_guard_inactive в самом диспетчере (порядок как в seg005). */
|
||||
|
||||
/* seg005:352 — Кид с мечом ВИДИТ активного стража: достаёт клинок сам.
|
||||
* Без этого игрок стоит с мечом в ножнах, пока его рубят. */
|
||||
if (pop_have_sword) {
|
||||
if (offguard != 0 && control_shift >= CONTROL_RELEASED) {
|
||||
/* Меч убран намеренно (down в бою) — не доставать, пока не
|
||||
* отпустят Shift; в оригинале это переход на loc_6213. */
|
||||
} else if (can_guard_see_kid >= 2) {
|
||||
int16_t d = pop_char_opp_dist();
|
||||
if (d >= -10 && d < 90) {
|
||||
holding_sword = 1;
|
||||
/* (word)d < (word)-6 — беззнаковое: истинно для ВСЕГО, кроме
|
||||
* −6..−1. То есть «страж вплотную за спиной» -> отступить,
|
||||
* иначе достать меч. */
|
||||
if (d >= -6 && d < 0) { back_pressed(); return; }
|
||||
draw_sword();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
offguard = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (control_shift == CONTROL_HELD) {
|
||||
if (control_backward == CONTROL_HELD) back_pressed();
|
||||
else if (control_up == CONTROL_HELD) up_pressed();
|
||||
@@ -324,10 +357,199 @@ static void control_jumpup(void)
|
||||
standing_jump();
|
||||
}
|
||||
|
||||
/* control() — диспетчер по кадру (seg005.c:252), Kid жив, без меча. */
|
||||
/* draw_sword (seg005:945): достать меч. Кид переходит в seq_55 (подъём
|
||||
* клинка со звуком), соперник — сразу в стойку en garde (seq_90). Стрелки
|
||||
* гасятся, чтобы «вперёд», которым меч достали, не сработало второй раз
|
||||
* уже как выпад. */
|
||||
static void draw_sword(void)
|
||||
{
|
||||
uint8_t seq_id = SEQ_55_DRAW_SWORD;
|
||||
control_forward = control_shift2 = release_arrows();
|
||||
if (Char.charid == CHARID_0_KID) {
|
||||
offguard = 0; /* TODO: play_sound(19) — звука пока нет */
|
||||
} else if (Char.charid != CHARID_1_SHADOW) {
|
||||
seq_id = SEQ_90_EN_GARDE; /* соперник встаёт сразу в стойку */
|
||||
}
|
||||
Char.sword = SWORD_2_DRAWN;
|
||||
seqtbl_offset_char(seq_id);
|
||||
}
|
||||
|
||||
/* control_guard_inactive (seg006:2123): неактивный страж реагирует ТОЛЬКО в
|
||||
* кадре 166 (стойка) и только на «вниз»: вниз+вперёд — достать меч,
|
||||
* вниз без «вперёд» — развернуться (stand flipped). */
|
||||
static void control_guard_inactive(void)
|
||||
{
|
||||
if (Char.frame == FRAME_166_STAND_INACTIVE && control_down == CONTROL_HELD) {
|
||||
if (control_forward == CONTROL_HELD) {
|
||||
draw_sword();
|
||||
} else {
|
||||
control_down = CONTROL_IGNORE;
|
||||
seqtbl_offset_char(SEQ_80_STAND_FLIPPED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Боёвка (seg005:964..) ---------------------------------------- *
|
||||
* Всё это ветки ОБЩЕГО диспетчера: у стража тот же control(), просто
|
||||
* control_* ему выставляет ИИ (autocontrol_*, guards.c), а не клавиатура. */
|
||||
|
||||
/* back_with_sword (seg005): шаг назад — только из стоек. */
|
||||
static void back_with_sword(void)
|
||||
{
|
||||
uint8_t f = Char.frame;
|
||||
if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
|
||||
f == FRAME_171_STAND_WITH_SWORD) {
|
||||
control_backward = CONTROL_IGNORE; /* без авто-повтора */
|
||||
seqtbl_offset_char(SEQ_57_BACK_WITH_SWORD);
|
||||
}
|
||||
}
|
||||
|
||||
/* forward_with_sword (seg005): шаг вперёд; у стража своя последовательность. */
|
||||
static void forward_with_sword(void)
|
||||
{
|
||||
uint8_t f = Char.frame;
|
||||
if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
|
||||
f == FRAME_171_STAND_WITH_SWORD) {
|
||||
control_forward = CONTROL_IGNORE;
|
||||
seqtbl_offset_char(Char.charid != CHARID_0_KID ? SEQ_56_GUARD_FORWARD
|
||||
: SEQ_86_FORWARD_WITH_SWORD);
|
||||
}
|
||||
}
|
||||
|
||||
/* sword_strike (seg005): выпад из стойки/шага либо добивание после парирования. */
|
||||
static void sword_strike(void)
|
||||
{
|
||||
uint8_t f = Char.frame;
|
||||
uint8_t seq_id;
|
||||
if (f == FRAME_157_WALK_WITH_SWORD || f == FRAME_158_STAND_WITH_SWORD ||
|
||||
f == FRAME_170_STAND_WITH_SWORD || f == FRAME_171_STAND_WITH_SWORD ||
|
||||
f == FRAME_165_WALK_WITH_SWORD) {
|
||||
seq_id = (Char.charid == CHARID_0_KID) ? SEQ_75_STRIKE : SEQ_58_GUARD_STRIKE;
|
||||
} else if (f == FRAME_150_PARRY || f == FRAME_161_PARRY) {
|
||||
seq_id = SEQ_66_STRIKE_AFTER_PARRY;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
control_shift2 = CONTROL_IGNORE;
|
||||
seqtbl_offset_char(seq_id);
|
||||
}
|
||||
|
||||
/* parry (seg005): блок. Условие сильно разное у Кида и у соперника —
|
||||
* соперник блокирует ТОЛЬКО на кадре 152 (второй кадр удара), а Кид ещё и
|
||||
* по 153 (и тогда последовательность прокручивается сразу). */
|
||||
static void parry(void)
|
||||
{
|
||||
uint8_t cf = Char.frame, of = Opp.frame;
|
||||
uint8_t seq_id = SEQ_62_PARRY;
|
||||
uint8_t do_play_seq = 0;
|
||||
|
||||
if (cf == FRAME_158_STAND_WITH_SWORD || cf == FRAME_170_STAND_WITH_SWORD ||
|
||||
cf == FRAME_171_STAND_WITH_SWORD || cf == FRAME_168_BACK ||
|
||||
cf == FRAME_165_WALK_WITH_SWORD) {
|
||||
if (pop_char_opp_dist() >= 32 && Char.charid != CHARID_0_KID) {
|
||||
back_with_sword();
|
||||
return;
|
||||
} else if (Char.charid == CHARID_0_KID) {
|
||||
if (of == FRAME_168_BACK) return;
|
||||
if (of != FRAME_151_STRIKE_1 && of != FRAME_152_STRIKE_2 &&
|
||||
of != FRAME_162_BLOCK_TO_STRIKE) {
|
||||
if (of == FRAME_153_STRIKE_3) do_play_seq = 1;
|
||||
}
|
||||
} else {
|
||||
if (of != FRAME_152_STRIKE_2) return;
|
||||
}
|
||||
} else {
|
||||
if (cf != FRAME_167_BLOCKED) return;
|
||||
seq_id = SEQ_61_PARRY_AFTER_STRIKE;
|
||||
}
|
||||
control_up = CONTROL_IGNORE;
|
||||
seqtbl_offset_char(seq_id);
|
||||
if (do_play_seq) play_seq();
|
||||
}
|
||||
|
||||
/* swordfight (seg005:0CDB) — разбор ввода в бою. Порядок веток важен:
|
||||
* удар старше «убрать меч», тот старше блока, блок старше шагов. */
|
||||
static void swordfight(void)
|
||||
{
|
||||
uint8_t f = Char.frame;
|
||||
uint8_t seq_id;
|
||||
|
||||
if (f == FRAME_161_PARRY && control_shift2 >= CONTROL_RELEASED) {
|
||||
seqtbl_offset_char(SEQ_57_BACK_WITH_SWORD); /* отскок после парирования */
|
||||
return;
|
||||
} else if (control_shift2 == CONTROL_HELD) {
|
||||
if (Char.charid == CHARID_0_KID) kid_sword_strike = 15;
|
||||
sword_strike();
|
||||
if (control_shift2 == CONTROL_IGNORE) return;
|
||||
}
|
||||
|
||||
if (control_down == CONTROL_HELD) {
|
||||
if (f == FRAME_158_STAND_WITH_SWORD || f == FRAME_170_STAND_WITH_SWORD ||
|
||||
f == FRAME_171_STAND_WITH_SWORD) {
|
||||
control_down = CONTROL_IGNORE;
|
||||
Char.sword = SWORD_0_SHEATHED;
|
||||
if (Char.charid == CHARID_0_KID) {
|
||||
offguard = 1; /* убрал намеренно — сразу не доставать */
|
||||
guard_refrac = 9; /* и страж на это время придержан */
|
||||
holding_sword = 0;
|
||||
seq_id = SEQ_93_PUT_SWORD_AWAY_FAST;
|
||||
} else if (Char.charid == CHARID_1_SHADOW) {
|
||||
seq_id = SEQ_92_PUT_SWORD_AWAY;
|
||||
} else {
|
||||
seq_id = SEQ_87_GUARD_BECOME_INACT; /* Кид ушёл из виду */
|
||||
}
|
||||
seqtbl_offset_char(seq_id);
|
||||
}
|
||||
} else if (control_up == CONTROL_HELD) {
|
||||
parry();
|
||||
} else if (control_forward == CONTROL_HELD) {
|
||||
forward_with_sword();
|
||||
} else if (control_backward == CONTROL_HELD) {
|
||||
back_with_sword();
|
||||
}
|
||||
}
|
||||
|
||||
/* control_with_sword (seg005:964): пока соперник виден и близко — бой;
|
||||
* иначе страж убирает меч, а Кид просто стоит. */
|
||||
static void control_with_sword(void)
|
||||
{
|
||||
if (Char.action >= 2) return; /* actions_2_hang_climb и выше */
|
||||
if (pop_tile_at(Char.curr_col, Char.curr_row) == 11 /* loose */ ||
|
||||
can_guard_see_kid >= 2) {
|
||||
int16_t d = pop_char_opp_dist();
|
||||
if (d < 90) {
|
||||
if (d >= 0) { swordfight(); return; }
|
||||
/* Соперник за спиной: далеко — развернуться, вплотную — драться. */
|
||||
if (d < -4) { seqtbl_offset_char(SEQ_60_TURN_WITH_SWORD); return; }
|
||||
swordfight();
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Не видит / далеко. */
|
||||
if (Char.charid < CHARID_2_GUARD) {
|
||||
if (Char.frame == FRAME_171_STAND_WITH_SWORD) {
|
||||
Char.sword = SWORD_0_SHEATHED;
|
||||
seqtbl_offset_char(SEQ_92_PUT_SWORD_AWAY); /* страж убит */
|
||||
}
|
||||
} else {
|
||||
swordfight();
|
||||
}
|
||||
}
|
||||
|
||||
/* control() — диспетчер по кадру (seg005.c:252) для АКТИВНОГО персонажа. */
|
||||
void pop_control(void)
|
||||
{
|
||||
uint8_t f = Char.frame;
|
||||
/* seg005 control(), самая первая ветка: персонаж УМИРАЕТ (alive >= 0).
|
||||
* Из любой стойки он валится в seq_71_dying; остальное управление в
|
||||
* этом состоянии не обрабатывается. Через это же место проходит чит K
|
||||
* (pop_guard_kill ставит Guard.alive = 0). */
|
||||
if (Char.alive >= 0) {
|
||||
if (f == FRAME_15_STAND || f == FRAME_166_STAND_INACTIVE ||
|
||||
f == FRAME_158_STAND_WITH_SWORD || f == FRAME_171_STAND_WITH_SWORD)
|
||||
seqtbl_offset_char(SEQ_71_DYING);
|
||||
return;
|
||||
}
|
||||
/* seg005 control(): в отскоке (bumped) и в свободном падении управление
|
||||
* НЕ обрабатывается вовсе — только гасятся стрелки. Без этого игрок
|
||||
* прерывает присед после удара (medland/плита по голове) нажатием ↑, и
|
||||
@@ -338,7 +560,13 @@ void pop_control(void)
|
||||
release_arrows();
|
||||
return;
|
||||
}
|
||||
if (f == FRAME_15_STAND || (f >= FRAME_50_TURN && f < 53))
|
||||
/* Порядок веток — как в seg005: меч старше charid, charid старше кадров.
|
||||
* Пока Kid безоружен, обе ветки — про стража. */
|
||||
if (Char.sword == SWORD_2_DRAWN)
|
||||
control_with_sword();
|
||||
else if (Char.charid >= CHARID_2_GUARD)
|
||||
control_guard_inactive();
|
||||
else if (f == FRAME_15_STAND || (f >= FRAME_50_TURN && f < 53))
|
||||
control_standing();
|
||||
else if (f == FRAME_48_TURN)
|
||||
control_turning();
|
||||
|
||||
@@ -39,6 +39,14 @@ int8_t pop_ctrl_shift_held(void);
|
||||
#define POP_CONTROL_RELEASED 0
|
||||
#define POP_CONTROL_IGNORE 1
|
||||
#define POP_CONTROL_HELD (-1)
|
||||
/* Оси ЭТОГО кадра (абсолютные): X = -1 влево / +1 вправо, Y = -1 вверх /
|
||||
* +1 вниз. Для ИИ «вперёд/назад» уже в системе персонажа, поэтому оси он
|
||||
* заполняет как есть, без flip_control_x. */
|
||||
extern int8_t control_x, control_y, control_shift;
|
||||
#define POP_CONTROL_HELD_FORWARD (-1)
|
||||
#define POP_CONTROL_HELD_BACKWARD 1
|
||||
#define POP_CONTROL_HELD_UP (-1)
|
||||
#define POP_CONTROL_HELD_DOWN 1
|
||||
extern int8_t control_forward, control_backward, control_up, control_down,
|
||||
control_shift2;
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
#include <gfx.h>
|
||||
#include <sprite.h>
|
||||
#include "pop_guard.h"
|
||||
#include "pop_kid.h" /* pop_sword_draw — клинок общий с Кидом */
|
||||
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char */
|
||||
#include "pop_map.h" /* hitp_curr/hitp_max — HP Кида */
|
||||
#include "kid_data.h"
|
||||
|
||||
/* ---- Атласы стража (chtab_5, база 750): g[id>>3], idx id&7 ---------- */
|
||||
@@ -29,6 +31,13 @@ static int gx_l[2], gy_l[2];
|
||||
static uint16_t gw_l[2], gh_l[2];
|
||||
static uint8_t gvalid[2];
|
||||
|
||||
/* Клинок — ОТДЕЛЬНЫМ прямоугольником, не объединением со стражем: объединение
|
||||
* почти вдвое больше суммы двух (клинок уходит вперёд-вверх), а heal стоит
|
||||
* ровно по площади. Объединение считаем только для окна fore-клипа. */
|
||||
static int sx_l[2], sy_l[2];
|
||||
static uint16_t sw_l[2], sh_l[2];
|
||||
static uint8_t svalid[2];
|
||||
|
||||
int pop_guard_load(void) __banked
|
||||
{
|
||||
static const char *const names[GUARD_PAGES] = {
|
||||
@@ -54,15 +63,20 @@ void pop_guard_heal(void) __banked
|
||||
{
|
||||
uint8_t p = gfx_get_draw_page() & 1;
|
||||
if (gvalid[p]) gfx_heal(gx_l[p], gy_l[p] + POP_YOFF, gw_l[p], gh_l[p]);
|
||||
gvalid[p] = 0;
|
||||
if (svalid[p]) gfx_heal(sx_l[p], sy_l[p] + POP_YOFF, sw_l[p], sh_l[p]);
|
||||
gvalid[p] = svalid[p] = 0;
|
||||
}
|
||||
|
||||
void pop_guard_draw(void) __banked
|
||||
{
|
||||
int obj_x, obj_y, top, fwd, fp_x, bx0 = 0, top0 = 0;
|
||||
int obj_x, obj_y, top, fwd, fp_x;
|
||||
uint8_t page, idx, p, flip;
|
||||
const uint8_t *img;
|
||||
uint16_t w, h;
|
||||
/* Окно fore-клипа — по ОБЪЕДИНЁННОМУ габариту «страж + клинок»; сам
|
||||
* футпринт (pop_fore_over_char) считается по спрайту стража, поэтому
|
||||
* w/h ниже НЕ трогаем. */
|
||||
int clip_x = 0, clip_y = 0; uint16_t clip_w = 0, clip_h = 0;
|
||||
|
||||
if (!g_ok || Guard.charid == 0 || pop_gframe.image == 255) return;
|
||||
/* load_frame_to_obj (seg008:1728) — та же математика, что у Kid. */
|
||||
@@ -85,17 +99,44 @@ void pop_guard_draw(void) __banked
|
||||
flip = (uint8_t)(Guard.direction >= 0); /* спрайты нарисованы влево */
|
||||
if (flip) bx -= (int)w;
|
||||
top = obj_y - (int)h + 1;
|
||||
bx0 = bx; top0 = top;
|
||||
p = gfx_get_draw_page() & 1;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
gfx_blit_cols_part(bx, top + POP_YOFF, img, flip, 0, 0);
|
||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, h))
|
||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip, 0, 0);
|
||||
else
|
||||
gfx_blit_cols_part(bx, top + POP_YOFF, img, flip, 0, 0);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
gx_l[p] = bx; gy_l[p] = top; gw_l[p] = w; gh_l[p] = h; gvalid[p] = 1;
|
||||
clip_x = bx; clip_y = top; clip_w = w; clip_h = h;
|
||||
}
|
||||
/* Клинок стража — тот же chtab_0, что у Кида (add_sword_to_objtable,
|
||||
* seg006:1798): у ЖИВОГО стража меч в руке всегда. Свой прямоугольник
|
||||
* heal; окно fore-клипа — объединение (без него клинок полез бы поверх
|
||||
* переднего столба). */
|
||||
{
|
||||
int sx, sy; uint16_t sw, sh;
|
||||
if (pop_sword_draw(&Guard, &pop_gframe, obj_x, obj_y,
|
||||
&sx, &sy, &sw, &sh)) {
|
||||
p = gfx_get_draw_page() & 1;
|
||||
sx_l[p] = sx; sy_l[p] = sy; sw_l[p] = sw; sh_l[p] = sh;
|
||||
svalid[p] = 1;
|
||||
if (!clip_w) {
|
||||
clip_x = sx; clip_y = sy; clip_w = sw; clip_h = sh;
|
||||
} else {
|
||||
int x1 = clip_x + (int)clip_w, y1 = clip_y + (int)clip_h;
|
||||
if (sx < clip_x) clip_x = sx;
|
||||
if (sy < clip_y) clip_y = sy;
|
||||
if (sx + (int)sw > x1) x1 = sx + (int)sw;
|
||||
if (sy + (int)sh > y1) y1 = sy + (int)sh;
|
||||
clip_w = (uint16_t)(x1 - clip_x);
|
||||
clip_h = (uint16_t)(y1 - clip_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
/* Окно fore-слоя = прямоугольник стража (см. pop_bg.c, fclip_on):
|
||||
* без него проход возвращал ЦЕЛЫЕ тайлы — 432 К тактов на кадр. */
|
||||
pop_fore_set_clip(bx0, top0 + POP_YOFF, (int)w, (int)h);
|
||||
/* Окно fore-слоя = прямоугольник стража с клинком (см. pop_bg.c,
|
||||
* fclip_on): без него проход возвращал ЦЕЛЫЕ тайлы — 432 К тактов. */
|
||||
pop_fore_set_clip(clip_x, clip_y + POP_YOFF, (int)clip_w, (int)clip_h);
|
||||
/* Передние грани тайлов ПОВЕРХ стража: в оригинале персонаж — запись
|
||||
* midtable, а foretable рисуется после всех midtable-записей, поэтому
|
||||
* колонна/кромка пола перекрывает ЛЮБОГО персонажа, не только Kid
|
||||
@@ -104,3 +145,79 @@ void pop_guard_draw(void) __banked
|
||||
if (w && h)
|
||||
pop_fore_over_char(fp_x, obj_y, w, h, Guard.direction);
|
||||
}
|
||||
|
||||
/* ---- Индикаторы HP (порт draw_kid_hp / draw_guard_hp, seg000:1416) --- *
|
||||
* Оригинал рисует их в статус-полосе на y = 194 (ниже поля тайлов, внутри
|
||||
* тех же 200 строк экрана PoP): HP Кида слева направо шагом 7, HP стража
|
||||
* справа налево от x = 314. Пустые деления у Кида — свой спрайт (chtab_2
|
||||
* id 217); у стража оригинал печатает тот же спрайт ЧЁРНЫМ силуэтом, а мы
|
||||
* просто не печатаем — полоса и так на чёрном фоне.
|
||||
*
|
||||
* Спрайты Кида (216/217) лежат в его же атласах, поэтому блитим через
|
||||
* pop_kid_img_blit; спрайт стража — image 0 его chtab (наша страница 0). */
|
||||
#define HP_Y 194
|
||||
#define HP_STEP 7
|
||||
#define HP_KID_FULL 216
|
||||
#define HP_KID_EMPTY 217
|
||||
#define HP_MAXDRAW 10 /* больше в полосу всё равно не влезает */
|
||||
|
||||
/* Полоса перерисовывается ТОЛЬКО когда числа изменились — и тогда на ОБЕИХ
|
||||
* страницах дабл-буфера (иначе на второй останется старое значение и его
|
||||
* будет видно как мерцание через кадр). Отсюда счётчик страниц, а не флаг.
|
||||
*
|
||||
* Снимки прошлых значений пишем ДО сравнения: `if (n != s) s = n;` SDCC z80
|
||||
* собирает неверно — SUB затирает A, и в s уезжает разность (memory
|
||||
* sdcc_z80_cmp_store_a_bug). */
|
||||
static uint8_t hp_todo; /* сколько страниц ещё обновить */
|
||||
static uint8_t hp_kid_prev, hp_kidmax_prev, hp_gd_prev, hp_gdmax_prev;
|
||||
|
||||
/* Заставить перерисовать полосу на обеих страницах: звать при входе в
|
||||
* комнату (фон перерисован целиком и стёр её) и при старте. */
|
||||
void pop_hp_invalidate(void) __banked { hp_todo = 2; }
|
||||
|
||||
void pop_hp_draw(void) __banked
|
||||
{
|
||||
uint8_t i, n;
|
||||
uint8_t gd = (uint8_t)(Guard.charid ? guardhp_curr : 0);
|
||||
uint8_t gm = (uint8_t)(Guard.charid ? guardhp_max : 0);
|
||||
|
||||
{ /* Изменилось хоть одно число — обновить обе страницы. */
|
||||
uint8_t k = hitp_curr, km = hitp_max;
|
||||
uint8_t changed = (uint8_t)(k != hp_kid_prev || km != hp_kidmax_prev ||
|
||||
gd != hp_gd_prev || gm != hp_gdmax_prev);
|
||||
hp_kid_prev = k; hp_kidmax_prev = km;
|
||||
hp_gd_prev = gd; hp_gdmax_prev = gm;
|
||||
if (changed) hp_todo = 2;
|
||||
}
|
||||
if (hp_todo == 0) return;
|
||||
hp_todo--;
|
||||
|
||||
/* Стереть прошлую полосу: фон под ней — чёрный борт. */
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
setfillstyle(SOLID_FILL, BLACK);
|
||||
bar(0, HP_Y + POP_YOFF, 319, HP_Y + POP_YOFF + 6);
|
||||
|
||||
/* Кид: сначала пустые деления, поверх — полные (порядок оригинала). */
|
||||
n = (uint8_t)(hitp_max > HP_MAXDRAW ? HP_MAXDRAW : hitp_max);
|
||||
for (i = hitp_curr; i < n; i++)
|
||||
pop_kid_img_blit(HP_KID_EMPTY, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
||||
for (i = 0; i < hitp_curr && i < HP_MAXDRAW; i++)
|
||||
pop_kid_img_blit(HP_KID_FULL, (int)i * HP_STEP, HP_Y + POP_YOFF);
|
||||
|
||||
/* Страж: только полные деления, справа налево. Скелет полосы не имеет. */
|
||||
if (g_ok && Guard.charid != 0 && Guard.charid != CHARID_4_SKELETON && gd) {
|
||||
const uint8_t *img = (const uint8_t *)atlas_image(&gp[0], 0);
|
||||
uint16_t w, h;
|
||||
gfx_w0_map(gp[0].page);
|
||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||
if (w && h) {
|
||||
/* Атлас стража тоже column-major — блит колоночный. */
|
||||
n = (uint8_t)(gd > HP_MAXDRAW ? HP_MAXDRAW : gd);
|
||||
for (i = 0; i < n; i++)
|
||||
gfx_blit_cols_part_noclip(314 - (int)i * HP_STEP,
|
||||
HP_Y + POP_YOFF, img, 0, 0, 0);
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,222 @@ int8_t pop_y_to_row(int16_t y)
|
||||
return (int8_t)((y + 60) / TILE_SIZEY % 4 - 1);
|
||||
}
|
||||
|
||||
uint16_t pop_prandom(unsigned long *seed, uint16_t maxv)
|
||||
/* Уложить значение в диапазон 0..maxv. Вызовы оригинала — prandom(1),
|
||||
* prandom(255), prandom(0xFF): делитель степень двойки, то есть маска, а не
|
||||
* деление. __moduint на Z80 стоит заметно дороже проверки n & (n-1). */
|
||||
static uint16_t pop_rnd_fit(uint16_t v, uint16_t maxv)
|
||||
{
|
||||
unsigned long s = *seed * 214013UL + 2531011UL;
|
||||
*seed = s;
|
||||
return (uint16_t)((uint16_t)(s >> 16) % (uint16_t)(maxv + 1));
|
||||
uint16_t n = (uint16_t)(maxv + 1);
|
||||
if ((uint16_t)(n & (uint16_t)(n - 1)) == 0)
|
||||
return (uint16_t)(v & (uint16_t)(n - 1));
|
||||
return (uint16_t)(v % n);
|
||||
}
|
||||
|
||||
#if POP_PRANDOM_EXACT
|
||||
/* ---- LCG оригинала: s = s*214013 + 2531011 (seg009:321) ------------ *
|
||||
*
|
||||
* ЕДИНСТВЕННОЕ место в порте на ассемблере, и вот почему. На Z80 нет
|
||||
* умножения, а SDCC для 32-битного `s * 214013` зовёт __mullong — 8 430
|
||||
* тактов на вызов (замер в MAME). Написанное на C через 16-битные
|
||||
* половины это ~3 500. Здесь ~1 000, при полностью той же
|
||||
* последовательности, что в SDLPoP. Альтернатива «взять генератор
|
||||
* попроще» (8-битный RND Apple II, xorshift) экономит меньше и ломает
|
||||
* совместимость с эталоном, по которому сверяем картинку.
|
||||
*
|
||||
* Приём: 214013 = ((((1<<1)+1)<<2 + 1)<<4 + 1)<<10 - 3 — схема Горнера по
|
||||
* РАЗРЕЖЕННОЙ записи константы. Вместо 12 сложений (по числу единиц в
|
||||
* 0x343FD) — 17 удвоений, ТРИ сложения и одно вычитание. Величина 3*s,
|
||||
* нужная в конце, попадается по дороге на втором шаге — её и сохраняем.
|
||||
*
|
||||
* ABI __sdcccall(1): arg1 (указатель на сид) -> HL, возврат -> DE. IX не
|
||||
* используется, сохранять нечего. Клоббер AF/BC/DE/HL. Аккумулятор —
|
||||
* DE:HL (DE старшее слово); s.lo живёт в BC, s.hi и 3*s — в статиках
|
||||
* (регистров на всё не хватает).
|
||||
*/
|
||||
uint16_t pop_pr_ptr; /* адрес сида */
|
||||
uint16_t pop_pr_sh; /* старшее слово исходного сида */
|
||||
uint16_t pop_pr_3s_lo, pop_pr_3s_hi; /* 3*s, нужное в конце */
|
||||
|
||||
static uint16_t pop_lcg_step(pop_rnd_t *seed) __naked
|
||||
{
|
||||
(void)seed;
|
||||
__asm
|
||||
ld (_pop_pr_ptr), hl
|
||||
ld c, (hl)
|
||||
inc hl
|
||||
ld b, (hl) ; BC = s.lo
|
||||
inc hl
|
||||
ld a, (hl)
|
||||
ld (_pop_pr_sh), a
|
||||
inc hl
|
||||
ld a, (hl)
|
||||
ld (_pop_pr_sh + 1), a ; (pop_pr_sh) = s.hi
|
||||
|
||||
ld h, b
|
||||
ld l, c
|
||||
ld de, (_pop_pr_sh) ; акк = s
|
||||
|
||||
;; акк = акк*2 + s => 3s
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, bc
|
||||
ex de, hl
|
||||
ld a, (_pop_pr_sh) ; LD A,(nn) флаги НЕ трогает
|
||||
adc a, l
|
||||
ld l, a
|
||||
ld a, (_pop_pr_sh + 1)
|
||||
adc a, h
|
||||
ld h, a
|
||||
ex de, hl
|
||||
ld (_pop_pr_3s_lo), hl
|
||||
ld (_pop_pr_3s_hi), de
|
||||
|
||||
;; акк = акк*4 + s => 13s
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, bc
|
||||
ex de, hl
|
||||
ld a, (_pop_pr_sh)
|
||||
adc a, l
|
||||
ld l, a
|
||||
ld a, (_pop_pr_sh + 1)
|
||||
adc a, h
|
||||
ld h, a
|
||||
ex de, hl
|
||||
|
||||
;; акк = акк*16 + s => 209s
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, bc
|
||||
ex de, hl
|
||||
ld a, (_pop_pr_sh)
|
||||
adc a, l
|
||||
ld l, a
|
||||
ld a, (_pop_pr_sh + 1)
|
||||
adc a, h
|
||||
ld h, a
|
||||
ex de, hl
|
||||
|
||||
;; акк *= 1024 => 214016s (10 удвоений)
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
|
||||
;; акк -= 3s => 214013s
|
||||
ld bc, (_pop_pr_3s_lo)
|
||||
or a ; CY = 0
|
||||
sbc hl, bc
|
||||
ld bc, (_pop_pr_3s_hi) ; LD BC,(nn) флаги НЕ трогает
|
||||
ex de, hl
|
||||
sbc hl, bc
|
||||
ex de, hl
|
||||
|
||||
;; акк += 2531011 = 0x0026_9EC3
|
||||
ld bc, #0x9EC3
|
||||
add hl, bc
|
||||
ld bc, #0x0026
|
||||
ex de, hl
|
||||
adc hl, bc
|
||||
ex de, hl
|
||||
|
||||
;; сохранить сид, вернуть СТАРШЕЕ слово в DE
|
||||
push de
|
||||
ex de, hl ; DE = младшее слово
|
||||
ld hl, (_pop_pr_ptr)
|
||||
ld (hl), e
|
||||
inc hl
|
||||
ld (hl), d
|
||||
inc hl
|
||||
pop de ; DE = старшее слово
|
||||
ld (hl), e
|
||||
inc hl
|
||||
ld (hl), d
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv)
|
||||
{
|
||||
return pop_rnd_fit(pop_lcg_step(seed), maxv);
|
||||
}
|
||||
|
||||
#else /* !POP_PRANDOM_EXACT — генератор попроще, НЕ совместимый с эталоном */
|
||||
|
||||
/* xorshift16 + шаг Вейля. Зачем не LCG оригинала: тот 32-битный, и даже
|
||||
* без __mullong (см. ветку выше) стоит ~7 000 тактов на вызов — при двух
|
||||
* вызовах за кадр это 3 % бюджета на генератор случайных чисел. Здесь
|
||||
* умножений нет вовсе, только сдвиги и xor.
|
||||
*
|
||||
* Почему НЕ 8-битный RND Apple II (RNDseed := 5*RNDseed + 23 mod 256,
|
||||
* Prince-of-Persia-Apple-II): он ещё быстрее, но у LCG по модулю 256
|
||||
* младшие биты вырождены — бит 0 просто чередуется. Наши вызовы это
|
||||
* видят: раскладка кладки берёт prandom(1) (один бит) и prandom(4), и
|
||||
* вместо шума получилась бы правильная шахматка. У xorshift такой
|
||||
* структуры нет, а стоит он примерно столько же.
|
||||
*
|
||||
* Шаг Вейля (+0x9E37, нечётный) нужен ещё и потому, что у чистого
|
||||
* xorshift ноль — неподвижная точка, а сид кладки (номер комнаты + ряд +
|
||||
* колонка) вполне может оказаться нулём.
|
||||
*
|
||||
* ПОСЛЕДСТВИЕ: последовательность отличается от SDLPoP — другая (но
|
||||
* статистически такая же) раскладка кладки и другие броски в боёвке.
|
||||
* Поэтому по умолчанию собирается НЕ эта ветка: после переписывания LCG
|
||||
* на ассемблер выигрыш от смены генератора почти исчез, а сверять
|
||||
* картинку с эталоном стало важнее. Ветка оставлена как запасной ход. */
|
||||
uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv)
|
||||
{
|
||||
uint16_t s = seed->lo;
|
||||
s ^= (uint16_t)(s << 7);
|
||||
s ^= (uint16_t)(s >> 9);
|
||||
s ^= (uint16_t)(s << 8);
|
||||
s = (uint16_t)(s + 0x9E37u);
|
||||
seed->lo = s;
|
||||
seed->hi = 0; /* старшая половина не используется */
|
||||
/* Отдаём СТАРШИЙ байт: у него нет короткопериодической структуры
|
||||
* младших бит, а вызывающие берут от результата именно младшие. */
|
||||
return pop_rnd_fit((uint16_t)(s >> 8), maxv);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -26,9 +26,36 @@ extern const int16_t pop_y_land[5];
|
||||
* над комнатой, 3 = под комнатой). */
|
||||
int8_t pop_y_to_row(int16_t y);
|
||||
|
||||
/* prandom (seg009:321) — 32-битный LCG оригинала. Сид ВНЕШНИЙ: у стен
|
||||
* (раскладка кладки) и у анимаций тайлов (фаза факелов) свои
|
||||
* последовательности, смешивать их нельзя. */
|
||||
uint16_t pop_prandom(unsigned long *seed, uint16_t maxv);
|
||||
/* prandom (порт seg009:321). Сид ВНЕШНИЙ: у стен (раскладка кладки) и у
|
||||
* анимаций тайлов (фаза факелов) свои последовательности, смешивать их
|
||||
* нельзя.
|
||||
*
|
||||
* ДВА генератора, выбор через POP_PRANDOM_EXACT (см. pop_geom.c):
|
||||
* 1 (по умолчанию) — LCG оригинала (s*214013+2531011) БИТ-В-БИТ: та же
|
||||
* раскладка кладки и те же броски, что в SDLPoP, поэтому по картинке
|
||||
* можно сверяться с эталоном;
|
||||
* 0 — xorshift16 + шаг Вейля, без единого умножения. Дешевле ровно на
|
||||
* 5 646 тактов за кадр, 1.3 % бюджета (замер A/B в MAME, комната 3,
|
||||
* 175 кадров: 403 632 против 397 986). Последовательность другая —
|
||||
* статистически эквивалентная, но не та же.
|
||||
*
|
||||
* Обе версии считают 16-битными половинами, а не unsigned long: на 32-бит
|
||||
* умножении SDCC зовёт __mullong, и это 8 430 тактов на вызов (замер в
|
||||
* MAME, breakpoint на __mullong).
|
||||
*
|
||||
* ЗАДЕЛ: разрыв между версиями определяется не алгоритмом, а кодогенерацией
|
||||
* SDCC — те же генераторы на Z80-ассемблере укладываются в 86..148 тактов.
|
||||
* Если процент понадобится, сначала переписать в asm ОРИГИНАЛЬНЫЙ LCG (там
|
||||
* умножение на константу), а менять генератор — только если и этого мало. */
|
||||
typedef struct { uint16_t lo, hi; } pop_rnd_t;
|
||||
|
||||
#ifndef POP_PRANDOM_EXACT
|
||||
#define POP_PRANDOM_EXACT 1
|
||||
#endif
|
||||
|
||||
uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv);
|
||||
|
||||
/* Сид из 16-битного числа (старшая половина = 0). */
|
||||
#define pop_prandom_set(s, v) do { (s).lo = (uint16_t)(v); (s).hi = 0; } while (0)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -10,11 +10,22 @@
|
||||
#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. */
|
||||
@@ -32,6 +43,10 @@ void pop_guard_reset(void)
|
||||
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):
|
||||
@@ -44,11 +59,35 @@ 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;
|
||||
guardhp_curr = 0;
|
||||
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). */
|
||||
@@ -61,6 +100,20 @@ void pop_guard_load_frame(void)
|
||||
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));
|
||||
}
|
||||
|
||||
void pop_guard_enter(uint8_t room)
|
||||
{
|
||||
uint8_t tile, color, skill;
|
||||
@@ -80,13 +133,32 @@ void pop_guard_enter(uint8_t room)
|
||||
Guard.alive = -1;
|
||||
Guard.action = 1; /* actions_1_run_jump */
|
||||
Guard.fall_x = Guard.fall_y = 0;
|
||||
guardhp_max = guardhp_curr = 3; /* get_guard_hp: skill 0, уровень 1 */
|
||||
/* 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(); /* до первого кадра */
|
||||
/* 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,45 @@
|
||||
#include <stdint.h>
|
||||
#include "kid_data.h" /* kframe */
|
||||
#include "pop_char.h"
|
||||
#include "pop_geom.h" /* pop_rnd_t — сид бросков боёвки */
|
||||
|
||||
/* seqids (types.h:1070) — последовательности стража. */
|
||||
/* seqids (types.h:1052+) — последовательности боя и стойки. */
|
||||
#define SEQ_55_DRAW_SWORD 55
|
||||
#define SEQ_56_GUARD_FORWARD 56 /* шаг вперёд с мечом (страж) */
|
||||
#define SEQ_57_BACK_WITH_SWORD 57
|
||||
#define SEQ_58_GUARD_STRIKE 58
|
||||
#define SEQ_60_TURN_WITH_SWORD 60
|
||||
#define SEQ_61_PARRY_AFTER_STRIKE 61
|
||||
#define SEQ_62_PARRY 62
|
||||
#define SEQ_66_STRIKE_AFTER_PARRY 66
|
||||
#define SEQ_71_DYING 71
|
||||
#define SEQ_75_STRIKE 75 /* удар (Кид) */
|
||||
#define SEQ_77_GUARD_STAND_INACTIVE 77
|
||||
#define SEQ_80_STAND_FLIPPED 80
|
||||
#define SEQ_86_FORWARD_WITH_SWORD 86 /* шаг вперёд с мечом (Кид) */
|
||||
#define SEQ_87_GUARD_BECOME_INACT 87
|
||||
#define SEQ_90_EN_GARDE 90
|
||||
#define SEQ_92_PUT_SWORD_AWAY 92
|
||||
#define SEQ_93_PUT_SWORD_AWAY_FAST 93
|
||||
|
||||
/* frameids (types.h:911+) и состояния меча (types.h:332). */
|
||||
#define FRAME_150_PARRY 150
|
||||
#define FRAME_151_STRIKE_1 151
|
||||
#define FRAME_152_STRIKE_2 152
|
||||
#define FRAME_153_STRIKE_3 153
|
||||
#define FRAME_157_WALK_WITH_SWORD 157
|
||||
#define FRAME_158_STAND_WITH_SWORD 158
|
||||
#define FRAME_161_PARRY 161
|
||||
#define FRAME_162_BLOCK_TO_STRIKE 162
|
||||
#define FRAME_165_WALK_WITH_SWORD 165
|
||||
#define FRAME_166_STAND_INACTIVE 166
|
||||
#define FRAME_167_BLOCKED 167
|
||||
#define FRAME_168_BACK 168
|
||||
#define FRAME_169_BEGIN_BLOCK 169
|
||||
#define FRAME_170_STAND_WITH_SWORD 170
|
||||
#define FRAME_171_STAND_WITH_SWORD 171
|
||||
#define SWORD_0_SHEATHED 0
|
||||
#define SWORD_2_DRAWN 2
|
||||
|
||||
/* charids (types.h:320) — нужны хотя бы для «скелет не убивается читом». */
|
||||
#define CHARID_0_KID 0
|
||||
@@ -31,12 +67,53 @@
|
||||
#define CHARID_4_SKELETON 4
|
||||
#define CHARID_5_PRINCESS 5
|
||||
|
||||
/* Градаций мастерства стража (seg002: таблицы вероятностей по 12 записей). */
|
||||
#define NUM_GUARD_SKILLS 12
|
||||
|
||||
/* Тип персонажа — в pop_char.h (общий с Кидом и Char). */
|
||||
|
||||
extern pop_char_t Guard; /* страж ТЕКУЩЕЙ комнаты (как в оригинале) */
|
||||
extern uint8_t guardhp_curr, guardhp_max;
|
||||
extern int8_t guardhp_delta; /* <0 = в этом кадре отняли HP */
|
||||
|
||||
/* Видит ли страж Кида (порт can_guard_see_kid, seg003:688):
|
||||
* 0 — не видит, 1 — видит, но не пойдёт (между ними loose/чомпер/дыра),
|
||||
* 2 — видит и пойдёт. Считает pop_check_can_guard_see_kid (банк) раз в
|
||||
* кадр ДО логики персонажей; читают ИИ (autocontrol_*) и боёвка. */
|
||||
extern int8_t can_guard_see_kid;
|
||||
|
||||
/* Кид «нашумел» (порт is_guard_notice, seg000:364): заставляет неактивного
|
||||
* стража обернуться, даже если Кид у него за спиной. Ставит источник шума,
|
||||
* снимает сам ИИ. */
|
||||
extern int8_t is_guard_notice;
|
||||
|
||||
/* Боевые таймеры/уровень мастерства (seg000:295..311). Все убывают раз в
|
||||
* кадр в autocontrol_opponent — как в оригинале.
|
||||
* guard_skill — 0..11, из данных уровня; индекс в таблицы вероятностей;
|
||||
* guard_refrac — «отдышка» стража: пока >0, он не атакует и не наступает;
|
||||
* justblocked — Кид только что парировал (влияет на вероятность блока);
|
||||
* kid_sword_strike — Кид только что бил (стража это придерживает). */
|
||||
extern uint8_t guard_skill;
|
||||
extern int8_t guard_refrac, justblocked, kid_sword_strike;
|
||||
|
||||
/* Состояние меча КИДА (seg005/seg006):
|
||||
* offguard — меч убран НАМЕРЕННО (вниз в бою): пока стоит, Кид не
|
||||
* достаёт клинок автоматически, даже видя стража;
|
||||
* holding_sword — клинок в руке (для индикатора HP стража). */
|
||||
extern int8_t offguard, holding_sword;
|
||||
|
||||
/* Сид бросков боёвки (свой домен последовательности, см. pop_geom.h). */
|
||||
extern pop_rnd_t pop_fight_seed;
|
||||
|
||||
/* Луч видимости стража — раз в кадр, ДО тиков персонажей (в оригинале
|
||||
* check_can_guard_see_kid стоит в play_frame перед play_kid_frame). */
|
||||
void pop_check_can_guard_see_kid(void) __banked;
|
||||
|
||||
/* Боёвка: пометить попадания (обе стороны) и применить их. Порядок в
|
||||
* кадре как в play_frame: hurting -> hurt -> do_delta_hp. */
|
||||
void pop_check_sword_hurting(void) __banked;
|
||||
void pop_check_sword_hurt(void) __banked;
|
||||
|
||||
/* Текущий кадр стража (порт cur_frame для Char): заполняет логика в W1/W2,
|
||||
* читает отрисовка в резиденте W3. image == 255 — рисовать нечего. */
|
||||
extern kframe pop_gframe;
|
||||
@@ -54,9 +131,19 @@ void pop_guard_free(void) __banked;
|
||||
* seg002:0112) либо убрать его, если в комнате стража нет. */
|
||||
void pop_guard_enter(uint8_t room);
|
||||
|
||||
/* Запомнить стража УХОДЯЩЕЙ комнаты (порт leave_guard). Звать в начале
|
||||
* enter_room, пока Guard ещё держит прежнего. */
|
||||
void pop_guard_leave(void);
|
||||
|
||||
/* Нарисовать текущий кадр стража на back-странице (банк GFX_BANK_SPRITE,
|
||||
* как kid_draw) и стереть прошлый (heal). Реализация — pop_gdraw.c
|
||||
* (резидент W3), поэтому звать ТОЛЬКО из главного цикла, не из банка. */
|
||||
/* Полоса HP (Кид слева, страж справа) в статус-полосе. Рисует главный цикл
|
||||
* ПОСЛЕ персонажей — она ничего не перекрывает. */
|
||||
void pop_hp_draw(void) __banked;
|
||||
/* Полоса стёрта перерисовкой фона (вход в комнату) — обновить обе страницы. */
|
||||
void pop_hp_invalidate(void) __banked;
|
||||
|
||||
void pop_guard_heal(void) __banked;
|
||||
void pop_guard_draw(void) __banked;
|
||||
|
||||
@@ -67,6 +154,10 @@ void pop_guard_reset(void);
|
||||
* при включённых читах — проверку делает вызывающий. */
|
||||
void pop_guard_kill(void);
|
||||
|
||||
/* do_delta_hp (seg003): применить дельту HP, накопленную за кадр. Звать
|
||||
* раз в кадр ПОСЛЕ тиков персонажей (в оригинале — в play_frame). */
|
||||
void pop_do_delta_hp(void);
|
||||
|
||||
/* Тик логики стража — В БАНКЕ (guards.c). Состояние берёт отсюда. */
|
||||
void pop_guard_tick(void) __banked;
|
||||
|
||||
|
||||
@@ -28,15 +28,15 @@ int8_t knock;
|
||||
static atlas_t kidp[KID_MAXPAGES];
|
||||
static uint8_t kid_npages;
|
||||
|
||||
/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): отдельный
|
||||
* спрайт клинка поверх Kid в кадрах «нашёл меч»/убирание в ножны (229..237).
|
||||
* Из sword_tbl взяты только строки 35..42 — те, что адресуют эти кадры (бой
|
||||
* не портирован); индекс в атласе = sword_frame − 35. Смещения таблицы —
|
||||
* в ЭКРАННЫХ пикселях: оригинал применяет их ПОСЛЕ calc_screen_x_coord. */
|
||||
#define SWORD_SF_LO 35
|
||||
#define SWORD_SF_HI 42
|
||||
static const int8_t SWORD_DX[8] = { 7, 14, 14, 14, 28, 28, 21, 14 };
|
||||
static const int8_t SWORD_DY[8] = {-32,-32,-31,-29,-28,-28,-25,-22 };
|
||||
/* chtab_0 «меч в руке» (add_sword_to_objtable, seg006:1798): клинок —
|
||||
* ОТДЕЛЬНЫЙ спрайт поверх персонажа, один и тот же атлас у Кида и у стража.
|
||||
* Таблица sword_tbl (kid_data.h, генерится из seg006) индексируется
|
||||
* sword_frame = кадр.sword & 0x3F; id 255 = клинка в этом кадре нет.
|
||||
* Смещения — в ЭКРАННЫХ пикселях: оригинал применяет их ПОСЛЕ
|
||||
* calc_screen_x_coord, x — «вперёд» по направлению персонажа. */
|
||||
static const uint8_t SWORD_ID[KID_NSWORD] = KID_SWORD_ID_INIT;
|
||||
static const int8_t SWORD_DX[KID_NSWORD] = KID_SWORD_DX_INIT;
|
||||
static const int8_t SWORD_DY[KID_NSWORD] = KID_SWORD_DY_INIT;
|
||||
static atlas_t swordp;
|
||||
static uint8_t sword_ok;
|
||||
|
||||
@@ -47,6 +47,72 @@ int pop_sword_load(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* add_sword_to_objtable (seg006:1798). Клинок рисуется, если персонаж в
|
||||
* кадрах подъёма/ножен, ИЛИ держит меч, ИЛИ это ЖИВОЙ страж (у стража меч
|
||||
* в руке всегда, даже пока sword ещё sheathed). Страницу W0 оставляем
|
||||
* замапленной — вызывающий делает gfx_w0_unmap сам. */
|
||||
uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr,
|
||||
int obj_x, int obj_y,
|
||||
int *rx, int *ry, uint16_t *rw, uint16_t *rh)
|
||||
{
|
||||
uint8_t sf, id, flip;
|
||||
const uint8_t *simg;
|
||||
uint16_t w, h;
|
||||
int bx, top;
|
||||
|
||||
if (!sword_ok) return 0;
|
||||
if (!((ch->frame >= 229 && ch->frame < 238) ||
|
||||
ch->sword != 0 ||
|
||||
(ch->charid == CHARID_2_GUARD && ch->alive < 0)))
|
||||
return 0;
|
||||
sf = (uint8_t)(fr->sword & 0x3F);
|
||||
if (sf == 0 || sf >= KID_NSWORD) return 0;
|
||||
id = SWORD_ID[sf];
|
||||
if (id == 0xFF) return 0;
|
||||
|
||||
simg = (const uint8_t *)atlas_image(&swordp, id);
|
||||
gfx_w0_map(swordp.page);
|
||||
w = (uint16_t)(simg[0] | ((uint16_t)simg[1] << 8));
|
||||
h = (uint16_t)(simg[2] | ((uint16_t)simg[3] << 8));
|
||||
if (!w || !h) return 0;
|
||||
|
||||
flip = (uint8_t)(ch->direction >= 0);
|
||||
bx = obj_x + ((ch->direction < 0) ? -(int)SWORD_DX[sf] : (int)SWORD_DX[sf]);
|
||||
top = obj_y + (int)SWORD_DY[sf] - (int)h + 1;
|
||||
if (flip) bx -= (int)w;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
/* Клинок почти всегда целиком на экране — noclip-путь (см. gfx.h:
|
||||
* подготовка клипающего варианта стоит ~5.6 К тактов на вызов). */
|
||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, h))
|
||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, simg, flip, 0, 0);
|
||||
else
|
||||
gfx_blit_cols_part(bx, top + POP_YOFF, simg, flip, 0, 0);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
*rx = bx; *ry = top; *rw = w; *rh = h;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Блит спрайта chtab_2 (атласы Kid) по ЕГО id, левым верхним углом в (x,y).
|
||||
* Нужен полосе HP: она рисует id 216/217 из тех же атласов, что и кадры
|
||||
* Кида, а держать вторую копию раскладки в pop_gdraw ни к чему. */
|
||||
void pop_kid_img_blit(uint8_t image, int x, int y)
|
||||
{
|
||||
uint8_t pg = (uint8_t)(image >> 3); /* раскладка атласов: 8 спрайтов/стр. */
|
||||
uint8_t idx = (uint8_t)(image & 7);
|
||||
const uint8_t *img;
|
||||
uint16_t w, h;
|
||||
if (pg >= kid_npages) return;
|
||||
img = (const uint8_t *)atlas_image(&kidp[pg], idx);
|
||||
gfx_w0_map(kidp[pg].page);
|
||||
w = (uint16_t)(img[0] | ((uint16_t)img[1] << 8));
|
||||
h = (uint16_t)(img[2] | ((uint16_t)img[3] << 8));
|
||||
/* Атласы Кида хранятся COLUMN-MAJOR (transpose_cols в pop_pack_kid —
|
||||
* ради бесплатного горизонтального флипа), поэтому блит обязан быть
|
||||
* колоночным. Row-major gfx_blit_noclip выдал бы марку транспонированной. */
|
||||
if (w && h) gfx_blit_cols_part_noclip(x, y, img, 0, 0, 0);
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
|
||||
int pop_kid_load(uint8_t npages)
|
||||
{
|
||||
static const char *const names[KID_MAXPAGES] = {
|
||||
@@ -132,13 +198,61 @@ static kframe kid_frame; /* кадр Kid для kid_draw */
|
||||
* копирование платится один раз на переключение персонажа. */
|
||||
pop_char_t Char;
|
||||
|
||||
void pop_loadkid(void) { Char = Kid; }
|
||||
void pop_savekid(void) { Kid = Char; kid_frame = cur_frame; }
|
||||
/* Выгрузка БЕЗ кадра: control() кадр не трогает, а cur_frame в этот
|
||||
* момент принадлежит тому, кто последним крутил play_seq. */
|
||||
void pop_savekid_state(void) { Kid = Char; }
|
||||
/* Opp (seg006): СОПЕРНИК активного персонажа — вторая копия, которую ставят
|
||||
* рядом с Char (loadkid_and_opp / loadshad_and_opp, seg006:809). Через неё
|
||||
* ИИ и боёвка меряют дистанцию (char_opp_dist), не зная, кто именно перед
|
||||
* ними: для стража Opp = Kid, для Кида Opp = Guard. */
|
||||
pop_char_t Opp;
|
||||
|
||||
/* Снимок последовательности на момент входа в окно Char: по нему
|
||||
* pop_savekid_state отличает «последовательность сменил ДИСПЕТЧЕР (в Char)»
|
||||
* от «её сменил pop_map (прямо в Kid)». */
|
||||
static uint16_t kid_seq_at_load;
|
||||
|
||||
void pop_loadkid(void) { Char = Kid; kid_seq_at_load = Kid.curr_seq; }
|
||||
void pop_savekid(void) { Kid = Char; }
|
||||
/* Выгрузка после control(): копируем ТОЛЬКО то, что диспетчер реально
|
||||
* меняет у активного персонажа — последовательность и состояние меча.
|
||||
*
|
||||
* Почему не `Kid = Char`, как делает savekid оригинала: у нас часть
|
||||
* действий control() исполняет pop_map (pop_down_action, pop_jump_up_seq,
|
||||
* safe_step...), и он пишет ПРЯМО в Kid — там ещё не портирован переход на
|
||||
* Char. Полное копирование затирало эти правки: Kid терял выравнивание x
|
||||
* и ряд, и спуск с уступа через вис не срабатывал — персонаж просто
|
||||
* приседал. Когда pop_map переведём на Char, здесь вернётся Kid = Char. */
|
||||
void pop_savekid_state(void)
|
||||
{
|
||||
/* Последовательность мог сменить КАЖДЫЙ из двоих: диспетчер — в Char
|
||||
* (pop_char_set_seq), pop_map — прямо в Kid (kid_set_seq: спуск с
|
||||
* уступа SEQ_68, зацеп, осторожный шаг). Решение pop_map старше:
|
||||
* диспетчер про эти переходы не знает и, копируя Char вслепую, затирал
|
||||
* их — спуск в проём не срабатывал, а персонаж даже не приседал, потому
|
||||
* что pop_down_action уже вернул «обработано». */
|
||||
uint8_t map_changed = (uint8_t)(Kid.curr_seq != kid_seq_at_load);
|
||||
if (!map_changed) Kid.curr_seq = Char.curr_seq;
|
||||
Kid.sword = Char.sword;
|
||||
}
|
||||
void pop_loadshad(void) { Char = Guard; }
|
||||
void pop_saveshad(void) { Guard = Char; pop_gframe = cur_frame; }
|
||||
void pop_saveshad(void) { Guard = Char; }
|
||||
void pop_loadshad_and_opp(void) { Char = Guard; Opp = Kid; }
|
||||
void pop_saveshad_and_opp(void) { Guard = Char; Kid = Opp; }
|
||||
void pop_loadkid_and_opp(void) { Char = Kid; Opp = Guard; }
|
||||
void pop_savekid_and_opp(void) { Kid = Char; Guard = Opp; }
|
||||
|
||||
/* char_opp_dist (seg006:2135): >0 — соперник ПЕРЕД Char, <0 — за спиной.
|
||||
* +13 при встречных направлениях: у стоящих лицом друг к другу «ноль»
|
||||
* приходится на ширину тайла между ними. Живёт здесь, а не в guards.c,
|
||||
* потому что нужен по обе стороны банковой границы — и ИИ, и диспетчеру
|
||||
* боёвки (pop_ctrl). */
|
||||
int16_t pop_char_opp_dist(void)
|
||||
{
|
||||
int16_t distance;
|
||||
if (Char.room != Opp.room) return 999;
|
||||
distance = (int16_t)Opp.x - (int16_t)Char.x;
|
||||
if (Char.direction < 0) distance = -distance;
|
||||
if (distance >= 0 && Char.direction != Opp.direction) distance += 13;
|
||||
return distance;
|
||||
}
|
||||
|
||||
int pop_kid_data_load(const char *path)
|
||||
{
|
||||
@@ -215,6 +329,15 @@ static void load_frame(void)
|
||||
cur_frame.dy = (int8_t)f[2];
|
||||
cur_frame.flags = f[3];
|
||||
cur_frame.sword = f[4];
|
||||
/* Кэш кадра для ОТРИСОВКИ раскладываем ЗДЕСЬ, по charid активного
|
||||
* персонажа, а не в save*-функциях. Раньше кэш писали pop_savekid/
|
||||
* pop_saveshad, и любое окно Char БЕЗ play_seq (окна боёвки
|
||||
* check_sword_hurting / check_sword_hurt) записывало Киду кадр, который
|
||||
* на самом деле принадлежал СТРАЖУ: kid_draw брал его image из атласа
|
||||
* Кида и рисовал произвольную позу. Теперь владелец один — тот, для
|
||||
* кого кадр реально загружен. */
|
||||
if (Char.charid == CHARID_0_KID) kid_frame = cur_frame;
|
||||
else pop_gframe = cur_frame;
|
||||
}
|
||||
|
||||
static uint8_t char_dx_forward(int8_t dx)
|
||||
@@ -327,6 +450,7 @@ void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir)
|
||||
Kid.fall_x = Kid.fall_y = 0;
|
||||
Kid.repeat = 0;
|
||||
Kid.charid = 0; /* charid_0_kid: своя таблица кадров */
|
||||
Kid.alive = -1; /* в оригинале ЖИВОЙ = alive < 0 */
|
||||
Kid.curr_seq = kid_seq_off[seq_id];
|
||||
pop_kid_play(); /* до первого кадра */
|
||||
}
|
||||
@@ -363,6 +487,64 @@ static int kid_lx[2], kid_ly[2];
|
||||
static uint16_t kid_lw[2], kid_lh[2];
|
||||
static uint8_t kid_lvalid[2];
|
||||
|
||||
/* ОТДЕЛЬНЫЙ прямоугольник накладных спрайтов (клинок chtab_0, брызги урона).
|
||||
* Раньше он объединялся с прямоугольником персонажа — но объединение сильно
|
||||
* больше суммы двух: клинок уходит вперёд-вверх, и общий габарит выходил
|
||||
* почти вдвое шире и выше самого Kid, а heal стоит ровно по площади.
|
||||
* Поэтому heal делаем двумя вызовами по своим прямоугольникам, а объединение
|
||||
* оставляем ТОЛЬКО для окна fore-клипа (там это 4 сравнения, без рисования —
|
||||
* зато клинок/брызги честно уходят под передние грани тайлов). */
|
||||
static int kid_ox[2], kid_oy[2];
|
||||
static uint16_t kid_ow[2], kid_oh[2];
|
||||
static uint8_t kid_ovalid[2];
|
||||
|
||||
/* Добавить накладной спрайт в overlay-прямоугольник страницы dp. Клинок и
|
||||
* брызги в одном кадре встречаются редко и оба маленькие — их объединение
|
||||
* дешевле третьего слота. */
|
||||
static void kid_overlay_add(uint8_t dp, int x, int y, uint16_t w, uint16_t h)
|
||||
{
|
||||
if (!kid_ovalid[dp]) {
|
||||
kid_ox[dp] = x; kid_oy[dp] = y; kid_ow[dp] = w; kid_oh[dp] = h;
|
||||
kid_ovalid[dp] = 1;
|
||||
return;
|
||||
}
|
||||
{
|
||||
int x0 = kid_ox[dp], y0 = kid_oy[dp];
|
||||
int x1 = x0 + (int)kid_ow[dp], y1 = y0 + (int)kid_oh[dp];
|
||||
if (x < x0) x0 = x;
|
||||
if (y < y0) y0 = y;
|
||||
if (x + (int)w > x1) x1 = x + (int)w;
|
||||
if (y + (int)h > y1) y1 = y + (int)h;
|
||||
kid_ox[dp] = x0; kid_oy[dp] = y0;
|
||||
kid_ow[dp] = (uint16_t)(x1 - x0); kid_oh[dp] = (uint16_t)(y1 - y0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Окно fore-клипа = объединение «персонаж + накладные». Считается по
|
||||
* прямоугольникам, ничего не рисует. */
|
||||
static void kid_fore_clip(uint8_t dp)
|
||||
{
|
||||
int x0, y0, x1, y1;
|
||||
if (!kid_lvalid[dp] && !kid_ovalid[dp]) { pop_fore_set_clip(0, 0, 0, 0); return; }
|
||||
if (!kid_ovalid[dp]) {
|
||||
pop_fore_set_clip(kid_lx[dp], kid_ly[dp] + POP_YOFF,
|
||||
(int)kid_lw[dp], (int)kid_lh[dp]);
|
||||
return;
|
||||
}
|
||||
if (!kid_lvalid[dp]) {
|
||||
pop_fore_set_clip(kid_ox[dp], kid_oy[dp] + POP_YOFF,
|
||||
(int)kid_ow[dp], (int)kid_oh[dp]);
|
||||
return;
|
||||
}
|
||||
x0 = kid_lx[dp] < kid_ox[dp] ? kid_lx[dp] : kid_ox[dp];
|
||||
y0 = kid_ly[dp] < kid_oy[dp] ? kid_ly[dp] : kid_oy[dp];
|
||||
x1 = kid_lx[dp] + (int)kid_lw[dp];
|
||||
y1 = kid_ly[dp] + (int)kid_lh[dp];
|
||||
if (kid_ox[dp] + (int)kid_ow[dp] > x1) x1 = kid_ox[dp] + (int)kid_ow[dp];
|
||||
if (kid_oy[dp] + (int)kid_oh[dp] > y1) y1 = kid_oy[dp] + (int)kid_oh[dp];
|
||||
pop_fore_set_clip(x0, y0 + POP_YOFF, x1 - x0, y1 - y0);
|
||||
}
|
||||
|
||||
/* Метрики последнего kid_draw для футпринта fore-over-Kid (pop_bg):
|
||||
* obj_x — ЛОГИЧЕСКАЯ X кадра (ДО ×8/7), obj_y — низ, w/h — размер. */
|
||||
static int kid_fp_x, kid_fp_y;
|
||||
@@ -406,26 +588,18 @@ void kid_draw_splash(void)
|
||||
int top = obj_y - (int)h + 1;
|
||||
if (flip) bx -= (int)w;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
gfx_blit_cols(bx, top + POP_YOFF, img, flip);
|
||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, h))
|
||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, img, flip, 0, 0);
|
||||
else
|
||||
gfx_blit_cols(bx, top + POP_YOFF, img, flip);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
pop_clip_sprite(bx, w, top + POP_YOFF, (int)h);
|
||||
/* Прямоугольник splash расширяет область heal ЭТОЙ страницы: иначе
|
||||
* брызги останутся на экране после кадра. */
|
||||
/* Брызги — накладной спрайт: свой прямоугольник heal (см.
|
||||
* kid_overlay_add), окно fore пересчитываем объединением. */
|
||||
{
|
||||
uint8_t dp = gfx_get_draw_page();
|
||||
if (kid_lvalid[dp]) {
|
||||
int x0 = kid_lx[dp] < bx ? kid_lx[dp] : bx;
|
||||
int y0 = kid_ly[dp] < top ? kid_ly[dp] : top;
|
||||
int x1 = kid_lx[dp] + (int)kid_lw[dp];
|
||||
int y1 = kid_ly[dp] + (int)kid_lh[dp];
|
||||
if (bx + (int)w > x1) x1 = bx + (int)w;
|
||||
if (top + (int)h > y1) y1 = top + (int)h;
|
||||
kid_lx[dp] = x0; kid_ly[dp] = y0;
|
||||
kid_lw[dp] = (uint16_t)(x1 - x0); kid_lh[dp] = (uint16_t)(y1 - y0);
|
||||
/* Окно fore-слоя расширяем тем же объединением: брызги
|
||||
* тоже должны уходить под передние грани тайлов. */
|
||||
pop_fore_set_clip(x0, y0 + POP_YOFF, x1 - x0, y1 - y0);
|
||||
}
|
||||
kid_overlay_add(dp, bx, top, w, h);
|
||||
kid_fore_clip(dp);
|
||||
}
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
@@ -437,7 +611,9 @@ void kid_heal(void)
|
||||
uint8_t p = gfx_get_draw_page();
|
||||
if (kid_lvalid[p])
|
||||
gfx_heal(kid_lx[p], kid_ly[p] + POP_YOFF, kid_lw[p], kid_lh[p]); /* +YOFF: центрирование */
|
||||
kid_lvalid[p] = 0;
|
||||
if (kid_ovalid[p])
|
||||
gfx_heal(kid_ox[p], kid_oy[p] + POP_YOFF, kid_ow[p], kid_oh[p]);
|
||||
kid_lvalid[p] = kid_ovalid[p] = 0;
|
||||
}
|
||||
|
||||
void kid_draw(void)
|
||||
@@ -496,8 +672,14 @@ void kid_draw(void)
|
||||
if (skip >= (int)h) skip = (int)h; /* обрезан целиком */
|
||||
}
|
||||
gfx_set_bank(GFX_BANK_SPRITE); /* видео-ОЗУ, без теневой копии */
|
||||
if (skip < (int)h) /* +YOFF: центрирование */
|
||||
gfx_blit_cols_part(bx, top + skip + POP_YOFF, img, flip, skip, 0);
|
||||
if (skip < (int)h) { /* +YOFF: центрирование */
|
||||
if (pop_onscreen_cols(bx, top + skip + POP_YOFF, w,
|
||||
(uint16_t)((int)h - skip)))
|
||||
gfx_blit_cols_part_noclip(bx, top + skip + POP_YOFF, img, flip,
|
||||
(uint8_t)skip, 0);
|
||||
else
|
||||
gfx_blit_cols_part(bx, top + skip + POP_YOFF, img, flip, skip, 0);
|
||||
}
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
/* клип СВОЕГО выхода за поле (падение ниже поля / верхний борт) —
|
||||
* узко, только по прямоугольнику Кида и только при выходе. */
|
||||
@@ -510,57 +692,16 @@ void kid_draw(void)
|
||||
kid_fp_w = w; kid_fp_h = h; /* размер кадра для футпринта fore */
|
||||
}
|
||||
/* add_sword_to_objtable (seg006:1798): клинок отдельным спрайтом поверх
|
||||
* Kid. Прямоугольник heal расширяем объединением — иначе клинок, выйдя
|
||||
* за габарит Kid, оставит след. */
|
||||
if (sword_ok && Kid.frame >= 229 && Kid.frame < 238) {
|
||||
uint8_t sf = (uint8_t)(fr.sword & 0x3F);
|
||||
if (sf >= SWORD_SF_LO && sf <= SWORD_SF_HI) {
|
||||
uint8_t si = (uint8_t)(sf - SWORD_SF_LO);
|
||||
const uint8_t *simg = (const uint8_t *)atlas_image(&swordp, si);
|
||||
uint16_t sw, sh;
|
||||
gfx_w0_map(swordp.page);
|
||||
sw = (uint16_t)(simg[0] | ((uint16_t)simg[1] << 8));
|
||||
sh = (uint16_t)(simg[2] | ((uint16_t)simg[3] << 8));
|
||||
if (sw && sh) {
|
||||
uint8_t sflip = (Kid.direction >= 0);
|
||||
int sbx = obj_x + ((Kid.direction < 0) ? -(int)SWORD_DX[si]
|
||||
: (int)SWORD_DX[si]);
|
||||
int stop = obj_y + (int)SWORD_DY[si] - (int)sh + 1;
|
||||
uint8_t dp = gfx_get_draw_page() & 1;
|
||||
if (sflip) sbx -= (int)sw;
|
||||
gfx_set_bank(GFX_BANK_SPRITE);
|
||||
gfx_blit_cols_part(sbx, stop + POP_YOFF, simg, sflip, 0, 0);
|
||||
gfx_set_bank(GFX_BANK_NORMAL);
|
||||
if (!kid_lvalid[dp]) {
|
||||
kid_lx[dp] = sbx; kid_ly[dp] = stop;
|
||||
kid_lw[dp] = sw; kid_lh[dp] = sh;
|
||||
kid_lvalid[dp] = 1;
|
||||
} else {
|
||||
int x0 = kid_lx[dp], y0 = kid_ly[dp];
|
||||
int x1 = x0 + (int)kid_lw[dp], y1 = y0 + (int)kid_lh[dp];
|
||||
if (sbx < x0) x0 = sbx;
|
||||
if (stop < y0) y0 = stop;
|
||||
if (sbx + (int)sw > x1) x1 = sbx + (int)sw;
|
||||
if (stop + (int)sh > y1) y1 = stop + (int)sh;
|
||||
kid_lx[dp] = x0; kid_ly[dp] = y0;
|
||||
kid_lw[dp] = (uint16_t)(x1 - x0);
|
||||
kid_lh[dp] = (uint16_t)(y1 - y0);
|
||||
}
|
||||
}
|
||||
}
|
||||
* Kid — со СВОИМ прямоугольником heal (kid_overlay_add). */
|
||||
{
|
||||
int sx, sy; uint16_t sw, sh;
|
||||
if (pop_sword_draw(&Kid, &fr, obj_x, obj_y, &sx, &sy, &sw, &sh))
|
||||
kid_overlay_add(gfx_get_draw_page() & 1, sx, sy, sw, sh);
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
/* Окно fore-слоя = итоговый прямоугольник спрайта (с клинком и
|
||||
* обрезкой clip_char): fore-проход вернёт куски тайлов ТОЛЬКО поверх
|
||||
* него, а не целыми тайлами — см. pop_bg.c, fclip_on. Ставим и когда
|
||||
* рисовать нечего (kid_lvalid=0): нулевое окно = fore-проход не
|
||||
* трогает экран, что и правильно — перекрывать нечего. */
|
||||
{
|
||||
uint8_t dp = gfx_get_draw_page() & 1;
|
||||
if (kid_lvalid[dp])
|
||||
pop_fore_set_clip(kid_lx[dp], kid_ly[dp] + POP_YOFF,
|
||||
(int)kid_lw[dp], (int)kid_lh[dp]);
|
||||
else
|
||||
pop_fore_set_clip(0, 0, 0, 0);
|
||||
}
|
||||
/* Окно fore-слоя = объединение «Kid + клинок» (с обрезкой clip_char):
|
||||
* fore-проход вернёт куски тайлов ТОЛЬКО поверх него, а не целыми
|
||||
* тайлами — см. pop_bg.c, fclip_on. Ставим и когда рисовать нечего:
|
||||
* нулевое окно = fore-проход не трогает экран, что и правильно. */
|
||||
kid_fore_clip(gfx_get_draw_page() & 1);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include "pop_char.h"
|
||||
#include "kid_data.h" /* kframe — тип кадра для pop_sword_draw */
|
||||
|
||||
/* Состояние персонажа — общий тип для Кида, стража и Char (pop_char.h). */
|
||||
extern pop_char_t Kid;
|
||||
@@ -43,6 +44,15 @@ void pop_kid_data_free(void);
|
||||
int pop_sword_load(void);
|
||||
void pop_kid_free(void);
|
||||
|
||||
/* Нарисовать клинок chtab_0 поверх персонажа (порт add_sword_to_objtable,
|
||||
* seg006:1798) — общая точка для Кида и стража: меч в оригинале ОДИН на
|
||||
* всех. obj_x/obj_y — та же опорная точка, что у спрайта персонажа.
|
||||
* Возвращает 1 и прямоугольник клинка (для heal), 0 — клинка нет.
|
||||
* Страницу W0 оставляет замапленной: вызывающий делает gfx_w0_unmap. */
|
||||
uint8_t pop_sword_draw(const pop_char_t *ch, const kframe *fr,
|
||||
int obj_x, int obj_y,
|
||||
int *rx, int *ry, uint16_t *rw, uint16_t *rh);
|
||||
|
||||
/* Инициализировать Kid: последовательность seq_id, клетка col/row,
|
||||
* направление dir (-1/0). Ставит x/y по x_bump/y_land. */
|
||||
void kid_init(uint8_t seq_id, int8_t col, int8_t row, int8_t dir);
|
||||
@@ -59,11 +69,22 @@ void play_seq(void); /* работает с Char — см. pop_kid_play
|
||||
* Интерпретатор последовательностей общий для Kid и стража: у них общая
|
||||
* seqtbl, но РАЗНЫЕ таблицы кадров (load_frame выбирает по Char.charid). */
|
||||
extern pop_char_t Char;
|
||||
/* Соперник активного персонажа (порт Opp): ставится вместе с Char через
|
||||
* pop_loadshad_and_opp. Читают ИИ стража и боёвка (char_opp_dist). */
|
||||
extern pop_char_t Opp;
|
||||
void pop_loadkid(void);
|
||||
void pop_savekid(void);
|
||||
void pop_savekid_state(void); /* Kid = Char, кадр не трогаем */
|
||||
void pop_loadshad(void);
|
||||
void pop_saveshad(void);
|
||||
void pop_loadshad_and_opp(void); /* Char = Guard, Opp = Kid (seg006:841) */
|
||||
void pop_saveshad_and_opp(void); /* Guard = Char, Kid = Opp */
|
||||
void pop_loadkid_and_opp(void); /* Char = Kid, Opp = Guard */
|
||||
void pop_savekid_and_opp(void); /* Kid = Char, Guard = Opp */
|
||||
|
||||
/* Дистанция до соперника в системе Char: >0 впереди, <0 за спиной,
|
||||
* 999 — разные комнаты (порт char_opp_dist, seg006:2135). */
|
||||
int16_t pop_char_opp_dist(void);
|
||||
|
||||
/* Прокрутить последовательность персонажа целиком (load + play_seq +
|
||||
* save). Звать вместо голого play_seq. */
|
||||
@@ -96,6 +117,10 @@ void kid_draw(void);
|
||||
* звать СРАЗУ после kid_draw в кадре, когда HP убавилось (pop_kid_hurt). */
|
||||
void kid_draw_splash(void);
|
||||
|
||||
/* Блит спрайта chtab_2 по id, левым верхним углом в (x,y) — для полосы HP
|
||||
* (id 216 полное деление / 217 пустое). */
|
||||
void pop_kid_img_blit(uint8_t image, int x, int y);
|
||||
|
||||
/* Восстановить фон (из теневой копии) на месте ПРЕДЫДУЩЕГО кадра Kid —
|
||||
* вместо перерисовки всего экрана. Звать ПЕРЕД kid_tick/kid_draw. */
|
||||
void kid_heal(void);
|
||||
|
||||
@@ -27,12 +27,18 @@ extern void _gfx_w0_isr(void);
|
||||
#define BP_GTILE 2119u
|
||||
#define BP_GDIR 2143u
|
||||
#define BP_GX 2167u
|
||||
#define BP_GSEQLO 2191u
|
||||
#define BP_GSKILL 2215u
|
||||
#define BP_GSEQHI 2239u
|
||||
#define BP_GCOLOR 2263u
|
||||
#define BP_START 2112u
|
||||
#define ROOM_TILES 30u
|
||||
#define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */
|
||||
|
||||
/* fwd: инициализация живой копии стражей (тело ниже). */
|
||||
static void gstate_init(void);
|
||||
|
||||
|
||||
static uint8_t lvl_blk, lvl_page, lvl_ok;
|
||||
/* Копии таблиц дверных связей в W2 (LINKMAP мутабельна — таймеры кнопок). */
|
||||
static uint8_t dl1[256], dl2[256];
|
||||
@@ -74,6 +80,7 @@ int pop_level_load(const char *path)
|
||||
|
||||
if (n < (int)MIN_SIZE) { mem_free_block(blk); return -1; }
|
||||
lvl_ok = 1;
|
||||
gstate_init(); /* живая копия состояния стражей — из уровня */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -243,22 +250,95 @@ static uint8_t start_byte(uint16_t off)
|
||||
/* Страж комнаты (enter_guard, seg002:0112 + pos_guards, seg003). Возврат
|
||||
* 0 = стража в комнате нет (tile >= 30). X оригинал пересчитывает из
|
||||
* колонки (pos_guards), поэтому guards_x из файла не читаем. */
|
||||
/* ---- ЖИВОЕ состояние стражей (порт level.guards_*) ------------------ *
|
||||
* В оригинале массивы стражей — часть структуры уровня в ОЗУ, и движок их
|
||||
* ПЕРЕПИСЫВАЕТ: leave_guard при выходе из комнаты кладёт туда текущие
|
||||
* позицию/направление/мастерство, а у МЁРТВОГО ещё и curr_seq. Благодаря
|
||||
* этому, вернувшись, находишь труп там же, а не воскресшего стража.
|
||||
* У нас уровень лежит в EMM-странице только на чтение, поэтому держим
|
||||
* мутабельную копию в W2: 6 байт на комнату. */
|
||||
#define GS_TILE 0
|
||||
#define GS_DIR 1
|
||||
#define GS_X 2
|
||||
#define GS_SKILL 3
|
||||
#define GS_SEQL 4
|
||||
#define GS_SEQH 5
|
||||
static uint8_t gstate[24 * 6];
|
||||
|
||||
static void gstate_init(void)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)LVL_DATA_OFF;
|
||||
uint8_t r;
|
||||
gfx_w0_map(lvl_page);
|
||||
for (r = 0; r < 24; r++) {
|
||||
uint8_t *g = gstate + (uint16_t)r * 6;
|
||||
g[GS_TILE] = p[BP_GTILE + r];
|
||||
g[GS_DIR] = p[BP_GDIR + r];
|
||||
g[GS_X] = p[BP_GX + r];
|
||||
g[GS_SKILL] = p[BP_GSKILL + r];
|
||||
/* guards_seq_lo/hi в ФАЙЛЕ не используются — там 0xFF во всех
|
||||
* комнатах (оригинал чистит их в reset_level_unused_fields).
|
||||
* Осмысленное значение туда кладёт только leave_guard, поэтому
|
||||
* стартуем нулями: 0 = «поднимать стандартной стойкой».
|
||||
* Без этого в интерпретатор уходил curr_seq = 0xFFFF и он вис. */
|
||||
g[GS_SEQL] = 0;
|
||||
g[GS_SEQH] = 0;
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
|
||||
/* leave_guard (seg002:02F5): запомнить состояние стража комнаты. seq
|
||||
* сохраняем ТОЛЬКО у мёртвого — у живого seq_hi = 0 означает «поднять
|
||||
* стандартной стойкой», как в оригинале. */
|
||||
void pop_guard_state_save(uint8_t room, uint8_t tile, int8_t dir, uint8_t x,
|
||||
uint8_t skill, uint16_t seq, uint8_t dead)
|
||||
{
|
||||
uint8_t *g;
|
||||
if (room < 1 || room > 24) return;
|
||||
g = gstate + (uint16_t)(room - 1) * 6;
|
||||
g[GS_TILE] = tile;
|
||||
g[GS_DIR] = (uint8_t)dir;
|
||||
g[GS_X] = x;
|
||||
g[GS_SKILL] = skill;
|
||||
if (dead) { g[GS_SEQL] = (uint8_t)seq; g[GS_SEQH] = (uint8_t)(seq >> 8); }
|
||||
else { g[GS_SEQH] = 0; }
|
||||
}
|
||||
|
||||
uint16_t pop_guard_state_seq(uint8_t room)
|
||||
{
|
||||
const uint8_t *g;
|
||||
if (room < 1 || room > 24) return 0;
|
||||
g = gstate + (uint16_t)(room - 1) * 6;
|
||||
if (g[GS_SEQH] == 0) return 0; /* живой — стандартная стойка */
|
||||
return (uint16_t)(g[GS_SEQL] | ((uint16_t)g[GS_SEQH] << 8));
|
||||
}
|
||||
|
||||
/* Запомненная X стража комнаты (для трупа: он лежит там, где лёг, а не в
|
||||
* центре тайла). */
|
||||
uint8_t pop_guard_state_x(uint8_t room)
|
||||
{
|
||||
if (room < 1 || room > 24) return 0;
|
||||
return gstate[(uint16_t)(room - 1) * 6 + GS_X];
|
||||
}
|
||||
|
||||
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
|
||||
uint8_t *color, uint8_t *skill)
|
||||
{
|
||||
const uint8_t *p;
|
||||
const uint8_t *g;
|
||||
uint8_t t;
|
||||
if (!lvl_ok || room < 1 || room > 24) return 0;
|
||||
gfx_w0_map(lvl_page);
|
||||
p = (const uint8_t *)LVL_DATA_OFF;
|
||||
t = p[BP_GTILE + room - 1];
|
||||
/* Тайл/направление/мастерство — из ЖИВОЙ копии (её правит
|
||||
* pop_guard_state_save); цвет статичен, его читаем со страницы. */
|
||||
g = gstate + (uint16_t)(room - 1) * 6;
|
||||
t = g[GS_TILE];
|
||||
if (t < 30) {
|
||||
*tile = t;
|
||||
*dir = (int8_t)p[BP_GDIR + room - 1];
|
||||
*color = (uint8_t)(p[BP_GCOLOR + room - 1] & 0x0F);
|
||||
*skill = p[BP_GSKILL + room - 1];
|
||||
*dir = (int8_t)g[GS_DIR];
|
||||
*skill = g[GS_SKILL];
|
||||
gfx_w0_map(lvl_page);
|
||||
*color = (uint8_t)(((const uint8_t *)LVL_DATA_OFF)[BP_GCOLOR + room - 1] & 0x0F);
|
||||
gfx_w0_unmap();
|
||||
}
|
||||
gfx_w0_unmap();
|
||||
return (uint8_t)(t < 30);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,18 @@ int8_t pop_room_col_landing(uint8_t room, uint8_t col);
|
||||
/* Стартовая позиция уровня (блок @2112): комната / поза / направление. */
|
||||
/* Страж комнаты: 1 = есть (заполнит tile/dir/color/skill), 0 = нет.
|
||||
* Порт enter_guard (seg002:0112). */
|
||||
/* leave_guard (seg002:02F5): запомнить состояние стража комнаты в ЖИВОЙ
|
||||
* копии массивов уровня. dead != 0 — сохранить и последовательность, тогда
|
||||
* при возврате в комнату страж поднимется трупом там же, где лёг.
|
||||
* tile == 30 (или больше) — «стража в комнате больше нет». */
|
||||
void pop_guard_state_save(uint8_t room, uint8_t tile, int8_t dir, uint8_t x,
|
||||
uint8_t skill, uint16_t seq, uint8_t dead);
|
||||
|
||||
/* Последовательность запомненного стража (0 — поднимать стандартной
|
||||
* стойкой, как у живого). */
|
||||
uint16_t pop_guard_state_seq(uint8_t room);
|
||||
uint8_t pop_guard_state_x(uint8_t room);
|
||||
|
||||
uint8_t pop_level_guard(uint8_t room, uint8_t *tile, int8_t *dir,
|
||||
uint8_t *color, uint8_t *skill);
|
||||
|
||||
|
||||
@@ -114,6 +114,12 @@ uint8_t pop_loose_fell; /* 0=нет; иначе tilepos+1 упавше
|
||||
uint8_t pop_kid_dead; /* 1 = Kid мёртв (напоролся на пики и т.п.) */
|
||||
uint8_t pop_kid_hurt; /* 1 = в этом кадре убавилось HP → «брызги» */
|
||||
uint8_t hitp_curr; /* текущее HP (0 = мёртв); pop_kid_hp_reset ставит старт */
|
||||
uint8_t hitp_max; /* стартовое HP Кида (для индикатора) */
|
||||
/* Дельта HP Кида за кадр (порт hitp_delta). Её ставит СВОРД-путь боёвки
|
||||
* (guards.c), а применяет pop_do_delta_hp раз в кадр — как в оригинале.
|
||||
* Путь пик/падения (take_hp ниже) правит hitp_curr сразу: он был написан и
|
||||
* проверен до боёвки, и переводить его на дельту без нужды рискованно. */
|
||||
int8_t hitp_delta;
|
||||
static uint8_t g_room; /* номер ТЕКУЩЕЙ комнаты (для room_modif пик/ворот) */
|
||||
static uint8_t *g_fg; /* fg[30], row*10+col (МУТАБЕЛЬНАЯ — loose→empty) */
|
||||
static uint8_t g_curr_tile; /* = curr_tile2: тайл последнего get_tile */
|
||||
@@ -156,7 +162,7 @@ void pop_map_set(uint8_t *fg) __banked { g_fg = fg; }
|
||||
|
||||
void pop_map_set_room(uint8_t room) __banked { g_room = room; }
|
||||
|
||||
void pop_kid_hp_reset(void) __banked { hitp_curr = 3; pop_kid_dead = 0; }
|
||||
void pop_kid_hp_reset(void) __banked { hitp_curr = hitp_max = 3; hitp_delta = 0; pop_kid_dead = 0; }
|
||||
|
||||
/* take_hp (seg006:06BD, только Kid): count>=hitp → смерть (return 1). */
|
||||
static uint8_t take_hp(uint8_t count)
|
||||
@@ -202,6 +208,16 @@ static uint8_t get_tile(int8_t col, int8_t row)
|
||||
return g_curr_tile;
|
||||
}
|
||||
|
||||
/* Тайл ТЕКУЩЕЙ комнаты по клетке — наружу (порт get_tile(room,col,row) для
|
||||
* случая room == текущая). Нужен лучу видимости стража
|
||||
* (check_can_guard_see_kid, seg003:688): тот идёт по ряду между Кидом и
|
||||
* стражем, а оба по условию в одной (= отрисованной) комнате. Колонки за
|
||||
* пределами 0..9 get_tile разрешает через швы — здесь не используются. */
|
||||
uint8_t pop_tile_at(int8_t col, int8_t row) __banked
|
||||
{
|
||||
return get_tile(col, row);
|
||||
}
|
||||
|
||||
/* can_bump_into_gate (seg004:373): ОТКРЫТЫЕ ворота проходимы — Kid не бампит и
|
||||
* идёт сквозь них (на шве — уходит в соседнюю комнату; внутри комнаты — просто
|
||||
* проходит по полу под поднятыми барами). ВАЖНО: ворота остаются тайлом 4 (в
|
||||
@@ -793,6 +809,9 @@ uint8_t pop_down_action(void) __banked
|
||||
uint8_t pop_have_sword;
|
||||
uint8_t pop_item_taken; /* 0 = нет; иначе tilepos+1 поднятого */
|
||||
uint8_t pop_flash_time; /* кадров вспышки фона (flash_time) */
|
||||
/* Цвет вспышки: 0 = ярко-жёлтая (меч), 1 = красная (зелье). В оригинале это
|
||||
* flash_color; нам хватает двух значений, которые реально встречаются. */
|
||||
uint8_t pop_flash_red;
|
||||
static int8_t pickup_obj_type; /* -1 = меч (порт pickup_obj_type) */
|
||||
|
||||
/* do_pickup (seg006:1671): стереть предмет с карты (тайл → пол) и запомнить
|
||||
@@ -816,7 +835,29 @@ void pop_proc_get_object(void) __banked
|
||||
* чёрное поле экрана. У нас так же (gfx_pal_set 0), см. roomtest. */
|
||||
pop_flash_time = 8;
|
||||
}
|
||||
pickup_obj_type = 0; /* зелья (типы 1..6) не портированы */
|
||||
else {
|
||||
/* proc_get_object (seg006:1927), ветка зелий. Типы 3/4/6
|
||||
* (перо/переворот/открыть все ворота) — свойства поздних уровней,
|
||||
* их портируем вместе с этими уровнями. */
|
||||
switch (pickup_obj_type) {
|
||||
case 1: /* здоровье: +1 HP, красная вспышка */
|
||||
if (hitp_curr != hitp_max) {
|
||||
hitp_delta = 1;
|
||||
pop_flash_red = 1;
|
||||
pop_flash_time = 2;
|
||||
}
|
||||
break;
|
||||
case 5: /* «злое» зелье: −1 HP */
|
||||
hitp_delta = -1;
|
||||
pop_flash_red = 1;
|
||||
pop_flash_time = 2;
|
||||
break;
|
||||
default: /* 2 жизнь / 3 перо / 4 переворот /
|
||||
* 6 открыть — TODO с их уровнями */
|
||||
break;
|
||||
}
|
||||
}
|
||||
pickup_obj_type = 0;
|
||||
}
|
||||
|
||||
/* check_get_item + get_item (seg005:061F/073E). Возврат: 0 — предмета нет
|
||||
@@ -845,9 +886,17 @@ uint8_t pop_get_item_action(void) __banked
|
||||
Kid.x = (uint8_t)char_dx_forward((int8_t)((t == TILE_POTION) - 2));
|
||||
return 1;
|
||||
}
|
||||
if (t != TILE_SWORD) return 0; /* питьё зелий не портировано */
|
||||
do_pickup(-1, (uint8_t)(Kid.curr_row * 10 + icol));
|
||||
return 2;
|
||||
{
|
||||
uint8_t tp = (uint8_t)(Kid.curr_row * 10 + icol);
|
||||
if (t == TILE_SWORD) {
|
||||
do_pickup(-1, tp);
|
||||
return 2; /* поднять меч (seq_91) */
|
||||
}
|
||||
/* Зелье: тип лежит в СТАРШИХ битах модификатора (get_item,
|
||||
* seg005: do_pickup(curr_room_modif[curr_tilepos] >> 3)). */
|
||||
do_pickup((int8_t)(pop_trob_modif(g_room)[tp] >> 3), tp);
|
||||
return 3; /* пить (seq_78) */
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Диспетчер ----------------------------------------------------- */
|
||||
|
||||
@@ -30,11 +30,18 @@ void pop_map_set_edges(uint8_t left, uint8_t right, uint8_t up, uint8_t down,
|
||||
* Звать в enter_room. */
|
||||
void pop_map_set_room(uint8_t room) __banked;
|
||||
|
||||
/* Тайл-код текущей комнаты по клетке (col 0..9, row 0..2) — порт
|
||||
* get_tile(room,col,row) для текущей комнаты. Нужен лучу видимости стража
|
||||
* (guards.c): он идёт по ряду между Кидом и стражем. */
|
||||
uint8_t pop_tile_at(int8_t col, int8_t row) __banked;
|
||||
|
||||
/* HP/смерть. pop_kid_dead=1 когда Kid убит (пики); hitp_curr — текущее HP.
|
||||
* pop_kid_hp_reset() ставит старт HP и снимает смерть (звать в kid_init/
|
||||
* респавн). */
|
||||
extern uint8_t pop_kid_dead;
|
||||
extern uint8_t hitp_curr;
|
||||
extern uint8_t hitp_max; /* стартовое HP (для индикатора) */
|
||||
extern int8_t hitp_delta; /* урон за кадр от меча; применяет pop_do_delta_hp */
|
||||
void pop_kid_hp_reset(void) __banked;
|
||||
|
||||
/* Предметы (seg005 check_get_item/get_item, seg006 do_pickup/proc_get_object).
|
||||
@@ -46,7 +53,9 @@ extern uint8_t pop_item_taken;
|
||||
/* Вспышка фона (do_flash/set_bg_attr, seg001:0908): счётчик кадров, пока
|
||||
* НУЛЕВАЯ запись палитры подменена ярко-жёлтым. Тикает вызывающий. */
|
||||
extern uint8_t pop_flash_time;
|
||||
uint8_t pop_get_item_action(void) __banked; /* 0 нет / 1 присесть / 2 поднять меч */
|
||||
/* Цвет вспышки: 0 = ярко-жёлтая (подобран меч), 1 = красная (зелье). */
|
||||
extern uint8_t pop_flash_red;
|
||||
uint8_t pop_get_item_action(void) __banked; /* 0 нет / 1 присесть / 2 поднять меч / 3 пить */
|
||||
void pop_proc_get_object(void) __banked; /* эффект — из play_seq (опкод GET_ITEM) */
|
||||
|
||||
/* Уход из комнаты ВБОК/ВВЕРХ: 0=нет; 1=left,2=right,3=up (= side+1 для
|
||||
|
||||
@@ -13,3 +13,7 @@
|
||||
#include "pop_state.h"
|
||||
|
||||
uint8_t pop_loose_landed;
|
||||
|
||||
/* Чит «бессмертие» (клавиша I). Отладочный: Кид получает анимацию удара,
|
||||
* но HP не теряет — иначе проверять боёвку приходится с трёх попаданий. */
|
||||
uint8_t pop_immortal;
|
||||
|
||||
@@ -14,4 +14,8 @@
|
||||
* разбирает логика loose-полов (pop_map, W1/W2). */
|
||||
extern uint8_t pop_loose_landed;
|
||||
|
||||
/* Чит «бессмертие» (I): Кида бьют, но HP не убавляется. Читают пути урона
|
||||
* (боёвка в банке, пики в pop_map), переключает главный цикл. */
|
||||
extern uint8_t pop_immortal;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -47,7 +47,7 @@ static uint8_t trob_drawn[POP_ROOMTILES];
|
||||
|
||||
/* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и
|
||||
* выбора следующего кадра пламени. */
|
||||
static unsigned long trob_seed;
|
||||
static pop_rnd_t trob_seed;
|
||||
|
||||
#define trob_prandom(maxv) pop_prandom(&trob_seed, (maxv))
|
||||
|
||||
|
||||
@@ -46,8 +46,13 @@ __sfr __at (0xFE) io_border;
|
||||
#define PROF(n) ((void)0)
|
||||
#endif
|
||||
|
||||
/* Сколько банков грузит crt0_banked (читает его стартовый код). */
|
||||
const uint8_t n_banks = 3;
|
||||
/* Сколько банков грузит crt0_banked (читает его стартовый код).
|
||||
* 1 = guards (ИИ/боёвка), 2 = pop_bg (фон, fore, loose), 3 = pop_map
|
||||
* (коллизия/физика), 4 = pop_gdraw (отрисовка стража + полоса HP).
|
||||
* Отрисовка стража вынесена из банка 2 отдельно: тот подошёл к 16 КБ
|
||||
* вплотную (16 021 из 16 384), а расти ему ещё есть куда — тайлы поздних
|
||||
* уровней, чомперы, зеркало. */
|
||||
const uint8_t n_banks = 4;
|
||||
|
||||
/* Режим читов (pop_cheat.h): на время разработки включаем в main. */
|
||||
uint8_t pop_cheats;
|
||||
@@ -73,6 +78,7 @@ uint8_t pop_cheats;
|
||||
* Убирается закомментированием ОДНОЙ строки ниже. */
|
||||
#define ROOMNAV 1
|
||||
|
||||
|
||||
/* Рабочая копия ТЕКУЩЕЙ комнаты (W2, мутабельная: loose→empty) + срезы
|
||||
* соседей для кромок. Заполняются pop_room_load из уровня. File-scope,
|
||||
* чтобы enter_room переключал комнату без длинного списка параметров. */
|
||||
@@ -164,6 +170,7 @@ static void apply_overrides(uint8_t room, uint8_t *tiles, uint8_t base, uint8_t
|
||||
static void enter_room(uint8_t room)
|
||||
{
|
||||
uint8_t pg;
|
||||
pop_guard_leave(); /* запомнить стража уходящей комнаты */
|
||||
uint8_t *room_mod;
|
||||
pop_room_load(room, room_fg, room_bg, lcol_fg, lcol_bg, rcol_fg, rcol_bg, below_fg);
|
||||
apply_overrides(room, room_fg, 0, 30); /* щебень/пустота от упавших плит */
|
||||
@@ -191,10 +198,16 @@ static void enter_room(uint8_t room)
|
||||
}
|
||||
}
|
||||
pop_map_set_room(room);
|
||||
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
|
||||
/* Kid.room — как в оригинале: «комната персонажа». Полная модель
|
||||
* (Kid.room ≠ отрисованной у шва) ещё не портирована, но сравнение
|
||||
* Kid.room == Guard.room нужно уже сейчас: без него луч видимости
|
||||
* стража никогда не срабатывает. */
|
||||
Kid.room = room;
|
||||
pop_loose_reset(); /* loose-состояние не тянем из старой комнаты */
|
||||
pop_trob_room_changed(); /* отложенные перерисовки пик — не из старой комнаты */
|
||||
pop_trob_anim_room(room); /* факелы/зелья: стартовая фаза + trob */
|
||||
pop_guard_enter(room); /* страж комнаты (порт enter_guard) */
|
||||
pop_hp_invalidate(); /* фон перерисован — полоса HP тоже */
|
||||
/* Модификаторы тайлов (openness ворот, состояние пик) — ПЕРСИСТЕНТНЫЕ
|
||||
* per-room (room_modif живёт между входами); рисуем и коллидируем по ним,
|
||||
* а не по свежему backtable. Фаза P0. */
|
||||
@@ -232,7 +245,7 @@ int main(void)
|
||||
#ifdef ROOMNAV
|
||||
uint8_t nav_prev = 0;
|
||||
#endif
|
||||
uint8_t cheat_prev = 0;
|
||||
uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0;
|
||||
|
||||
if (pop_bg_load() != 0) { /* атласы — ДО initgraph (снимает DSS-страницу W0) */
|
||||
puts("pop_bg_load failed");
|
||||
@@ -309,11 +322,22 @@ int main(void)
|
||||
* стоит, можно спокойно разбирать позу Kid и окклюзию. */
|
||||
if (kbd_raw_down(0x16)) frozen = 1;
|
||||
if (kbd_raw_down(0x1E)) frozen = 0;
|
||||
{ /* Читы (seg000: switch под if (cheats_enabled)). K — убить
|
||||
* стража; по фронту нажатия, чтобы не срабатывало каждый кадр. */
|
||||
{ /* Читы (seg000: switch под if (cheats_enabled)). Все — по
|
||||
* ФРОНТУ нажатия, иначе срабатывали бы каждый кадр.
|
||||
* K — убить стража;
|
||||
* I — бессмертие Кида (наш отладочный, в оригинале его нет:
|
||||
* иначе боёвку проверять получается ровно три раза);
|
||||
* S — выдать меч, не бегая за ним в комнату 15. Штатно меч
|
||||
* берётся с пола (pop_have_sword ставит подбор). */
|
||||
uint8_t k = kbd_raw_down(KBD_CHEAT_KILL);
|
||||
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
|
||||
uint8_t i = kbd_raw_down(KBD_CHEAT_IMMO);
|
||||
uint8_t s = kbd_raw_down(KBD_CHEAT_SWORD);
|
||||
if (pop_cheats && k && !cheat_prev) pop_guard_kill();
|
||||
if (pop_cheats && i && !immo_prev) pop_immortal = !pop_immortal;
|
||||
if (pop_cheats && s && !sword_prev) pop_have_sword = 1;
|
||||
cheat_prev = k;
|
||||
immo_prev = i;
|
||||
sword_prev = s;
|
||||
}
|
||||
if (frozen) {
|
||||
PROF(0); /* чёрный: ждём vsync */
|
||||
@@ -356,6 +380,7 @@ int main(void)
|
||||
back = dbuf ? (uint8_t)(front ^ 1) : 0;
|
||||
gfx_set_draw_page(back);
|
||||
|
||||
pop_check_can_guard_see_kid(); /* луч видимости — ДО логики персонажей */
|
||||
pop_ctrl_tick(); /* ввод -> control(): смена seq */
|
||||
kid_heal(); /* стереть прошлый кадр ЭТОЙ страницы */
|
||||
pop_guard_heal(); /* и прошлый кадр стража */
|
||||
@@ -364,6 +389,11 @@ int main(void)
|
||||
kid_tick(); /* play_seq: следующий кадр */
|
||||
pop_phys_tick(); /* K3: падение/приземление/стена */
|
||||
pop_guard_tick(); /* логика стража — В БАНКЕ (guards.c) */
|
||||
/* Боёвка, порядок как в play_frame (seg000:872): сначала пометить
|
||||
* попадания с обеих сторон, потом применить, потом свести HP. */
|
||||
pop_check_sword_hurting();
|
||||
pop_check_sword_hurt();
|
||||
pop_do_delta_hp();
|
||||
if (pop_fell_out) { /* Kid пересёк нижнюю границу комнаты */
|
||||
uint8_t down = pop_room_link(cur_room, LINK_DOWN);
|
||||
pop_fell_out = 0;
|
||||
@@ -463,8 +493,12 @@ int main(void)
|
||||
* Приоритет как в оригинале: активный flash_time > кадр урона
|
||||
* (hitp_delta<0 -> ярко-красный, ровно на один кадр). */
|
||||
if (pop_flash_time) {
|
||||
flash_bg(255, 255, 85); /* color_14_brightyellow */
|
||||
/* Цвет вспышки: меч — ярко-жёлтый, зелье — красный (flash_color
|
||||
* оригинала; нам хватает этих двух). */
|
||||
if (pop_flash_red) flash_bg(255, 0, 0); /* color_4_red */
|
||||
else flash_bg(255, 255, 85); /* color_14_brightyellow */
|
||||
pop_flash_time--;
|
||||
if (!pop_flash_time) pop_flash_red = 0;
|
||||
flash_on = 1;
|
||||
} else if (pop_kid_hurt) {
|
||||
flash_bg(255, 85, 85); /* color_12_brightred */
|
||||
@@ -480,6 +514,7 @@ int main(void)
|
||||
kid_draw_splash();
|
||||
pop_kid_hurt = 0;
|
||||
}
|
||||
pop_hp_draw(); /* полоса HP — только при изменении */
|
||||
PROF(6); /* fore поверх Kid + борта */
|
||||
pop_fore_over_kid(kid_fp_obj_x(), kid_fp_obj_y(),
|
||||
kid_fp_width(), kid_fp_height(),
|
||||
|
||||
@@ -14,7 +14,7 @@ seqtbl адресуется базой SEQTBL_BASE=0x196E: элемент = kid_
|
||||
import os
|
||||
import re
|
||||
|
||||
SRC = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/src"
|
||||
SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "SDLPoP", "src")
|
||||
OUT_BIN_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"..", "poc", "res", "kid")
|
||||
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
@@ -44,6 +44,18 @@ def parse_frame_table(txt, name="frame_table_kid"):
|
||||
return frames
|
||||
|
||||
|
||||
def parse_sword_tbl(txt):
|
||||
"""sword_tbl[] (seg006:2047) -> список (id, x, y). id 255 = клинка нет.
|
||||
Индекс — sword_frame (cur_frame.sword & 0x3F); один и тот же chtab_0 и у
|
||||
Кида, и у стража."""
|
||||
m = re.search(r'const sword_table_type sword_tbl\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
|
||||
rows = []
|
||||
for row in re.findall(r'\{([^}]*)\}', m.group(1)):
|
||||
a, b, c = [int(p.strip(), 0) for p in row.split(',')]
|
||||
rows.append((a & 0xFF, b & 0xFF, c & 0xFF))
|
||||
return rows
|
||||
|
||||
|
||||
def parse_byte_array(txt, name):
|
||||
m = re.search(r'const byte %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S)
|
||||
return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))]
|
||||
@@ -69,6 +81,7 @@ def main():
|
||||
gframes = parse_frame_table(seg006, "frame_tbl_guard")
|
||||
seqtbl = parse_byte_array(seqc, "original_seqtbl")
|
||||
offs = parse_word_array(seqc, "original_seqtbl_offsets")
|
||||
swtbl = parse_sword_tbl(seg006)
|
||||
|
||||
with open(OUT, "w") as f:
|
||||
f.write("/* kid_data.h — данные анимации Kid (frame_table + seqtbl +\n"
|
||||
@@ -102,7 +115,22 @@ def main():
|
||||
f.write("#define KID_NSEQ %d\n" % len(offs))
|
||||
f.write("static const uint16_t kid_seq_off[KID_NSEQ] = {\n")
|
||||
f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n")
|
||||
f.write("#endif\n")
|
||||
|
||||
# sword_tbl — макро-инициализаторами, а не готовыми массивами: так
|
||||
# 159 байт таблицы лягут в ОДИН TU (pop_kid.c), а не в каждый, кто
|
||||
# включил заголовок.
|
||||
f.write("/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0.\n"
|
||||
" * Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет.\n"
|
||||
" * Смещения x/y — ЭКРАННЫЕ (применяются после calc_screen_x_coord),\n"
|
||||
" * x — «вперёд» по направлению персонажа. */\n")
|
||||
f.write("#define KID_NSWORD %d\n" % len(swtbl))
|
||||
for i, (nm, fmt) in enumerate((("ID", "0x%02X"), ("DX", "%d"), ("DY", "%d"))):
|
||||
# dx/dy — знаковые: печатаем со знаком, иначе int8_t = overflow
|
||||
vals = [r[i] if i == 0 else (r[i] - 256 if r[i] > 127 else r[i])
|
||||
for r in swtbl]
|
||||
f.write("#define KID_SWORD_%s_INIT { \\\n" % nm)
|
||||
f.write(emit_c(vals, 12, fmt).replace("\n", " \\\n") + " \\\n}\n")
|
||||
f.write("\n#endif\n")
|
||||
|
||||
# kid_data.bin: кадры (по 5 байт) + seqtbl подряд
|
||||
os.makedirs(OUT_BIN_DIR, exist_ok=True)
|
||||
|
||||
@@ -26,7 +26,10 @@ OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid")
|
||||
PAL_BASE = 0x70 # слоты палитры Sprinter под Kid
|
||||
SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F
|
||||
GUARD_PAL_BASE = 0x90 # chtab_5 (страж) -> слоты 0x90..0x9F
|
||||
SWORD_IDS = tuple(range(20, 28)) # sword_tbl для кадров 229..236 (найти-убрать)
|
||||
# chtab_0 целиком: sword_tbl (seg006:2047) адресует id 0..33 — подъём меча,
|
||||
# ножны И вся боёвка. Меч в оригинале ОДИН на всех: и Кид, и страж рисуют
|
||||
# клинок из этого же chtab_0 (add_sword_to_objtable, seg006:1798).
|
||||
SWORD_IDS = tuple(range(0, 34))
|
||||
SHIFT = 3 # kid[img>>3], idx img&7 (Kid-кадры крупные: 8/стр ≤16КБ)
|
||||
MASK = (1 << SHIFT) - 1
|
||||
|
||||
@@ -150,8 +153,7 @@ def main():
|
||||
names.append(None)
|
||||
print(f" (kid стр {pg}: пусто)")
|
||||
|
||||
# chtab_0: меч в руке. Пакуем ТОЛЬКО кадры подъёма/ножен (sword_tbl
|
||||
# 35..42 -> id 20..27); индекс в атласе = id - 20 (бой не портирован).
|
||||
# chtab_0: меч в руке — весь chtab (id 0..33), индекс в атласе = id.
|
||||
sw = {}
|
||||
for i, id in enumerate(SWORD_IDS):
|
||||
w, h, pix = load_sword(id)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* gfx_blit_cols_part_noclip — блит спрайта column-major БЕЗ клипа по экрану,
|
||||
* с обрезкой сверху (как gfx_blit_cols_part). Пара к gfx_blit_cols_part
|
||||
* ровно так же, как gfx_blit_part_noclip — к gfx_blit_part.
|
||||
*
|
||||
* Зачем: у персонажей PoP спрайт почти всегда целиком на экране, а общий
|
||||
* gfx_blit_cols_part платит за проверку четырёх границ, пересчёт w/h/cx/sy и
|
||||
* 16-битную арифметику независимо от того, вылезает край или нет — замер в
|
||||
* MAME (roomtest, страж + клинок): 5 622 такта подготовки против 13 596 на
|
||||
* сам accel-проход. Здесь та же колонка уходит в _bgi_blit_cols_raw без
|
||||
* подготовки.
|
||||
*
|
||||
* Требования (проверяет ВЫЗЫВАЮЩИЙ, кода проверок здесь нет):
|
||||
* - прямоугольник назначения целиком на экране;
|
||||
* - ширина и высота кадра <= 255, y <= 255;
|
||||
* - skip < высоты кадра.
|
||||
* Не выполняется — зовите gfx_blit_cols_part.
|
||||
*
|
||||
* Флип, как и в gfx_blit_cols_part, бесплатный: подаём src с последней
|
||||
* колонки и отрицательный страйд — accel сам кладёт её в левый x.
|
||||
*
|
||||
* W3-скобка ставится ЗДЕСЬ (код libbgi живёт в W1): из приложения,
|
||||
* собранного с --w3, её вызывать нельзя — после _bgi_begin окно W3 занято
|
||||
* видеобанком и код вызывающего исчезает из адресного пространства.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include "../_bgi.h"
|
||||
|
||||
void gfx_blit_cols_part_noclip(int x, int y, const void *img, uint8_t flip,
|
||||
uint8_t skip, uint8_t rows)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)img;
|
||||
uint8_t w = p[0]; /* ширина кадра (< 256) */
|
||||
uint8_t fh = p[2]; /* высота кадра (< 256) */
|
||||
const uint8_t *src = p + 4;
|
||||
uint8_t h;
|
||||
|
||||
if (w == 0 || fh == 0 || skip >= fh) return;
|
||||
h = (uint8_t)(fh - skip);
|
||||
if (rows && rows < h) h = rows;
|
||||
|
||||
if (flip) src += (uint16_t)(w - 1) * fh; /* последняя колонка -> левый x */
|
||||
src += skip;
|
||||
|
||||
_bgi_begin();
|
||||
_bgi_blit_cols_raw(src, (uint8_t *)(_gfx_addr_base + (uint16_t)x),
|
||||
w, h, flip ? -(int)fh : (int)fh, (uint8_t)y);
|
||||
_bgi_end();
|
||||
}
|
||||
@@ -128,6 +128,16 @@ void gfx_blit_cols(int x, int y, const void *img, uint8_t flip);
|
||||
void gfx_blit_cols_part(int x, int y, const void *img, uint8_t flip,
|
||||
int skip, int rows);
|
||||
|
||||
/* То же БЕЗ клипа по экрану — пара к gfx_blit_cols_part так же, как
|
||||
* gfx_blit_part_noclip к gfx_blit_part. Вызывающий гарантирует: спрайт
|
||||
* целиком на экране, ширина/высота кадра и y <= 255, skip < высоты.
|
||||
* rows = 0 — «до низа кадра». Экономит ~5.6 К тактов подготовки на вызов
|
||||
* (замер PoP roomtest: подготовка 5 622 против 13 596 на сам accel-проход),
|
||||
* что для персонажа со спрайтом-компаньоном (клинок) даёт заметную долю
|
||||
* кадра. */
|
||||
void gfx_blit_cols_part_noclip(int x, int y, const void *img, uint8_t flip,
|
||||
uint8_t skip, uint8_t rows);
|
||||
|
||||
/* Восстановить прямоугольник экрана из ОЗУ-копии (стирание спрайта/
|
||||
* оверлея, нарисованного банком с битом 2: 0x54/0x5C). Всегда
|
||||
* работает банком GFX_BANK_NORMAL независимо от gfx_set_bank;
|
||||
|
||||
+31
-6
@@ -38,9 +38,31 @@ dd if=/dev/zero of="$RAW" bs=512 count=$SECTORS status=none
|
||||
printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC"
|
||||
export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1
|
||||
|
||||
# mtools собран с enable-raw-term и, когда ему нужно что-то спросить,
|
||||
# открывает УПРАВЛЯЮЩИЙ ТЕРМИНАЛ напрямую (/dev/tty) — в обход stdin.
|
||||
# Поэтому ни `< /dev/null`, ни перенаправление stdio не спасают: из
|
||||
# терминала сборка молча вставала навсегда на `mmd z:/BG` (подтверждено
|
||||
# lsof: fd 0 = /dev/null, fd 4 = /dev/tty, процесс спит). Лечится только
|
||||
# отсутствием управляющего терминала — запускаем mtools в новой сессии,
|
||||
# тогда открыть /dev/tty нечего и mtools идёт неинтерактивным путём.
|
||||
#
|
||||
# setsid(1) в macOS нет, поэтому делаем то же самое питоном.
|
||||
# Каналов, откуда mtools может ждать ответ, ДВА, и закрывать надо оба:
|
||||
# /dev/tty — убирается новой сессией (os.setsid);
|
||||
# stdin — убирается перенаправлением из /dev/null.
|
||||
# Закрыть только один недостаточно: с одним лишь setsid mmd переключался на
|
||||
# stdin и вис уже на нём (lsof: fd 0 = /dev/ttys002).
|
||||
mt() {
|
||||
python3 -c 'import os,sys; os.setsid(); os.execvp(sys.argv[1], sys.argv[1:])' \
|
||||
"$@" < /dev/null
|
||||
}
|
||||
|
||||
# MBR + раздел 1 на весь диск (тип FAT16), затем формат FAT16.
|
||||
mpartition -I -c -t $CYLS -h $HEADS -s $SECS z:
|
||||
mformat z:
|
||||
echo "--- разметка и формат образа ---"
|
||||
mt mpartition -I -c -t $CYLS -h $HEADS -s $SECS z:
|
||||
mt mformat z:
|
||||
|
||||
echo "--- копирование файлов ---"
|
||||
|
||||
# Аргумент может быть либо файлом (кладём в корень), либо парой
|
||||
# КАТАЛОГ:файл — тогда каталог создаётся на образе и файл кладётся туда.
|
||||
@@ -48,16 +70,19 @@ mformat z:
|
||||
for f in "$@"; do
|
||||
case "$f" in
|
||||
*:*) dir="${f%%:*}"; src="${f#*:}"
|
||||
mmd "z:/$dir" 2>/dev/null || true
|
||||
mcopy -o "$src" "z:/$dir/" ;;
|
||||
*) mcopy -o "$f" z: ;;
|
||||
# Каталог может уже быть — это не ошибка; stderr НЕ прячем, иначе
|
||||
# непрошедший вопрос mtools снова окажется невидимым.
|
||||
mt mmd "z:/$dir" >/dev/null 2>&1 || true
|
||||
mt mcopy -o "$src" "z:/$dir/" ;;
|
||||
*) mt mcopy -o "$f" z: ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "--- содержимое образа ---"
|
||||
mdir -/ z:
|
||||
mt mdir -/ z:
|
||||
|
||||
# RAW -> CHD (без сжатия = быстро; та же геометрия).
|
||||
echo "--- конвертация RAW -> CHD ---"
|
||||
chdman createhd -i "$RAW" -o "$OUT_CHD" -c none \
|
||||
-chs $CYLS,$HEADS,$SECS -ss 512 -f
|
||||
echo "готово: $OUT_CHD"
|
||||
|
||||
Reference in New Issue
Block a user