Резидент: pop_kid/pop_guard/main расколоты по частоте вызова (куча 6669 Б)
MEM-COLD3, шаги 3-5. Кандидаты выбирались не по размеру, а по тому, КТО зовёт: если единственный вызывающий уже в банке, код едет к нему и трамплин не появляется вовсе. * pop_kdraw.c -> БАНК 4 (к pop_cdraw.c): клинок в руке (pop_sword_draw) и блит спрайта chtab_2 по id (pop_kid_img_blit). Оба звал ровно pop_cdraw, теперь вызовы прямые — контракт и условие корректности в _pop_kdraw.h. * pop_kboot.c -> БАНК 8: загрузка атласов Кида и страницы kid_data.bin (раз за игру). Дескриптор страницы — _pop_kid.h. * pop_guard_cold.c -> БАНК 8: страж идёт за Кидом + вход стража в комнату. Оба зовёт roomtest_cold.c из того же банка — снова без трамплина. * Из main в банк 8 уехали pop_boot (загрузка ресурсов, палитра, первый экран), pop_level_switch (смена уровня) и pop_shutdown. FIRST_LEVEL переехал в pop_tune.h — константа нужна обеим половинам цикла. _CODE 23005 -> 19861, куча 3525 -> 6669 Б. Банк 8 — 6.5 из 16 КБ. tests-host 6/6 (в наборы добавлены eng_pop_kboot и eng_pop_guard_cold).
This commit is contained in:
@@ -35,7 +35,7 @@ MEMORY ?= huge # small-раскладка + банки кода в W
|
|||||||
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
# ВАЖНО: любое сравнение занятости банков имеет смысл только при ОДНОМ и том
|
||||||
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
# же ALLOCS — иначе сравниваются не правки, а уровни оптимизации.
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c --bank 7=pop_redraw.c --bank 8=roomtest_cold.c --bank 8=pop_level_cold.c $(PROF_FLAGS)
|
EXTRA_FLAGS ?= --gfx 256 -I $(CURDIR)/../poc/res/bg --max-allocs $(ALLOCS) --bank 2=pop_bg.c --bank 7=pop_room.c --bank 4=pop_cdraw.c --bank 4=pop_kdraw.c --bank 3=pop_map.c --bank 1=guards.c --bank 5=pop_ctrl.c --bank 6=pop_trob.c --bank 7=pop_redraw.c --bank 8=roomtest_cold.c --bank 8=pop_level_cold.c --bank 8=pop_kboot.c --bank 8=pop_guard_cold.c $(PROF_FLAGS)
|
||||||
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
# Профилирование полосами бордюра (см. PROF() в roomtest.c) — ВКЛЮЧЕНО по
|
||||||
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
# умолчанию, пока идёт работа с производительностью; make PROF=0 выключает.
|
||||||
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
# Каждая фаза кадра красит бордюр в свой цвет, высота полосы на скриншоте
|
||||||
@@ -112,4 +112,4 @@ $(KID_DATA): $(TC)/pop_pack_kid.py $(TC)/pop_pack_bg.py
|
|||||||
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
|
kid_data.h $(KID_BIN): $(TC)/pop_extract_kid_data.py
|
||||||
cd $(TC) && python3 pop_extract_kid_data.py
|
cd $(TC) && python3 pop_extract_kid_data.py
|
||||||
|
|
||||||
$(EXAMPLE).exe: roomtest_cold.c roomtest_cold.h guards.c pop_guard.c pop_guard.h pop_cdraw.c pop_cdraw.h pop_draw.c _pop_draw.h pop_tile.c pop_tile.h pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_room.c _pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level_cold.c _pop_level.h pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
|
$(EXAMPLE).exe: roomtest_cold.c roomtest_cold.h guards.c pop_guard.c pop_guard_cold.c pop_guard.h pop_cdraw.c pop_cdraw.h pop_draw.c _pop_draw.h pop_tile.c pop_tile.h pop_state.c pop_state.h pop_cheat.h pop_bg.c pop_bg.h pop_room.c _pop_bg.h pop_geom.c pop_geom.h pop_redraw.c pop_redraw.h pop_kid.c pop_kdraw.c pop_kboot.c _pop_kid.h _pop_kdraw.h pop_kid.h pop_ctrl.c pop_ctrl.h pop_map.c pop_map.h pop_level.c pop_level_cold.c _pop_level.h pop_level.h pop_trob.c pop_trob.h kid_data.h $(BG_DATA) $(KID_DATA) $(KID_BIN) $(LVL_DATA)
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* _pop_kdraw.h — ВНУТРЕННИЙ контракт слоя персонажей: pop_cdraw.c (банк 4)
|
||||||
|
* ↔ pop_kdraw.c (тот же банк 4).
|
||||||
|
*
|
||||||
|
* ВАЖНО: объявления здесь НЕ помечены __banked, то есть вызовы идут прямым
|
||||||
|
* `call` — это корректно РОВНО ПОКА оба модуля лежат в ОДНОЙ странице:
|
||||||
|
*
|
||||||
|
* --bank 4=pop_cdraw.c --bank 4=pop_kdraw.c
|
||||||
|
*
|
||||||
|
* Линкер этого НЕ проверяет: если один из них переедет в другой банк,
|
||||||
|
* сборка пройдёт молча, а на устройстве получится прыжок в чужую страницу.
|
||||||
|
* Меняешь раскладку банков в Makefile — проверь эту пару.
|
||||||
|
*
|
||||||
|
* Публичные (из резидента/других банков) входы того же модуля объявлены с
|
||||||
|
* __banked в pop_kid.h — там pop_sword_load.
|
||||||
|
*/
|
||||||
|
#ifndef _POP_KDRAW_H
|
||||||
|
#define _POP_KDRAW_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "pop_char.h"
|
||||||
|
#include "pop_kid.h"
|
||||||
|
|
||||||
|
/* Клинок в руке персонажа (add_sword_to_objtable). Возврат 1 — нарисован,
|
||||||
|
* и в (*rx,*ry,*rw,*rh) лежит его прямоугольник для heal. Страницу W0
|
||||||
|
* оставляет замапленной: размаппит вызывающий. */
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* Спрайт из атласов Кида по его id (полоса HP: деления 216/217). */
|
||||||
|
void pop_kid_img_blit(uint8_t image, int x, int y);
|
||||||
|
|
||||||
|
#endif /* _POP_KDRAW_H */
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* _pop_kid.h — ВНУТРЕННИЙ контракт движка Kid: pop_kid.c (резидент W1,
|
||||||
|
* интерпретатор seqtbl) ↔ pop_kboot.c (банк 8, загрузка ресурсов).
|
||||||
|
*
|
||||||
|
* Загрузка атласов Кида и страницы анимации отрабатывает РАЗ ЗА ИГРУ, и
|
||||||
|
* держать её в дефицитном W1 незачем; интерпретатор же зовут из четырёх
|
||||||
|
* банков каждый тик — он остался в резиденте. Общее между ними — только
|
||||||
|
* дескриптор страницы данных: он объявлен здесь и определён в pop_kid.c
|
||||||
|
* (данные банковых модулей всё равно линкуются в общий _DATA).
|
||||||
|
*/
|
||||||
|
#ifndef _POP_KID_H
|
||||||
|
#define _POP_KID_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Смещение данных в странице анимации; ниже 0x100 — ISR-стаб W0. */
|
||||||
|
#define KD_DATA_OFF 0x100u
|
||||||
|
|
||||||
|
extern uint8_t kdat_blk; /* EMM-блок страницы kid_data.bin */
|
||||||
|
extern uint8_t kdat_page; /* её номер (маппится в W0 на время play_seq) */
|
||||||
|
extern uint8_t kdat_ok; /* данные загружены */
|
||||||
|
|
||||||
|
#endif /* _POP_KID_H */
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
#include <gfx.h>
|
#include <gfx.h>
|
||||||
#include <sprite.h>
|
#include <sprite.h>
|
||||||
#include "pop_guard.h"
|
#include "pop_guard.h"
|
||||||
#include "pop_kid.h" /* атласы/кадр Кида, pop_sword_draw, окна Char */
|
#include "pop_kid.h"
|
||||||
|
#include "_pop_kdraw.h" /* меч + блит по id — сосед по банку 4 */ /* атласы/кадр Кида, pop_sword_draw, окна Char */
|
||||||
#include "pop_cdraw.h"
|
#include "pop_cdraw.h"
|
||||||
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char, pop_fore_set_clip */
|
#include "pop_bg.h" /* POP_YOFF, pop_fore_over_char, pop_fore_set_clip */
|
||||||
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
#include "_pop_draw.h" /* pop_onscreen_cols / pop_heal_fast */
|
||||||
|
|||||||
@@ -195,147 +195,3 @@ void pop_guard_leave(void)
|
|||||||
Guard.curr_seq,
|
Guard.curr_seq,
|
||||||
(uint8_t)(Guard.alive >= 0));
|
(uint8_t)(Guard.alive >= 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int8_t guard_col_from_x(uint8_t xpos); /* определена ниже */
|
|
||||||
|
|
||||||
/* ---- Страж ИДЁТ ЗА КИДОМ в соседнюю комнату ------------------------- *
|
|
||||||
* Порт ветки стража из exit_room (seg002:0340..0382) + follow_guard
|
|
||||||
* (seg002:039E). Своим ходом персонаж слота Guard из комнаты не уходит
|
|
||||||
* (его физика гейтится x ∈ [44,211), см. pop_guard_phys_tick), но когда
|
|
||||||
* комнату меняет КИД, движок может перенести стража следом — именно так в
|
|
||||||
* оригинале страж из комнаты 3 оказывается в комнате 2 за отступающим
|
|
||||||
* Кидом.
|
|
||||||
*
|
|
||||||
* Условия оригинала, все обязательные:
|
|
||||||
* - страж есть, ЖИВ и с ВЫНУТЫМ МЕЧОМ (то есть идёт бой);
|
|
||||||
* - переход ВБОК: вверх и вниз оригинал не пускает никогда (там условия
|
|
||||||
* `curr_row >= 0` и `curr_row < 3` истинны всегда → leave_guard);
|
|
||||||
* - в целевой комнате НЕТ своего ЖИВОГО стража (иначе их стало бы два:
|
|
||||||
* проверка `guards_tile >= 30 || guards_seq_hi != 0` — «пусто или
|
|
||||||
* труп»);
|
|
||||||
* - страж прижат к тому краю, в который ушёл Кид: x < 91 при уходе
|
|
||||||
* влево, x >= 165 при уходе вправо.
|
|
||||||
*
|
|
||||||
* side — куда ушёл КИД (0 = left, 1 = right, 2 = up, 3 = down), то есть
|
|
||||||
* roomleave_result оригинала. Возврат: 1 — страж перенесён в new_room
|
|
||||||
* (вызывающий НЕ должен звать ни pop_guard_leave, ни pop_guard_enter),
|
|
||||||
* 0 — остаётся, всё как раньше. */
|
|
||||||
uint8_t pop_guard_follow(uint8_t new_room, uint8_t side)
|
|
||||||
{
|
|
||||||
uint8_t tile, color, skill;
|
|
||||||
int8_t dir;
|
|
||||||
|
|
||||||
if (Guard.charid == 0) return 0; /* dir_56_none */
|
|
||||||
if (Guard.alive >= 0 || Guard.sword != SWORD_2_DRAWN) return 0;
|
|
||||||
if (side > 1) return 0; /* только вбок */
|
|
||||||
/* «В целевой комнате уже есть живой страж» = страж по данным есть И это
|
|
||||||
* не запомненный труп (у трупа сохранена последовательность). */
|
|
||||||
if (pop_level_guard(new_room, &tile, &dir, &color, &skill) &&
|
|
||||||
pop_guard_state_seq(new_room) == 0) return 0;
|
|
||||||
if (side == 0) { if (Guard.x >= 91) return 0; } /* Кид ушёл влево */
|
|
||||||
else { if (Guard.x < 165) return 0; } /* Кид ушёл вправо */
|
|
||||||
|
|
||||||
/* follow_guard: сначала «стража нет» в ОБЕИХ комнатах (в оригинале
|
|
||||||
* guards_tile = 0xFF). Без этого он раздвоится: в старой комнате его
|
|
||||||
* поднимут из данных при возврате, а в новой — при следующем входе. */
|
|
||||||
pop_guard_state_save(Guard.room, 30, 0, 0, 0, 0, 0);
|
|
||||||
pop_guard_state_save(new_room, 30, 0, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
/* goto_other_room (seg002:0486) для стража: комната + перенос X через
|
|
||||||
* шов (влево +140, вправо −140). Ряд при боковом переходе не меняется;
|
|
||||||
* колонку пересчитываем из новой X, как enter_guard. */
|
|
||||||
Guard.room = new_room;
|
|
||||||
Guard.x = (uint8_t)(side == 0 ? Guard.x + 140 : Guard.x - 140);
|
|
||||||
Guard.curr_col = guard_col_from_x(Guard.x);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* get_tile_div_mod_m7 (seg006:697): колонка комнаты по X персонажа —
|
|
||||||
* floor((xpos − 7 − 58) / 14), где 58 = pop_x_bump[FIRST_ONSCREEN_COLUMN]. */
|
|
||||||
static int8_t guard_col_from_x(uint8_t xpos)
|
|
||||||
{
|
|
||||||
/* Таблицей (резидентная POP_TILE_DIV: −58 уже внутри значений) — тот же
|
|
||||||
* приём, что в tile_at_kid/get_tile_div_mod. Здесь он кроет ПОЧТИ всё:
|
|
||||||
* xpos — байт, так что медленный хвост остаётся только для xpos < 7. */
|
|
||||||
if (xpos >= 7) return POP_TILE_DIV[xpos - 7];
|
|
||||||
{
|
|
||||||
int16_t x = (int16_t)xpos - 7 - pop_x_bump[FIRST_ONSCREEN_COLUMN];
|
|
||||||
int16_t col = x / TILE_SIZEX;
|
|
||||||
if (x % TILE_SIZEX < 0) --col;
|
|
||||||
return (int8_t)col;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_guard_enter(uint8_t room)
|
|
||||||
{
|
|
||||||
uint8_t tile, color, skill;
|
|
||||||
int8_t dir;
|
|
||||||
pop_guard_reset();
|
|
||||||
/* load_lev_spr (seg000:1112): tbl_guard_type == −1 — стражей на уровне
|
|
||||||
* НЕТ вообще (14/15), их графика даже не грузится. Без этой проверки
|
|
||||||
* на таком уровне полезли бы стражи из данных комнат. */
|
|
||||||
if (pop_level_guard_type(pop_current_level) < 0) return;
|
|
||||||
if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return;
|
|
||||||
/* Цвет — ДО отрисовки комнаты (как redraw_screen, seg003:255): слоты
|
|
||||||
* палитры общие на экран, и смена посреди кадра дала бы стража в новой
|
|
||||||
* палитре с полосой HP в старой. */
|
|
||||||
pop_guard_set_palette(color);
|
|
||||||
/* enter_guard (seg002:0112) + pos_guards (seg003): X считается из
|
|
||||||
* колонки тайла, а не берётся из файла. */
|
|
||||||
Guard.room = room;
|
|
||||||
Guard.curr_row = (int8_t)(tile / 10);
|
|
||||||
Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1];
|
|
||||||
Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
|
|
||||||
Guard.curr_col = (int8_t)(tile % 10);
|
|
||||||
Guard.direction = dir;
|
|
||||||
/* seg002:196: на уровне со скелетами слот поднимает СКЕЛЕТА, и он
|
|
||||||
* встаёт сразу активным — с вынутым мечом (seq_63), а не в стойке
|
|
||||||
* покоя (seq_77). Стойку ставит ветка ниже, вместе с seq трупа. */
|
|
||||||
Guard.charid = (pop_level_guard_type(pop_current_level) == 2)
|
|
||||||
? CHARID_4_SKELETON : CHARID_2_GUARD;
|
|
||||||
Guard.sword = (Guard.charid == CHARID_4_SKELETON)
|
|
||||||
? SWORD_2_DRAWN : 0; /* sword_0_sheathed */
|
|
||||||
Guard.alive = -1;
|
|
||||||
Guard.action = 1; /* actions_1_run_jump */
|
|
||||||
Guard.fall_x = Guard.fall_y = 0;
|
|
||||||
/* enter_guard (seg002:214): мастерство из данных уровня, 12 градаций;
|
|
||||||
* всё, что вне диапазона, оригинал сводит к 3. */
|
|
||||||
guard_skill = (uint8_t)(skill < NUM_GUARD_SKILLS ? skill : 3);
|
|
||||||
/* get_guard_hp (seg002:54): extrastrength[skill] + tbl_guard_hp[уровень].
|
|
||||||
* extrastrength (seg002:38) ненулевой только у skill 4 (=1). */
|
|
||||||
guardhp_max = guardhp_curr =
|
|
||||||
(uint8_t)(pop_level_guard_hp(pop_current_level) + (guard_skill == 4 ? 1 : 0));
|
|
||||||
guardhp_delta = 0;
|
|
||||||
/* enter_guard (seg002:0208). Живого поднимаем стандартной стойкой; у
|
|
||||||
* ЗАПОМНЕННОГО МЁРТВОГО берём сохранённую последовательность — он лежит
|
|
||||||
* ровно там, где лёг (порт ветки guards_seq_hi != 0). */
|
|
||||||
{
|
|
||||||
uint16_t saved_seq = pop_guard_state_seq(room);
|
|
||||||
/* У ЗАПОМНЕННОГО трупа X берём сохранённую: pos_guards (seg003:620)
|
|
||||||
* пересчитывает её из колонки только при загрузке уровня, а дальше
|
|
||||||
* ею владеет leave_guard. Иначе тело при возврате прыгает в центр
|
|
||||||
* тайла. */
|
|
||||||
if (saved_seq) Guard.x = pop_guard_state_x(room);
|
|
||||||
/* ...и КОЛОНКУ пересчитываем из X (enter_guard, seg002:180:
|
|
||||||
* `Char.curr_col = get_tile_div_mod_m7(Char.x)`). Брать её из
|
|
||||||
* тайла нельзя: leave_guard сохраняет get_tilepos(0, row), то
|
|
||||||
* есть колонку 0 всегда. У живого стража это незаметно (X там
|
|
||||||
* сам считается из колонки), а у ЗАПОМНЕННОГО ТРУПА X
|
|
||||||
* настоящая, и curr_col оставался нулём при теле, лежащем в
|
|
||||||
* колонке 2. Тайл трупа шёл в обходе раньше тайла Кида, и Кид
|
|
||||||
* рисовался ПОВЕРХ тела вместо того, чтобы пройти за ним
|
|
||||||
* (BUG-DRAWORDER-1). Колонка нужна не только отрисовке — на
|
|
||||||
* неё смотрят коллизия и check_can_guard_see_kid. */
|
|
||||||
Guard.curr_col = guard_col_from_x(Guard.x);
|
|
||||||
pop_loadshad();
|
|
||||||
if (saved_seq) Char.curr_seq = saved_seq;
|
|
||||||
else pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE);
|
|
||||||
pop_saveshad();
|
|
||||||
pop_guard_play(); /* до первого кадра */
|
|
||||||
/* enter_guard (seg002:218): кадр смерти -> страж поднят ТРУПОМ. */
|
|
||||||
if (Guard.frame == 185 || Guard.frame == 177 || Guard.frame == 178) {
|
|
||||||
Guard.alive = 1;
|
|
||||||
guardhp_curr = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* pop_guard_cold.c — переходы стража МЕЖДУ КОМНАТАМИ (банк 8): страж идёт
|
||||||
|
* за Кидом (follow_guard, seg002:039E) и вход стража в комнату
|
||||||
|
* (enter_guard, seg002:0112 + pos_guards, seg003).
|
||||||
|
*
|
||||||
|
* Обе зовутся РАЗ НА КОМНАТУ и обе — из roomtest_cold.c, который лежит в
|
||||||
|
* том же банке 8: значит в резиденте им делать нечего, а трамплин на
|
||||||
|
* вызове не появляется (объявления в pop_guard.h без __banked; условие
|
||||||
|
* корректности — оба модуля в одном банке, см. _pop_kdraw.h про ту же
|
||||||
|
* связку). Состояние стража (Guard, guard_skill, …) живёт в pop_guard.c
|
||||||
|
* и видно отсюда как обычные глобалы: данные банков — в общем _DATA.
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "pop_guard.h"
|
||||||
|
#include "pop_geom.h"
|
||||||
|
#include "pop_level.h"
|
||||||
|
#include "pop_kid.h" /* Char/play_seq — интерпретатор общий */
|
||||||
|
#include "pop_map.h"
|
||||||
|
#include "pop_state.h"
|
||||||
|
#include "pop_tile.h" /* POP_TILE_DIV — колонка по X без деления */
|
||||||
|
#include "kid_data.h"
|
||||||
|
|
||||||
|
static int8_t guard_col_from_x(uint8_t xpos); /* определена ниже */
|
||||||
|
|
||||||
|
/* ---- Страж ИДЁТ ЗА КИДОМ в соседнюю комнату ------------------------- *
|
||||||
|
* Порт ветки стража из exit_room (seg002:0340..0382) + follow_guard
|
||||||
|
* (seg002:039E). Своим ходом персонаж слота Guard из комнаты не уходит
|
||||||
|
* (его физика гейтится x ∈ [44,211), см. pop_guard_phys_tick), но когда
|
||||||
|
* комнату меняет КИД, движок может перенести стража следом — именно так в
|
||||||
|
* оригинале страж из комнаты 3 оказывается в комнате 2 за отступающим
|
||||||
|
* Кидом.
|
||||||
|
*
|
||||||
|
* Условия оригинала, все обязательные:
|
||||||
|
* - страж есть, ЖИВ и с ВЫНУТЫМ МЕЧОМ (то есть идёт бой);
|
||||||
|
* - переход ВБОК: вверх и вниз оригинал не пускает никогда (там условия
|
||||||
|
* `curr_row >= 0` и `curr_row < 3` истинны всегда → leave_guard);
|
||||||
|
* - в целевой комнате НЕТ своего ЖИВОГО стража (иначе их стало бы два:
|
||||||
|
* проверка `guards_tile >= 30 || guards_seq_hi != 0` — «пусто или
|
||||||
|
* труп»);
|
||||||
|
* - страж прижат к тому краю, в который ушёл Кид: x < 91 при уходе
|
||||||
|
* влево, x >= 165 при уходе вправо.
|
||||||
|
*
|
||||||
|
* side — куда ушёл КИД (0 = left, 1 = right, 2 = up, 3 = down), то есть
|
||||||
|
* roomleave_result оригинала. Возврат: 1 — страж перенесён в new_room
|
||||||
|
* (вызывающий НЕ должен звать ни pop_guard_leave, ни pop_guard_enter),
|
||||||
|
* 0 — остаётся, всё как раньше. */
|
||||||
|
uint8_t pop_guard_follow(uint8_t new_room, uint8_t side)
|
||||||
|
{
|
||||||
|
uint8_t tile, color, skill;
|
||||||
|
int8_t dir;
|
||||||
|
|
||||||
|
if (Guard.charid == 0) return 0; /* dir_56_none */
|
||||||
|
if (Guard.alive >= 0 || Guard.sword != SWORD_2_DRAWN) return 0;
|
||||||
|
if (side > 1) return 0; /* только вбок */
|
||||||
|
/* «В целевой комнате уже есть живой страж» = страж по данным есть И это
|
||||||
|
* не запомненный труп (у трупа сохранена последовательность). */
|
||||||
|
if (pop_level_guard(new_room, &tile, &dir, &color, &skill) &&
|
||||||
|
pop_guard_state_seq(new_room) == 0) return 0;
|
||||||
|
if (side == 0) { if (Guard.x >= 91) return 0; } /* Кид ушёл влево */
|
||||||
|
else { if (Guard.x < 165) return 0; } /* Кид ушёл вправо */
|
||||||
|
|
||||||
|
/* follow_guard: сначала «стража нет» в ОБЕИХ комнатах (в оригинале
|
||||||
|
* guards_tile = 0xFF). Без этого он раздвоится: в старой комнате его
|
||||||
|
* поднимут из данных при возврате, а в новой — при следующем входе. */
|
||||||
|
pop_guard_state_save(Guard.room, 30, 0, 0, 0, 0, 0);
|
||||||
|
pop_guard_state_save(new_room, 30, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
/* goto_other_room (seg002:0486) для стража: комната + перенос X через
|
||||||
|
* шов (влево +140, вправо −140). Ряд при боковом переходе не меняется;
|
||||||
|
* колонку пересчитываем из новой X, как enter_guard. */
|
||||||
|
Guard.room = new_room;
|
||||||
|
Guard.x = (uint8_t)(side == 0 ? Guard.x + 140 : Guard.x - 140);
|
||||||
|
Guard.curr_col = guard_col_from_x(Guard.x);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get_tile_div_mod_m7 (seg006:697): колонка комнаты по X персонажа —
|
||||||
|
* floor((xpos − 7 − 58) / 14), где 58 = pop_x_bump[FIRST_ONSCREEN_COLUMN]. */
|
||||||
|
static int8_t guard_col_from_x(uint8_t xpos)
|
||||||
|
{
|
||||||
|
/* Таблицей (резидентная POP_TILE_DIV: −58 уже внутри значений) — тот же
|
||||||
|
* приём, что в tile_at_kid/get_tile_div_mod. Здесь он кроет ПОЧТИ всё:
|
||||||
|
* xpos — байт, так что медленный хвост остаётся только для xpos < 7. */
|
||||||
|
if (xpos >= 7) return POP_TILE_DIV[xpos - 7];
|
||||||
|
{
|
||||||
|
int16_t x = (int16_t)xpos - 7 - pop_x_bump[FIRST_ONSCREEN_COLUMN];
|
||||||
|
int16_t col = x / TILE_SIZEX;
|
||||||
|
if (x % TILE_SIZEX < 0) --col;
|
||||||
|
return (int8_t)col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_guard_enter(uint8_t room)
|
||||||
|
{
|
||||||
|
uint8_t tile, color, skill;
|
||||||
|
int8_t dir;
|
||||||
|
pop_guard_reset();
|
||||||
|
/* load_lev_spr (seg000:1112): tbl_guard_type == −1 — стражей на уровне
|
||||||
|
* НЕТ вообще (14/15), их графика даже не грузится. Без этой проверки
|
||||||
|
* на таком уровне полезли бы стражи из данных комнат. */
|
||||||
|
if (pop_level_guard_type(pop_current_level) < 0) return;
|
||||||
|
if (!pop_level_guard(room, &tile, &dir, &color, &skill)) return;
|
||||||
|
/* Цвет — ДО отрисовки комнаты (как redraw_screen, seg003:255): слоты
|
||||||
|
* палитры общие на экран, и смена посреди кадра дала бы стража в новой
|
||||||
|
* палитре с полосой HP в старой. */
|
||||||
|
pop_guard_set_palette(color);
|
||||||
|
/* enter_guard (seg002:0112) + pos_guards (seg003): X считается из
|
||||||
|
* колонки тайла, а не берётся из файла. */
|
||||||
|
Guard.room = room;
|
||||||
|
Guard.curr_row = (int8_t)(tile / 10);
|
||||||
|
Guard.y = (uint8_t)pop_y_land[Guard.curr_row + 1];
|
||||||
|
Guard.x = (uint8_t)(pop_x_bump[(tile % 10) + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX);
|
||||||
|
Guard.curr_col = (int8_t)(tile % 10);
|
||||||
|
Guard.direction = dir;
|
||||||
|
/* seg002:196: на уровне со скелетами слот поднимает СКЕЛЕТА, и он
|
||||||
|
* встаёт сразу активным — с вынутым мечом (seq_63), а не в стойке
|
||||||
|
* покоя (seq_77). Стойку ставит ветка ниже, вместе с seq трупа. */
|
||||||
|
Guard.charid = (pop_level_guard_type(pop_current_level) == 2)
|
||||||
|
? CHARID_4_SKELETON : CHARID_2_GUARD;
|
||||||
|
Guard.sword = (Guard.charid == CHARID_4_SKELETON)
|
||||||
|
? SWORD_2_DRAWN : 0; /* sword_0_sheathed */
|
||||||
|
Guard.alive = -1;
|
||||||
|
Guard.action = 1; /* actions_1_run_jump */
|
||||||
|
Guard.fall_x = Guard.fall_y = 0;
|
||||||
|
/* enter_guard (seg002:214): мастерство из данных уровня, 12 градаций;
|
||||||
|
* всё, что вне диапазона, оригинал сводит к 3. */
|
||||||
|
guard_skill = (uint8_t)(skill < NUM_GUARD_SKILLS ? skill : 3);
|
||||||
|
/* get_guard_hp (seg002:54): extrastrength[skill] + tbl_guard_hp[уровень].
|
||||||
|
* extrastrength (seg002:38) ненулевой только у skill 4 (=1). */
|
||||||
|
guardhp_max = guardhp_curr =
|
||||||
|
(uint8_t)(pop_level_guard_hp(pop_current_level) + (guard_skill == 4 ? 1 : 0));
|
||||||
|
guardhp_delta = 0;
|
||||||
|
/* enter_guard (seg002:0208). Живого поднимаем стандартной стойкой; у
|
||||||
|
* ЗАПОМНЕННОГО МЁРТВОГО берём сохранённую последовательность — он лежит
|
||||||
|
* ровно там, где лёг (порт ветки guards_seq_hi != 0). */
|
||||||
|
{
|
||||||
|
uint16_t saved_seq = pop_guard_state_seq(room);
|
||||||
|
/* У ЗАПОМНЕННОГО трупа X берём сохранённую: pos_guards (seg003:620)
|
||||||
|
* пересчитывает её из колонки только при загрузке уровня, а дальше
|
||||||
|
* ею владеет leave_guard. Иначе тело при возврате прыгает в центр
|
||||||
|
* тайла. */
|
||||||
|
if (saved_seq) Guard.x = pop_guard_state_x(room);
|
||||||
|
/* ...и КОЛОНКУ пересчитываем из X (enter_guard, seg002:180:
|
||||||
|
* `Char.curr_col = get_tile_div_mod_m7(Char.x)`). Брать её из
|
||||||
|
* тайла нельзя: leave_guard сохраняет get_tilepos(0, row), то
|
||||||
|
* есть колонку 0 всегда. У живого стража это незаметно (X там
|
||||||
|
* сам считается из колонки), а у ЗАПОМНЕННОГО ТРУПА X
|
||||||
|
* настоящая, и curr_col оставался нулём при теле, лежащем в
|
||||||
|
* колонке 2. Тайл трупа шёл в обходе раньше тайла Кида, и Кид
|
||||||
|
* рисовался ПОВЕРХ тела вместо того, чтобы пройти за ним
|
||||||
|
* (BUG-DRAWORDER-1). Колонка нужна не только отрисовке — на
|
||||||
|
* неё смотрят коллизия и check_can_guard_see_kid. */
|
||||||
|
Guard.curr_col = guard_col_from_x(Guard.x);
|
||||||
|
pop_loadshad();
|
||||||
|
if (saved_seq) Char.curr_seq = saved_seq;
|
||||||
|
else pop_char_set_seq(SEQ_77_GUARD_STAND_INACTIVE);
|
||||||
|
pop_saveshad();
|
||||||
|
pop_guard_play(); /* до первого кадра */
|
||||||
|
/* enter_guard (seg002:218): кадр смерти -> страж поднят ТРУПОМ. */
|
||||||
|
if (Guard.frame == 185 || Guard.frame == 177 || Guard.frame == 178) {
|
||||||
|
Guard.alive = 1;
|
||||||
|
guardhp_curr = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* pop_kboot.c — ЗАГРУЗКА ресурсов Кида: атласы chtab_2 (kid0..27.atl) и
|
||||||
|
* страница таблиц анимации (kid_data.bin). БАНК 8.
|
||||||
|
*
|
||||||
|
* Отрабатывает раз за игру (и на выходе — освобождение), поэтому места в
|
||||||
|
* резиденте W1 не занимает: контракт с интерпретатором — _pop_kid.h, там
|
||||||
|
* же дескриптор страницы данных.
|
||||||
|
*/
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sprinter.h>
|
||||||
|
#include <sprinter_mem.h>
|
||||||
|
#include <sprite.h>
|
||||||
|
#include "pop_kid.h"
|
||||||
|
#include "_pop_kid.h"
|
||||||
|
#include "kid_data.h"
|
||||||
|
|
||||||
|
/* ISR-стаб W0-страницы (тот же приём, что atlas_load): при прерывании с
|
||||||
|
* замапленной в W0 страницей CPU прыгает на 0x0038 → там обязан быть JP на
|
||||||
|
* _gfx_w0_isr (восстановит окно). */
|
||||||
|
extern void _gfx_w0_isr(void);
|
||||||
|
|
||||||
|
#define KID_MAXPAGES 28
|
||||||
|
|
||||||
|
int pop_kid_load(uint8_t npages) __banked
|
||||||
|
{
|
||||||
|
static const char *const names[KID_MAXPAGES] = {
|
||||||
|
"KID\\kid0.atl","KID\\kid1.atl","KID\\kid2.atl","KID\\kid3.atl","KID\\kid4.atl","KID\\kid5.atl",
|
||||||
|
"KID\\kid6.atl","KID\\kid7.atl","KID\\kid8.atl","KID\\kid9.atl","KID\\kid10.atl","KID\\kid11.atl",
|
||||||
|
"KID\\kid12.atl","KID\\kid13.atl","KID\\kid14.atl","KID\\kid15.atl","KID\\kid16.atl","KID\\kid17.atl",
|
||||||
|
"KID\\kid18.atl","KID\\kid19.atl","KID\\kid20.atl","KID\\kid21.atl","KID\\kid22.atl","KID\\kid23.atl",
|
||||||
|
"KID\\kid24.atl","KID\\kid25.atl","KID\\kid26.atl","KID\\kid27.atl"
|
||||||
|
};
|
||||||
|
uint8_t i;
|
||||||
|
if (npages > KID_MAXPAGES) npages = KID_MAXPAGES;
|
||||||
|
for (i = 0; i < npages; i++)
|
||||||
|
if (atlas_load(&kidp[i], names[i]) != 0)
|
||||||
|
return -1;
|
||||||
|
kid_npages = npages;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_kid_free(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < kid_npages; i++)
|
||||||
|
atlas_free(&kidp[i]);
|
||||||
|
kid_npages = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Таблица кадров (241*5 Б) и seqtbl (2310 Б) занимали 3.5 КБ в _CODE окна
|
||||||
|
* W1/W2 — самый дефицитный ресурс. Теперь они лежат в отдельной EMM-
|
||||||
|
* странице, которая маппится в W0 РОВНО на время play_seq (раз за тик).
|
||||||
|
* Страница адресуется как у pop_level: данные с 0x100, ниже — ISR-стаб. */
|
||||||
|
int pop_kid_data_load(const char *path) __banked
|
||||||
|
{
|
||||||
|
uint8_t *pg = (uint8_t *)0xC000; /* страница мапится в W3 на время чтения */
|
||||||
|
uint8_t saved_w3, blk;
|
||||||
|
uint16_t stub;
|
||||||
|
int fd, n;
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY);
|
||||||
|
if (fd < 0) return -1;
|
||||||
|
blk = mem_alloc_pages(1);
|
||||||
|
if (!blk) { close(fd); return -1; }
|
||||||
|
kdat_blk = blk;
|
||||||
|
kdat_page = mem_get_page(blk, 0);
|
||||||
|
|
||||||
|
saved_w3 = _io_page_w3;
|
||||||
|
sprinter_page_w3(kdat_page);
|
||||||
|
n = read(fd, pg + KD_DATA_OFF, 16384 - KD_DATA_OFF);
|
||||||
|
if (n >= (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) {
|
||||||
|
stub = (uint16_t)&_gfx_w0_isr; /* ISR-стаб W0-страницы (как pop_level) */
|
||||||
|
pg[0x38] = 0xC3;
|
||||||
|
pg[0x39] = (uint8_t)(stub & 0xFF);
|
||||||
|
pg[0x3A] = (uint8_t)(stub >> 8);
|
||||||
|
pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN */
|
||||||
|
}
|
||||||
|
sprinter_page_w3(saved_w3);
|
||||||
|
close(fd);
|
||||||
|
if (n < (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) { mem_free_block(blk); return -1; }
|
||||||
|
kdat_ok = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_kid_data_free(void) __banked
|
||||||
|
{
|
||||||
|
if (kdat_ok) { mem_free_block(kdat_blk); kdat_ok = 0; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* pop_kdraw.c — отрисовка ИЗ АТЛАСОВ КИДА, которая нужна только слою
|
||||||
|
* персонажей: клинок в руке (add_sword_to_objtable, seg006:1798) и блит
|
||||||
|
* спрайта chtab_2 по его id (полоса HP). БАНК 4, вместе с pop_cdraw.c.
|
||||||
|
*
|
||||||
|
* Почему здесь. Оба вызова приходят РОВНО из pop_cdraw.c и больше ниоткуда
|
||||||
|
* (проверено грепом), а лежали они в pop_kid.c — то есть в резиденте W1,
|
||||||
|
* съедая ~1.2 КБ дефицитного окна ради кода, который зовёт сосед по банку.
|
||||||
|
* Раз модуль в том же банке 4 — вызовы прямые, без трамплина: объявления в
|
||||||
|
* _pop_kdraw.h НЕ помечены __banked (см. там же условие корректности).
|
||||||
|
*
|
||||||
|
* Загрузка атласа меча (pop_sword_load) осталась публичной и __banked: её
|
||||||
|
* зовёт инициализация, один раз за игру.
|
||||||
|
*/
|
||||||
|
#include <graphics.h>
|
||||||
|
#include <gfx.h>
|
||||||
|
#include <sprite.h>
|
||||||
|
#include "pop_kid.h"
|
||||||
|
#include "_pop_kdraw.h"
|
||||||
|
#include "pop_guard.h" /* CHARID_* — клинок есть и у стража */
|
||||||
|
#include "pop_bg.h" /* POP_YOFF — вертикальное центрирование */
|
||||||
|
#include "_pop_draw.h" /* pop_onscreen_cols */
|
||||||
|
#include "kid_data.h"
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
int pop_sword_load(void) __banked
|
||||||
|
{
|
||||||
|
if (atlas_load(&swordp, "KID\\sword.atl") != 0) return -1;
|
||||||
|
sword_ok = 1;
|
||||||
|
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->charid == CHARID_4_SKELETON) &&
|
||||||
|
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;
|
||||||
|
/* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507): клинок
|
||||||
|
* обрезается по нему так же, как сам персонаж. Без этого у падающего
|
||||||
|
* в пропасть скелета меч оставался виден НИЖЕ поля, на полосе HP. */
|
||||||
|
{
|
||||||
|
int skip = 0, rows = (int)h;
|
||||||
|
if (top < 0) { skip = -top; rows -= skip; top = 0; }
|
||||||
|
if (top + rows > 192) rows = 192 - top;
|
||||||
|
if (rows <= 0) return 0;
|
||||||
|
gfx_set_bank(GFX_BANK_SPRITE);
|
||||||
|
/* Клинок почти всегда целиком на экране — noclip-путь (см. gfx.h:
|
||||||
|
* подготовка клипающего варианта стоит ~5.6 К тактов на вызов). */
|
||||||
|
if (pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
|
||||||
|
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, simg, flip,
|
||||||
|
(uint8_t)skip, (uint8_t)rows);
|
||||||
|
else
|
||||||
|
gfx_blit_cols_part(bx, top + POP_YOFF, simg, flip, skip, rows);
|
||||||
|
gfx_set_bank(GFX_BANK_NORMAL);
|
||||||
|
*rx = bx; *ry = top; *rw = w; *rh = (uint16_t)rows;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Блит спрайта chtab_2 (атласы Kid) по ЕГО id, левым верхним углом в (x,y).
|
||||||
|
* Нужен полосе HP: она рисует id 216/217 из тех же атласов, что и кадры
|
||||||
|
* Кида, а держать вторую копию раскладки в pop_cdraw ни к чему. */
|
||||||
|
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 выдал бы марку транспонированной.
|
||||||
|
* noclip БЕЗ проверки (CLIP-1): единственный вызывающий — полоса HP
|
||||||
|
* (pop_cdraw.c), у неё координаты фиксированные и заведомо на экране. */
|
||||||
|
if (w && h) gfx_blit_cols_part_noclip(x, y, img, 0, 0, 0);
|
||||||
|
gfx_w0_unmap();
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "pop_map.h" /* pop_clip_char_top (clip_char) */
|
#include "pop_map.h" /* pop_clip_char_top (clip_char) */
|
||||||
#include "pop_geom.h"
|
#include "pop_geom.h"
|
||||||
#include "kid_data.h"
|
#include "kid_data.h"
|
||||||
|
#include "_pop_kid.h" /* дескриптор страницы данных (её грузит банк 8) */
|
||||||
|
|
||||||
pop_char_t Kid;
|
pop_char_t Kid;
|
||||||
|
|
||||||
@@ -48,130 +49,6 @@ uint8_t kid_npages;
|
|||||||
* kid_set_seq / pop_char_set_seq. */
|
* kid_set_seq / pop_char_set_seq. */
|
||||||
static const uint16_t kid_seq_off[KID_NSEQ] = KID_SEQ_OFF_INIT;
|
static const uint16_t kid_seq_off[KID_NSEQ] = KID_SEQ_OFF_INIT;
|
||||||
|
|
||||||
/* 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;
|
|
||||||
|
|
||||||
int pop_sword_load(void)
|
|
||||||
{
|
|
||||||
if (atlas_load(&swordp, "KID\\sword.atl") != 0) return -1;
|
|
||||||
sword_ok = 1;
|
|
||||||
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->charid == CHARID_4_SKELETON) &&
|
|
||||||
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;
|
|
||||||
/* Поле комнаты — 192 строки (reset_obj_clip, seg006:0507): клинок
|
|
||||||
* обрезается по нему так же, как сам персонаж. Без этого у падающего
|
|
||||||
* в пропасть скелета меч оставался виден НИЖЕ поля, на полосе HP. */
|
|
||||||
{
|
|
||||||
int skip = 0, rows = (int)h;
|
|
||||||
if (top < 0) { skip = -top; rows -= skip; top = 0; }
|
|
||||||
if (top + rows > 192) rows = 192 - top;
|
|
||||||
if (rows <= 0) return 0;
|
|
||||||
gfx_set_bank(GFX_BANK_SPRITE);
|
|
||||||
/* Клинок почти всегда целиком на экране — noclip-путь (см. gfx.h:
|
|
||||||
* подготовка клипающего варианта стоит ~5.6 К тактов на вызов). */
|
|
||||||
if (pop_onscreen_cols(bx, top + POP_YOFF, w, (uint16_t)rows))
|
|
||||||
gfx_blit_cols_part_noclip(bx, top + POP_YOFF, simg, flip,
|
|
||||||
(uint8_t)skip, (uint8_t)rows);
|
|
||||||
else
|
|
||||||
gfx_blit_cols_part(bx, top + POP_YOFF, simg, flip, skip, rows);
|
|
||||||
gfx_set_bank(GFX_BANK_NORMAL);
|
|
||||||
*rx = bx; *ry = top; *rw = w; *rh = (uint16_t)rows;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Блит спрайта chtab_2 (атласы Kid) по ЕГО id, левым верхним углом в (x,y).
|
|
||||||
* Нужен полосе HP: она рисует id 216/217 из тех же атласов, что и кадры
|
|
||||||
* Кида, а держать вторую копию раскладки в pop_cdraw ни к чему. */
|
|
||||||
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 выдал бы марку транспонированной.
|
|
||||||
* noclip БЕЗ проверки (CLIP-1): единственный вызывающий — полоса HP
|
|
||||||
* (pop_cdraw.c), у неё координаты фиксированные и заведомо на экране. */
|
|
||||||
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] = {
|
|
||||||
"KID\\kid0.atl","KID\\kid1.atl","KID\\kid2.atl","KID\\kid3.atl","KID\\kid4.atl","KID\\kid5.atl",
|
|
||||||
"KID\\kid6.atl","KID\\kid7.atl","KID\\kid8.atl","KID\\kid9.atl","KID\\kid10.atl","KID\\kid11.atl",
|
|
||||||
"KID\\kid12.atl","KID\\kid13.atl","KID\\kid14.atl","KID\\kid15.atl","KID\\kid16.atl","KID\\kid17.atl",
|
|
||||||
"KID\\kid18.atl","KID\\kid19.atl","KID\\kid20.atl","KID\\kid21.atl","KID\\kid22.atl","KID\\kid23.atl",
|
|
||||||
"KID\\kid24.atl","KID\\kid25.atl","KID\\kid26.atl","KID\\kid27.atl"
|
|
||||||
};
|
|
||||||
uint8_t i;
|
|
||||||
if (npages > KID_MAXPAGES) npages = KID_MAXPAGES;
|
|
||||||
for (i = 0; i < npages; i++)
|
|
||||||
if (atlas_load(&kidp[i], names[i]) != 0)
|
|
||||||
return -1;
|
|
||||||
kid_npages = npages;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_kid_free(void)
|
|
||||||
{
|
|
||||||
uint8_t i;
|
|
||||||
for (i = 0; i < kid_npages; i++)
|
|
||||||
atlas_free(&kidp[i]);
|
|
||||||
kid_npages = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Данные анимации в EMM-странице (kid_data.bin) ------------------ *
|
/* ---- Данные анимации в EMM-странице (kid_data.bin) ------------------ *
|
||||||
* Таблица кадров (241*5 Б) и seqtbl (2310 Б) занимали 3.5 КБ в _CODE окна
|
* Таблица кадров (241*5 Б) и seqtbl (2310 Б) занимали 3.5 КБ в _CODE окна
|
||||||
* W1/W2 — самый дефицитный ресурс. Теперь они лежат в отдельной EMM-
|
* W1/W2 — самый дефицитный ресурс. Теперь они лежат в отдельной EMM-
|
||||||
@@ -182,7 +59,6 @@ void pop_kid_free(void)
|
|||||||
* Страница адресуется как у pop_level: данные с 0x100, ниже — ISR-стаб. */
|
* Страница адресуется как у pop_level: данные с 0x100, ниже — ISR-стаб. */
|
||||||
extern void _gfx_w0_isr(void);
|
extern void _gfx_w0_isr(void);
|
||||||
|
|
||||||
#define KD_DATA_OFF 0x100u
|
|
||||||
/* Адрес кадра i в замапленной странице. Считаем В uint16_t и кастуем ОДИН
|
/* Адрес кадра i в замапленной странице. Считаем В uint16_t и кастуем ОДИН
|
||||||
* раз: запись `(const uint8_t *)CONST + (uint16_t)i * 5u` SDCC 4.5 собирает
|
* раз: запись `(const uint8_t *)CONST + (uint16_t)i * 5u` SDCC 4.5 собирает
|
||||||
* НЕВЕРНО — умножение делает в 16 битах (add hl,hl / add hl,bc), а потом
|
* НЕВЕРНО — умножение делает в 16 битах (add hl,hl / add hl,bc), а потом
|
||||||
@@ -214,7 +90,9 @@ static const uint8_t *kd_gframe_ptr(uint8_t i)
|
|||||||
* страницей данных — то есть внутри play_seq. */
|
* страницей данных — то есть внутри play_seq. */
|
||||||
#define SEQ(cs) KD_SEQ(cs)
|
#define SEQ(cs) KD_SEQ(cs)
|
||||||
|
|
||||||
static uint8_t kdat_blk, kdat_page, kdat_ok;
|
/* Страница данных анимации: заполняет ЗАГРУЗЧИК (pop_kboot.c, банк 8),
|
||||||
|
* читает интерпретатор здесь. Не static — контракт в _pop_kid.h. */
|
||||||
|
uint8_t kdat_blk, kdat_page, kdat_ok;
|
||||||
|
|
||||||
/* Текущий кадр (порт глобала cur_frame, seg006): 5 байт вместо таблицы.
|
/* Текущий кадр (порт глобала cur_frame, seg006): 5 байт вместо таблицы.
|
||||||
* Заполняется load_frame для АКТИВНОГО персонажа (Char), поэтому на
|
* Заполняется load_frame для АКТИВНОГО персонажа (Char), поэтому на
|
||||||
@@ -273,42 +151,6 @@ int16_t pop_char_opp_dist(void)
|
|||||||
return distance;
|
return distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pop_kid_data_load(const char *path)
|
|
||||||
{
|
|
||||||
uint8_t *pg = (uint8_t *)0xC000; /* страница мапится в W3 на время чтения */
|
|
||||||
uint8_t saved_w3, blk;
|
|
||||||
uint16_t stub;
|
|
||||||
int fd, n;
|
|
||||||
|
|
||||||
fd = open(path, O_RDONLY);
|
|
||||||
if (fd < 0) return -1;
|
|
||||||
blk = mem_alloc_pages(1);
|
|
||||||
if (!blk) { close(fd); return -1; }
|
|
||||||
kdat_blk = blk;
|
|
||||||
kdat_page = mem_get_page(blk, 0);
|
|
||||||
|
|
||||||
saved_w3 = _io_page_w3;
|
|
||||||
sprinter_page_w3(kdat_page);
|
|
||||||
n = read(fd, pg + KD_DATA_OFF, 16384 - KD_DATA_OFF);
|
|
||||||
if (n >= (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) {
|
|
||||||
stub = (uint16_t)&_gfx_w0_isr; /* ISR-стаб W0-страницы (как pop_level) */
|
|
||||||
pg[0x38] = 0xC3;
|
|
||||||
pg[0x39] = (uint8_t)(stub & 0xFF);
|
|
||||||
pg[0x3A] = (uint8_t)(stub >> 8);
|
|
||||||
pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN */
|
|
||||||
}
|
|
||||||
sprinter_page_w3(saved_w3);
|
|
||||||
close(fd);
|
|
||||||
if (n < (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) { mem_free_block(blk); return -1; }
|
|
||||||
kdat_ok = 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pop_kid_data_free(void)
|
|
||||||
{
|
|
||||||
if (kdat_ok) { mem_free_block(kdat_blk); kdat_ok = 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Прочитать кадр из ЛЮБОЙ таблицы страницы данных (у Kid и у стража они
|
/* Прочитать кадр из ЛЮБОЙ таблицы страницы данных (у Kid и у стража они
|
||||||
* разные: KID_BIN_FRAMES_OFF / KID_BIN_GFRAMES_OFF). Сама маппит страницу,
|
* разные: KID_BIN_FRAMES_OFF / KID_BIN_GFRAMES_OFF). Сама маппит страницу,
|
||||||
* поэтому годится для вызова извне (pop_guard) — но НЕ в горячем цикле. */
|
* поэтому годится для вызова извне (pop_guard) — но НЕ в горячем цикле. */
|
||||||
|
|||||||
@@ -29,35 +29,30 @@ extern uint8_t pop_next_level;
|
|||||||
|
|
||||||
/* Загрузить npages атласов Kid (kid0..kid{npages-1}.atl). ДО initgraph.
|
/* Загрузить npages атласов Kid (kid0..kid{npages-1}.atl). ДО initgraph.
|
||||||
* 0 — OK, -1 — ошибка. */
|
* 0 — OK, -1 — ошибка. */
|
||||||
int pop_kid_load(uint8_t npages);
|
int pop_kid_load(uint8_t npages) __banked;
|
||||||
|
|
||||||
/* Данные анимации (таблица кадров + seqtbl) — отдельная EMM-страница
|
/* Данные анимации (таблица кадров + seqtbl) — отдельная EMM-страница
|
||||||
* (kid_data.bin): в _CODE окна W1/W2 они занимали 3.5 КБ. Грузить ДО
|
* (kid_data.bin): в _CODE окна W1/W2 они занимали 3.5 КБ. Грузить ДО
|
||||||
* первого kid_init/play_seq. 0 — OK, -1 — ошибка (без них Kid не оживёт).
|
* первого kid_init/play_seq. 0 — OK, -1 — ошибка (без них Kid не оживёт).
|
||||||
* Страница маппится в W0 внутри play_seq, раз за тик. */
|
* Страница маппится в W0 внутри play_seq, раз за тик. */
|
||||||
int pop_kid_data_load(const char *path);
|
int pop_kid_data_load(const char *path) __banked;
|
||||||
|
|
||||||
/* Прочитать кадр из таблицы страницы данных: tbl_off — KID_BIN_FRAMES_OFF
|
/* Прочитать кадр из таблицы страницы данных: tbl_off — KID_BIN_FRAMES_OFF
|
||||||
* (Kid) или KID_BIN_GFRAMES_OFF (страж, своя таблица frame_tbl_guard).
|
* (Kid) или KID_BIN_GFRAMES_OFF (страж, своя таблица frame_tbl_guard).
|
||||||
* Маппит страницу сама; для горячего цикла есть cur_frame. */
|
* Маппит страницу сама; для горячего цикла есть cur_frame. */
|
||||||
void pop_kid_data_frame(uint16_t tbl_off, uint8_t idx, uint8_t *out5);
|
void pop_kid_data_frame(uint16_t tbl_off, uint8_t idx, uint8_t *out5);
|
||||||
void pop_kid_data_free(void);
|
void pop_kid_data_free(void) __banked;
|
||||||
|
|
||||||
/* Атлас chtab_0 «меч в руке» (sword.atl) — спрайт клинка поверх Kid в кадрах
|
/* Атлас chtab_0 «меч в руке» (sword.atl) — спрайт клинка поверх Kid в кадрах
|
||||||
* подъёма меча/убирания в ножны (add_sword_to_objtable, seg006:1798).
|
* подъёма меча/убирания в ножны (add_sword_to_objtable, seg006:1798).
|
||||||
* Грузить ДО initgraph, как и страницы Kid. 0 — OK, -1 — ошибка (тогда
|
* Грузить ДО initgraph, как и страницы Kid. 0 — OK, -1 — ошибка (тогда
|
||||||
* кадры рисуются без клинка). */
|
* кадры рисуются без клинка). */
|
||||||
int pop_sword_load(void);
|
int pop_sword_load(void) __banked;
|
||||||
void pop_kid_free(void);
|
void pop_kid_free(void) __banked;
|
||||||
|
|
||||||
/* Нарисовать клинок chtab_0 поверх персонажа (порт add_sword_to_objtable,
|
/* Сама отрисовка клинка (pop_sword_draw) и блит спрайта chtab_2 по id
|
||||||
* seg006:1798) — общая точка для Кида и стража: меч в оригинале ОДИН на
|
* (pop_kid_img_blit) переехали в pop_kdraw.c — БАНК 4, к единственному
|
||||||
* всех. obj_x/obj_y — та же опорная точка, что у спрайта персонажа.
|
* вызывающему (pop_cdraw.c). Их объявления — в _pop_kdraw.h. */
|
||||||
* Возвращает 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,
|
/* Инициализировать Kid: последовательность seq_id, клетка col/row,
|
||||||
* направление dir (-1/0). Ставит x/y по x_bump/y_land. */
|
* направление dir (-1/0). Ставит x/y по x_bump/y_land. */
|
||||||
@@ -125,10 +120,6 @@ uint8_t kid_cur_flags(void);
|
|||||||
* точка для pop_ctrl/pop_map (иначе каждый дублировал бы kid_seqtbl ~2.3КБ). */
|
* точка для pop_ctrl/pop_map (иначе каждый дублировал бы kid_seqtbl ~2.3КБ). */
|
||||||
void kid_set_seq(uint8_t seq_id);
|
void kid_set_seq(uint8_t seq_id);
|
||||||
|
|
||||||
/* Блит спрайта chtab_2 по id, левым верхним углом в (x,y) — для полосы HP
|
|
||||||
* (id 216 полное деление / 217 пустое). */
|
|
||||||
void pop_kid_img_blit(uint8_t image, int x, int y);
|
|
||||||
|
|
||||||
/* Атласы Кида (chtab_2) и его текущий кадр — набор слота POP_CH_KID общей
|
/* Атласы Кида (chtab_2) и его текущий кадр — набор слота POP_CH_KID общей
|
||||||
* отрисовки персонажей (pop_cdraw.c). Наружу отданы как данные, а не через
|
* отрисовки персонажей (pop_cdraw.c). Наружу отданы как данные, а не через
|
||||||
* аксессоры: читаются раз в кадр из другого банка, где вызов стоил бы
|
* аксессоры: читаются раз в кадр из другого банка, где вызов стоил бы
|
||||||
|
|||||||
@@ -114,4 +114,11 @@
|
|||||||
* В оригинале это условие `current_level == 15` прямо в proc_get_object. */
|
* В оригинале это условие `current_level == 15` прямо в proc_get_object. */
|
||||||
#define POP_POTIONS_LEVEL 15
|
#define POP_POTIONS_LEVEL 15
|
||||||
|
|
||||||
|
/* С какого уровня начинается игра (-DFIRST_LEVEL=N при сборке). Живёт
|
||||||
|
* здесь, а не в roomtest.c: старт уровня уехал в холодную половину цикла
|
||||||
|
* (roomtest_cold.c, банк 8), и константа нужна обеим. */
|
||||||
|
#ifndef FIRST_LEVEL
|
||||||
|
#define FIRST_LEVEL 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* POP_TUNE_H */
|
#endif /* POP_TUNE_H */
|
||||||
|
|||||||
@@ -105,10 +105,6 @@ static void kbd_idle(void)
|
|||||||
#define ROOM_1 1
|
#define ROOM_1 1
|
||||||
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
|
#define LINK_DOWN 3 /* сторона links: 0=L,1=R,2=U,3=D */
|
||||||
|
|
||||||
#ifndef FIRST_LEVEL
|
|
||||||
#define FIRST_LEVEL 1 /* с какого уровня начинается игра (-DFIRST_LEVEL=N) */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
|
#define RESPAWN_DELAY 400 /* авто-респавн (фолбэк), если не нажали ↑ */
|
||||||
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
|
#define RESPAWN_SETTLE 20 /* тиков до приёма ↑ (труп успел лечь) */
|
||||||
|
|
||||||
@@ -228,58 +224,9 @@ int main(void)
|
|||||||
uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0, nlvl_prev = 0;
|
uint8_t cheat_prev = 0, immo_prev = 0, sword_prev = 0, nlvl_prev = 0;
|
||||||
uint8_t xdec_prev = 0, xinc_prev = 0, upsd_prev = 0;
|
uint8_t xdec_prev = 0, xinc_prev = 0, upsd_prev = 0;
|
||||||
|
|
||||||
/* Набор фона — по типу СТАРТОВОГО уровня. Атласы грузятся ДО initgraph
|
/* Загрузка ресурсов, палитра, первый экран — В БАНКЕ (roomtest_cold.c):
|
||||||
* (тот снимает DSS-страницу W0); уровень к этому моменту ещё не прочитан,
|
* отрабатывает ровно один раз за игру, а кода там на полкилобайта. */
|
||||||
* поэтому тип берём напрямую от номера. */
|
if (pop_boot() != 0) return 1;
|
||||||
pop_current_level = FIRST_LEVEL;
|
|
||||||
if (pop_bg_load(pop_level_type()) != 0) {
|
|
||||||
puts("pop_bg_load failed");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
pop_cheats = 1; /* режим разработки: читы включены */
|
|
||||||
pop_guard_reset();
|
|
||||||
if (pop_kid_data_load("KID\\kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */
|
|
||||||
printf("kid_data.bin load failed\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */
|
|
||||||
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
|
|
||||||
if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
|
|
||||||
puts("kid atlas load failed");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (pop_level_load_num(FIRST_LEVEL) != 0) { /* сырой уровень в EMM-страницу */
|
|
||||||
puts("level load failed");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
pop_next_level = pop_current_level; /* current_level = next_level (load_lev_spr) */
|
|
||||||
pop_kid_hp_newgame(); /* init_game: hitp_beg_lev = start_hitp */
|
|
||||||
pop_checkpoint = 0; /* init_game (seg003:37) */
|
|
||||||
|
|
||||||
initgraph();
|
|
||||||
gfx_set_visible_page(0);
|
|
||||||
if (gfx_pal_fload(0, "KID\\kid.pal") < 0) /* единая игровая палитра */
|
|
||||||
gfx_pal_fload(0, "a:\\kid.pal");
|
|
||||||
gfx_pal_sync(); /* палитра 0 -> плоскость 1 */
|
|
||||||
/* kid.pal — ЕДИНАЯ игровая палитра, собранная из VDUNGEON, и она только
|
|
||||||
* что затёрла слоты тайлсета. Возвращаем цвета текущего набора: на
|
|
||||||
* дворцовых уровнях без этого рисуется дворцовая геометрия подземельными
|
|
||||||
* красками (сине-серое вместо песочного). */
|
|
||||||
pop_bg_pal_apply();
|
|
||||||
|
|
||||||
/* Фон комнаты рисуется в ОБЕ графические страницы: у каждой своя
|
|
||||||
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
|
|
||||||
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
|
|
||||||
* ТОЛЬКО прямоугольник Кида (pop_char_heal), а не весь экран. */
|
|
||||||
/* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить
|
|
||||||
* trob/room_modif (ленивая инициализация из уровня при первом входе). */
|
|
||||||
/* Старт — ПО ДАННЫМ УРОВНЯ (комната/тайл/направление/поза входа), см.
|
|
||||||
* pop_start_level. Управление: ←/→ бег, ↓ присед, ↑+→ прыжок с места,
|
|
||||||
* Shift шаг, ESC — выход. Карта коллизии задана внутри enter_room. */
|
|
||||||
pop_start_level();
|
|
||||||
gfx_set_draw_page(0);
|
|
||||||
pop_char_draw(POP_CH_KID);
|
|
||||||
pop_char_fore(POP_CH_KID);
|
|
||||||
|
|
||||||
if (pop_ctrl_init() != 0) { /* raw-канал клавиатуры (забирает ввод у DSS) */
|
if (pop_ctrl_init() != 0) { /* raw-канал клавиатуры (забирает ввод у DSS) */
|
||||||
closegraph();
|
closegraph();
|
||||||
@@ -516,40 +463,9 @@ int main(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pop_next_level != pop_current_level) {
|
if (pop_next_level != pop_current_level) {
|
||||||
/* Порт play_level_2 (seg003:0386..0410) + play_level (seg003:81):
|
/* Смена уровня целиком — в банке (pop_level_switch): грузится
|
||||||
* цикл уровня кончается, когда next_level разошёлся с
|
* файл, спрайты соперника и тайлсет. Раз на уровень. */
|
||||||
* current_level (END_LEVEL после seq_70 в двери уровня либо чит
|
if (pop_level_switch() != 0) return 1;
|
||||||
* Shift+L), и play_level грузит следующий.
|
|
||||||
*
|
|
||||||
* HP переносится: hitp_beg_lev = hitp_max ДО загрузки — так
|
|
||||||
* поднятая на уровне «жизнь» (большая склянка) переезжает
|
|
||||||
* дальше, а урон — нет (do_startpos дольёт до потолка). */
|
|
||||||
hitp_beg_lev = hitp_max;
|
|
||||||
pop_checkpoint = 0; /* уровень пройден (seg003:413) */
|
|
||||||
if (pop_level_load_num(pop_next_level) != 0) {
|
|
||||||
pop_next_level = pop_current_level; /* файла нет — остаёмся */
|
|
||||||
} else {
|
|
||||||
/* Набор спрайтов соперника у каждого уровня СВОЙ
|
|
||||||
* (load_lev_spr, seg000:1092): уровень 3 — скелет, у него и
|
|
||||||
* кадров меньше, чем у стража. Без перезагрузки соперник
|
|
||||||
* рисуется чужим атласом: часть индексов уходит за его
|
|
||||||
* пределы, и персонаж просто не виден. */
|
|
||||||
pop_guard_free();
|
|
||||||
pop_guard_load();
|
|
||||||
/* Тайлсет: подземелье <-> дворец (порт tbl_envir_ki,
|
|
||||||
* seg000:1108). Внутри — no-op, если тип не менялся;
|
|
||||||
* при смене выгружает старые атласы и правит 32 записи
|
|
||||||
* палитры. Только на границе уровня, не в кадре. */
|
|
||||||
if (pop_bg_load(pop_level_type()) != 0) {
|
|
||||||
puts("bg set load failed");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
/* Всё состояние прошлого уровня сносит сам pop_start_level:
|
|
||||||
* pop_trob_reset обнуляет room_seen (модификаторы комнат
|
|
||||||
* перечитаются из НОВОЙ страницы), тайлы/стражи приезжают
|
|
||||||
* из неё же, loose чистит enter_room. */
|
|
||||||
pop_start_level();
|
|
||||||
}
|
|
||||||
gfx_set_draw_page(back);
|
gfx_set_draw_page(back);
|
||||||
}
|
}
|
||||||
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
|
if (pop_kid_dead) { /* напоролся на пики: труп лежит; респавн по ↑ */
|
||||||
@@ -705,13 +621,6 @@ int main(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx_set_idle_hook(0); /* снять хук ДО закрытия raw-канала */
|
pop_shutdown(); /* снять хук, закрыть ввод/графику, отдать ресурсы */
|
||||||
pop_ctrl_close();
|
|
||||||
closegraph();
|
|
||||||
pop_bg_free();
|
|
||||||
pop_kid_free();
|
|
||||||
pop_guard_free();
|
|
||||||
pop_kid_data_free();
|
|
||||||
pop_level_free();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
#include "pop_cdraw.h" /* pop_cd — инвалидация слотов после переворота */
|
#include "pop_cdraw.h" /* pop_cd — инвалидация слотов после переворота */
|
||||||
#include <sprite.h> /* gfx_copy_page */
|
#include <sprite.h> /* gfx_copy_page */
|
||||||
#include "pop_tune.h"
|
#include "pop_tune.h"
|
||||||
|
#include "pop_cheat.h" /* pop_cheats — режим разработки включает pop_boot */
|
||||||
|
#include <stdio.h> /* puts/printf — сообщения об ошибках загрузки */
|
||||||
|
#include <kbd_raw.h>
|
||||||
|
|
||||||
#define SEQ_STAND 2
|
#define SEQ_STAND 2
|
||||||
#define SEQ_TURN 5
|
#define SEQ_TURN 5
|
||||||
@@ -529,3 +532,121 @@ void pop_flip_screen(void) __banked
|
|||||||
POP_CD_TOUCH_ALL(); /* фон трогали целиком */
|
POP_CD_TOUCH_ALL(); /* фон трогали целиком */
|
||||||
pop_hp_invalidate(); /* полосу HP перерисовать */
|
pop_hp_invalidate(); /* полосу HP перерисовать */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Запуск, смена уровня, завершение ------------------------------- *
|
||||||
|
* Три куска главного цикла, которые отрабатывают раз за игру (или раз на
|
||||||
|
* уровень). В резиденте от них оставались только вызовы — сам код здесь.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int pop_boot(void) __banked
|
||||||
|
{
|
||||||
|
/* Набор фона — по типу СТАРТОВОГО уровня. Атласы грузятся ДО initgraph
|
||||||
|
* (тот снимает DSS-страницу W0); уровень к этому моменту ещё не прочитан,
|
||||||
|
* поэтому тип берём напрямую от номера. */
|
||||||
|
pop_current_level = FIRST_LEVEL;
|
||||||
|
if (pop_bg_load(pop_level_type()) != 0) {
|
||||||
|
puts("pop_bg_load failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pop_cheats = 1; /* режим разработки: читы включены */
|
||||||
|
pop_guard_reset();
|
||||||
|
if (pop_kid_data_load("KID\\kid_data.bin") != 0) { /* кадры+seqtbl в EMM-странице */
|
||||||
|
printf("kid_data.bin load failed\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pop_sword_load(); /* меч в руке (без него кадры 229+ без клинка) */
|
||||||
|
pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */
|
||||||
|
if (pop_kid_load(28) != 0) { /* ВСЕ страницы: бег+стоп+разворот+присед+прыжок */
|
||||||
|
puts("kid atlas load failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (pop_level_load_num(FIRST_LEVEL) != 0) { /* сырой уровень в EMM-страницу */
|
||||||
|
puts("level load failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pop_next_level = pop_current_level; /* current_level = next_level (load_lev_spr) */
|
||||||
|
pop_kid_hp_newgame(); /* init_game: hitp_beg_lev = start_hitp */
|
||||||
|
pop_checkpoint = 0; /* init_game (seg003:37) */
|
||||||
|
|
||||||
|
initgraph();
|
||||||
|
gfx_set_visible_page(0);
|
||||||
|
if (gfx_pal_fload(0, "KID\\kid.pal") < 0) /* единая игровая палитра */
|
||||||
|
gfx_pal_fload(0, "a:\\kid.pal");
|
||||||
|
gfx_pal_sync(); /* палитра 0 -> плоскость 1 */
|
||||||
|
/* kid.pal — ЕДИНАЯ игровая палитра, собранная из VDUNGEON, и она только
|
||||||
|
* что затёрла слоты тайлсета. Возвращаем цвета текущего набора: на
|
||||||
|
* дворцовых уровнях без этого рисуется дворцовая геометрия подземельными
|
||||||
|
* красками (сине-серое вместо песочного). */
|
||||||
|
pop_bg_pal_apply();
|
||||||
|
|
||||||
|
/* Фон комнаты рисуется в ОБЕ графические страницы: у каждой своя
|
||||||
|
* ОЗУ-копия/видео-ОЗУ, а heal берёт чистый фон из ОЗУ-копии ТОЙ
|
||||||
|
* страницы, в которую рисуем. Дальше каждый тик восстанавливаем
|
||||||
|
* ТОЛЬКО прямоугольник Кида (pop_char_heal), а не весь экран. */
|
||||||
|
/* Извлечь и отрисовать стартовую комнату (в обе страницы). P0: сбросить
|
||||||
|
* trob/room_modif (ленивая инициализация из уровня при первом входе). */
|
||||||
|
/* Старт — ПО ДАННЫМ УРОВНЯ (комната/тайл/направление/поза входа), см.
|
||||||
|
* pop_start_level. Управление: ←/→ бег, ↓ присед, ↑+→ прыжок с места,
|
||||||
|
* Shift шаг, ESC — выход. Карта коллизии задана внутри enter_room. */
|
||||||
|
pop_start_level();
|
||||||
|
gfx_set_draw_page(0);
|
||||||
|
pop_char_draw(POP_CH_KID);
|
||||||
|
pop_char_fore(POP_CH_KID);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Порт play_level_2 (seg003:0386..0410) + play_level (seg003:81): цикл
|
||||||
|
* уровня кончается, когда next_level разошёлся с current_level (END_LEVEL
|
||||||
|
* после seq_70 в двери уровня либо чит Shift+L), и play_level грузит
|
||||||
|
* следующий. Возврат -1 — фатально (не удалось сменить набор фона). */
|
||||||
|
int pop_level_switch(void) __banked
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Порт play_level_2 (seg003:0386..0410) + play_level (seg003:81):
|
||||||
|
* цикл уровня кончается, когда next_level разошёлся с
|
||||||
|
* current_level (END_LEVEL после seq_70 в двери уровня либо чит
|
||||||
|
* Shift+L), и play_level грузит следующий.
|
||||||
|
*
|
||||||
|
* HP переносится: hitp_beg_lev = hitp_max ДО загрузки — так
|
||||||
|
* поднятая на уровне «жизнь» (большая склянка) переезжает
|
||||||
|
* дальше, а урон — нет (do_startpos дольёт до потолка). */
|
||||||
|
hitp_beg_lev = hitp_max;
|
||||||
|
pop_checkpoint = 0; /* уровень пройден (seg003:413) */
|
||||||
|
if (pop_level_load_num(pop_next_level) != 0) {
|
||||||
|
pop_next_level = pop_current_level; /* файла нет — остаёмся */
|
||||||
|
} else {
|
||||||
|
/* Набор спрайтов соперника у каждого уровня СВОЙ
|
||||||
|
* (load_lev_spr, seg000:1092): уровень 3 — скелет, у него и
|
||||||
|
* кадров меньше, чем у стража. Без перезагрузки соперник
|
||||||
|
* рисуется чужим атласом: часть индексов уходит за его
|
||||||
|
* пределы, и персонаж просто не виден. */
|
||||||
|
pop_guard_free();
|
||||||
|
pop_guard_load();
|
||||||
|
/* Тайлсет: подземелье <-> дворец (порт tbl_envir_ki,
|
||||||
|
* seg000:1108). Внутри — no-op, если тип не менялся;
|
||||||
|
* при смене выгружает старые атласы и правит 32 записи
|
||||||
|
* палитры. Только на границе уровня, не в кадре. */
|
||||||
|
if (pop_bg_load(pop_level_type()) != 0) {
|
||||||
|
puts("bg set load failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* Всё состояние прошлого уровня сносит сам pop_start_level:
|
||||||
|
* pop_trob_reset обнуляет room_seen (модификаторы комнат
|
||||||
|
* перечитаются из НОВОЙ страницы), тайлы/стражи приезжают
|
||||||
|
* из неё же, loose чистит enter_room. */
|
||||||
|
pop_start_level();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_shutdown(void) __banked
|
||||||
|
{
|
||||||
|
gfx_set_idle_hook(0); /* снять хук ДО закрытия raw-канала */
|
||||||
|
pop_ctrl_close();
|
||||||
|
closegraph();
|
||||||
|
pop_bg_free();
|
||||||
|
pop_kid_free();
|
||||||
|
pop_guard_free();
|
||||||
|
pop_kid_data_free();
|
||||||
|
pop_level_free();
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,4 +77,19 @@ uint8_t guard_over_kid(void) __banked;
|
|||||||
* переключение, по флагу pop_upside_dirty. */
|
* переключение, по флагу pop_upside_dirty. */
|
||||||
void pop_flip_screen(void) __banked;
|
void pop_flip_screen(void) __banked;
|
||||||
|
|
||||||
|
/* ---- Запуск / смена уровня / завершение ------------------------------ *
|
||||||
|
* Три куска главного цикла с частотой «раз за игру» или «раз на уровень».
|
||||||
|
* В резиденте от них остались только вызовы. */
|
||||||
|
|
||||||
|
/* Загрузить ресурсы, поднять графику и палитру, поставить Кида на старт и
|
||||||
|
* нарисовать первый экран. 0 — OK, -1 — фатально (сообщение уже выведено). */
|
||||||
|
int pop_boot(void) __banked;
|
||||||
|
|
||||||
|
/* Перейти на pop_next_level: файл уровня, спрайты соперника, тайлсет,
|
||||||
|
* старт. Если файла нет — молча остаться на текущем. -1 — фатально. */
|
||||||
|
int pop_level_switch(void) __banked;
|
||||||
|
|
||||||
|
/* Снять idle-хук, закрыть ввод и графику, отдать все ресурсы. */
|
||||||
|
void pop_shutdown(void) __banked;
|
||||||
|
|
||||||
#endif /* ROOMTEST_COLD_H */
|
#endif /* ROOMTEST_COLD_H */
|
||||||
|
|||||||
@@ -27,9 +27,14 @@ OBJS_geom := build/eng_pop_geom.rel
|
|||||||
# Char урон идёт через pop_take_hp, и кому именно он достанется (hitp_* или
|
# Char урон идёт через pop_take_hp, и кому именно он достанется (hitp_* или
|
||||||
# guardhp_*) решает charid — то есть ровно та логика, которую тесты обязаны
|
# guardhp_*) решает charid — то есть ровно та логика, которую тесты обязаны
|
||||||
# проверять, а не имитировать.
|
# проверять, а не имитировать.
|
||||||
|
# eng_pop_kboot — загрузчик таблиц анимации: с 2026-08-12 он живёт отдельным
|
||||||
|
# модулем (в продукте — банк 8), а сцене он нужен: sc_init зовёт
|
||||||
|
# pop_kid_data_load, и стабы open/read отдают ему настоящий блоб.
|
||||||
OBJS_phys := build/eng_pop_geom.rel build/eng_pop_kid.rel \
|
OBJS_phys := build/eng_pop_geom.rel build/eng_pop_kid.rel \
|
||||||
|
build/eng_pop_kboot.rel \
|
||||||
build/eng_pop_map.rel build/eng_pop_ctrl.rel \
|
build/eng_pop_map.rel build/eng_pop_ctrl.rel \
|
||||||
build/eng_pop_state.rel build/eng_pop_guard.rel \
|
build/eng_pop_state.rel build/eng_pop_guard.rel \
|
||||||
|
build/eng_pop_guard_cold.rel \
|
||||||
build/stubs.rel build/scene.rel build/kid_blob.rel \
|
build/stubs.rel build/scene.rel build/kid_blob.rel \
|
||||||
build/bank_stub.rel
|
build/bank_stub.rel
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user