Merge pull request #321 from kichikuou/controller

PartsEngine: Implement controller (layer) system
This commit is contained in:
Nunuhara Cabbage
2026-04-12 16:42:06 -07:00
committed by GitHub
6 changed files with 214 additions and 9 deletions
+7
View File
@@ -23,6 +23,7 @@ struct page;
struct string;
// parts.c
void PE_enable_multi_controller(void);
bool PE_Init(void);
void PE_Reset(void);
void PE_Update(int passed_time, bool message_window_show);
@@ -67,6 +68,7 @@ bool PE_SetNumeralSurfaceArea(int parts_no, int x, int y, int w, int h, int stat
void PE_ReleaseParts(int parts_no);
void PE_ReleaseAllParts(void);
void PE_ReleaseAllPartsWithoutSystem(void);
void PE_ReleaseAllWithoutSystem(struct page **erase_number_list);
void PE_SetPos(int parts_no, int x, int y);
int PE_GetPartsX(int parts_no);
int PE_GetPartsY(int parts_no);
@@ -113,10 +115,15 @@ bool PE_SetPartsCGDetectionSize(int parts_no, struct string *cg_name, int state)
bool PE_Save(struct page **buffer);
bool PE_SaveWithoutHideParts(struct page **buffer);
bool PE_Load(struct page **buffer);
int PE_AddController(int index);
void PE_RemoveController(struct page **erase_number_list, int index);
// GUIEngine
int PE_GetFreeNumber(void);
bool PE_IsExist(int parts_no);
// PartsFunc interface
void PE_set_active_controller(int controller_no);
int PE_get_active_controller(void);
int PE_get_system_controller(void);
bool PE_init_parts_movie(int parts_no, int width, int height, int bg_r, int bg_g, int bg_b, int state);
int PE_get_movie_sprite(int parts_no, int state);
+18 -3
View File
@@ -115,6 +115,18 @@ static int PartsEngine_PartsFunc(int func_id, struct page **array_int,
if (nr_ints != (n)) VM_ERROR("Invalid arguments for PartsFunc %d: expected %d ints, got %d", func_id, (n), nr_ints)
switch (func_id) {
case 0: // void SetActiveLayer(int layer)
REQUIRE_INTS(1);
PE_set_active_controller(ints[0].i);
return 1;
case 1: // int GetActiveLayer()
REQUIRE_INTS(1);
ints[0].i = PE_get_active_controller();
return 1;
case 2: // int GetSystemOverlayLayer()
REQUIRE_INTS(1);
ints[0].i = PE_get_system_controller();
return 1;
case 162: // bool InitPartsMovie(int parts_no, int width, int height, int bg_r, int bg_g, int bg_b, int state)
REQUIRE_INTS(8);
ints[7].i = PE_init_parts_movie(ints[0].i, ints[1].i, ints[2].i, ints[3].i, ints[4].i, ints[5].i, ints[6].i);
@@ -307,11 +319,11 @@ HLL_LIBRARY(PartsEngine,
HLL_EXPORT(PartsFunc, PartsEngine_PartsFunc),
HLL_EXPORT(Release, PE_ReleaseParts),
HLL_TODO_EXPORT(ReleaseAll, PartsEngine_ReleaseAll),
HLL_TODO_EXPORT(ReleaseAllWithoutSystem, PartsEngine_ReleaseAllWithoutSystem),
HLL_EXPORT(ReleaseAllWithoutSystem, PE_ReleaseAllWithoutSystem),
HLL_EXPORT(GetFreeNumber, PE_GetFreeNumber),
HLL_EXPORT(IsExist, PE_IsExist),
HLL_TODO_EXPORT(AddController, PartsEngine_AddController),
HLL_TODO_EXPORT(RemoveController, PartsEngine_RemoveController),
HLL_EXPORT(AddController, PE_AddController),
HLL_EXPORT(RemoveController, PE_RemoveController),
HLL_EXPORT(UpdateComponent, PartsEngine_Update),
HLL_EXPORT(Parts_SetThumbnailReductionSize, PE_SetThumbnailReductionSize),
HLL_EXPORT(Parts_SetThumbnailMode, PE_SetThumbnailMode),
@@ -684,4 +696,7 @@ static void PartsEngine_PreLink(void)
static_library_replace(&lib_PartsEngine, "Update",
PartsEngine_Update_Pascha3PC);
}
if (get_fun(libno, "AddController")) {
PE_enable_multi_controller();
}
}
+3 -1
View File
@@ -380,6 +380,8 @@ cJSON *parts_to_json(struct parts *parts, bool recursive, bool verbose)
cJSON_AddItemToObjectCS(obj, "clicked", parts_state_to_json(&parts->states[PARTS_STATE_CLICKED], verbose));
cJSON_AddItemToObjectCS(obj, "local", parts_params_to_json(&parts->local, verbose));
cJSON_AddItemToObjectCS(obj, "global", parts_params_to_json(&parts->global, verbose));
if (parts_multi_controller)
cJSON_AddNumberToObject(obj, "controller", parts->controller_no);
if (parts->delegate_index >= 0)
cJSON_AddNumberToObject(obj, "delegate_index", parts->delegate_index);
cJSON_AddNumberToObject(obj, "sprite_deform", parts->sprite_deform);
@@ -558,7 +560,7 @@ static void parts_list_print(struct parts *parts, int indent)
break;
}
sys_message(" @ z=%d (%d)\n", parts->global.z, parts->local.z);
sys_message(" @ controller=%d z=%d (%d)\n", parts->controller_no, parts->global.z, parts->local.z);
struct parts *child;
PARTS_FOREACH_CHILD(child, parts) {
+150 -5
View File
@@ -35,6 +35,12 @@ static struct parts_list dirty_list = TAILQ_HEAD_INITIALIZER(dirty_list);
static struct hash_table *parts_table = NULL;
static Point root_pos = { 0, 0 };
struct parts_controller_stack ctrl_stack;
bool parts_multi_controller;
static void ctrl_stack_init(void);
static void ctrl_stack_fini(void);
#define PARTS_PARAMS_INITIALIZER (struct parts_params) { \
.z = 1, \
.pos = { 0, 0 }, \
@@ -87,17 +93,38 @@ static void dirty_list_remove(struct parts *parts)
TAILQ_REMOVE(&dirty_list, parts, dirty_list_entry);
}
static int parts_get_sprite_z(struct parts *parts)
{
if (!parts_multi_controller)
return parts->global.z;
// The system overlay controller sorts above any in-stack controller.
return parts->controller_no;
}
static int parts_get_sprite_z2(struct parts *parts)
{
if (!parts_multi_controller)
return 0;
return parts->global.z;
}
static void parts_list_insert(struct parts *parts)
{
int z = parts_get_sprite_z(parts);
int z2 = parts_get_sprite_z2(parts);
parts->sp.z = z;
parts->sp.z2 = z2;
struct parts *p;
PARTS_LIST_FOREACH(p) {
if (p->global.z > parts->global.z) {
int pz = parts_get_sprite_z(p);
int pz2 = parts_get_sprite_z2(p);
if (pz > z || (pz == z && pz2 > z2)) {
TAILQ_INSERT_BEFORE(p, parts, parts_list_entry);
parts_engine_dirty();
return;
goto done;
}
}
TAILQ_INSERT_TAIL(&parts_list, parts, parts_list_entry);
done:
parts_engine_dirty();
scene_register_sprite(&parts->sp);
}
@@ -113,7 +140,6 @@ void parts_list_resort(struct parts *parts)
// TODO: this could be optimized
parts_list_remove(parts);
parts_list_insert(parts);
scene_set_sprite_z(&parts->sp, parts->global.z);
}
struct parts *parts_try_get(int parts_no)
@@ -132,6 +158,7 @@ struct parts *parts_get(int parts_no)
struct parts *parts = parts_alloc();
parts->no = parts_no;
parts->controller_no = ctrl_stack.active;
slot->value = parts;
parts_list_insert(parts);
return parts;
@@ -968,6 +995,14 @@ void parts_release_all(void)
static bool parts_engine_initialized = false;
void PE_enable_multi_controller(void)
{
if (parts_multi_controller)
return;
assert(!parts_engine_initialized);
parts_multi_controller = true;
}
bool PE_Init(void)
{
if (parts_engine_initialized)
@@ -977,6 +1012,7 @@ bool PE_Init(void)
parts_table = ht_create(1024);
parts_render_init();
parts_debug_init();
ctrl_stack_init();
parts_engine_initialized = true;
return true;
}
@@ -984,6 +1020,7 @@ bool PE_Init(void)
void PE_Reset(void)
{
PE_ReleaseAllParts();
ctrl_stack_fini();
sact_ModuleFini();
}
@@ -1021,7 +1058,8 @@ static void parts_update_component(struct parts *parts)
if (parts->parent) {
parts_combine_params(&parts->parent->global, &parts->local, &parts->global);
}
if (parts->global.z != parts->sp.z) {
if (parts_get_sprite_z(parts) != parts->sp.z
|| parts_get_sprite_z2(parts) != parts->sp.z2) {
parts_list_resort(parts);
}
@@ -1589,6 +1627,25 @@ void PE_ReleaseAllPartsWithoutSystem(void)
parts_release_all();
}
void PE_ReleaseAllWithoutSystem(struct page **erase_number_list)
{
// Release all parts not belonging to the system overlay controller
struct parts *parts = TAILQ_FIRST(&parts_list);
while (parts) {
struct parts *next = TAILQ_NEXT(parts, parts_list_entry);
if (parts->controller_no != PARTS_CONTROLLER_SYSTEM_OVERLAY) {
*erase_number_list = array_pushback(*erase_number_list,
(union vm_value){.i = parts->no}, AIN_ARRAY_INT, -1);
parts_release(parts->no);
}
parts = next;
}
// Drop all normal controllers and add a fresh default one.
ctrl_stack.nr_controllers = 0;
PE_AddController(-1);
}
void PE_SetPos(int parts_no, int x, int y)
{
parts_set_pos(parts_get(parts_no), (Point){ x, y });
@@ -1880,6 +1937,94 @@ void PE_SetSpeedupRateByMessageSkip(int parts_no, int rate)
UNIMPLEMENTED("(%d, %d)");
}
static void ctrl_stack_init(void)
{
memset(&ctrl_stack, 0, sizeof(ctrl_stack));
// Add initial default controller
PE_AddController(-1);
}
static void ctrl_stack_fini(void)
{
memset(&ctrl_stack, 0, sizeof(ctrl_stack));
}
// Adds a new controller to the stack and makes it active. The `index`
// parameter specifies the position in the stack at which to insert the new
// controller; -1 means "insert directly after the currently active
// controller". In practice the game only ever passes -1, and the active
// controller is always the top of the stack at that point, so this
// degenerates to a simple push.
int PE_AddController(int index)
{
if (index != -1)
VM_ERROR("index != -1 not supported (got %d)", index);
if (ctrl_stack.nr_controllers > 0 &&
ctrl_stack.active != ctrl_stack.nr_controllers - 1)
VM_ERROR("active controller is not at the top of the stack");
if (ctrl_stack.nr_controllers >= PARTS_CONTROLLER_STACK_MAX)
VM_ERROR("controller stack overflow");
int no = ctrl_stack.nr_controllers++;
ctrl_stack.active = no;
return no;
}
// Removes the controller at position `index` from the stack, releases all
// parts belonging to it, and returns their parts numbers in
// `erase_number_list`. `index == -1` means "remove the currently active
// controller". In practice the game only ever passes -1, and the active
// controller is always the top of the stack at that point, so this
// degenerates to a simple pop.
void PE_RemoveController(struct page **erase_number_list, int index)
{
if (index != -1)
VM_ERROR("index != -1 not supported (got %d)", index);
if (ctrl_stack.nr_controllers == 0 ||
ctrl_stack.active != ctrl_stack.nr_controllers - 1)
VM_ERROR("active controller is not at the top of the stack");
int ctrl_no = ctrl_stack.active;
// Collect and release parts belonging to this controller
struct parts *p = TAILQ_FIRST(&parts_list);
while (p) {
struct parts *next = TAILQ_NEXT(p, parts_list_entry);
if (p->controller_no == ctrl_no) {
*erase_number_list = array_pushback(*erase_number_list,
(union vm_value){.i = p->no}, AIN_ARRAY_INT, -1);
parts_release(p->no);
}
p = next;
}
ctrl_stack.nr_controllers--;
if (ctrl_stack.nr_controllers == 0) {
PE_AddController(-1);
} else {
ctrl_stack.active = ctrl_stack.nr_controllers - 1;
}
}
void PE_set_active_controller(int controller_no)
{
if (controller_no == PARTS_CONTROLLER_SYSTEM_OVERLAY ||
(controller_no >= 0 && controller_no < ctrl_stack.nr_controllers))
ctrl_stack.active = controller_no;
else
VM_ERROR("Invalid controller number: %d", controller_no);
}
int PE_get_active_controller(void)
{
return ctrl_stack.active;
}
int PE_get_system_controller(void)
{
return PARTS_CONTROLLER_SYSTEM_OVERLAY;
}
bool PE_init_parts_movie(int parts_no, int width, int height, int bg_r, int bg_g, int bg_b, int state)
{
if (!parts_state_valid(--state))
+14
View File
@@ -374,6 +374,7 @@ struct parts {
bool message_window;
int alpha_clipper_parts_no;
struct parts_motion_list motion;
int controller_no;
};
#define PARTS_LIST_FOREACH(iter) TAILQ_FOREACH(iter, &parts_list, parts_list_entry)
@@ -381,6 +382,19 @@ struct parts {
// parts.c
extern struct parts_list parts_list;
// Controllers are identified by their position in the stack (0 = bottom). The
// system overlay controller lives outside the stack.
#define PARTS_CONTROLLER_STACK_MAX 10000
#define PARTS_CONTROLLER_SYSTEM_OVERLAY PARTS_CONTROLLER_STACK_MAX
struct parts_controller_stack {
int nr_controllers;
int active; // stack index or PARTS_CONTROLLER_SYSTEM_OVERLAY
};
extern struct parts_controller_stack ctrl_stack;
extern bool parts_multi_controller;
struct parts *parts_try_get(int parts_no);
struct parts *parts_get(int parts_no);
struct parts_cg *parts_get_cg(struct parts *parts, int state);
+22
View File
@@ -530,6 +530,10 @@ static void save_parts(struct iarray_writer *w, struct parts *parts)
iarray_write(w, parts->draw_filter);
iarray_write(w, parts->message_window);
iarray_write(w, parts->alpha_clipper_parts_no);
// TODO: once the Rance 9 save format stabilizes, bump save version
// and save unconditionally
if (parts_multi_controller)
iarray_write(w, parts->controller_no);
unsigned motion_count_pos = iarray_writer_pos(w);
iarray_write(w, 0); // size of motion list
@@ -569,6 +573,10 @@ static void load_parts(struct iarray_reader *r, int version)
parts->message_window = iarray_read(r);
if (version > 2)
parts->alpha_clipper_parts_no = iarray_read(r);
// TODO: once the Rance 9 save format stabilizes, bump save version
// and load based on version check
if (parts_multi_controller)
parts->controller_no = iarray_read(r);
int motion_count = iarray_read(r);
for (int i = 0; i < motion_count; i++) {
@@ -620,6 +628,13 @@ static bool parts_engine_save(struct page **buffer, bool save_hidden)
if (CURRENT_SAVE_VERSION > 1)
save_numeral_fonts(&w);
// TODO: once the Rance 9 save format stabilizes, bump save version
// and save unconditionally
if (parts_multi_controller) {
iarray_write(&w, ctrl_stack.active);
iarray_write(&w, ctrl_stack.nr_controllers);
}
unsigned count_pos = iarray_writer_pos(&w);
iarray_write(&w, 0); // size of parts list
@@ -677,6 +692,13 @@ bool PE_Load(struct page **buffer)
if (version > 1)
load_numeral_fonts(&r);
// TODO: once the Rance 9 save format stabilizes, bump save version
// and load based on version check
if (parts_multi_controller) {
ctrl_stack.active = iarray_read(&r);
ctrl_stack.nr_controllers = iarray_read(&r);
}
int nr_parts = iarray_read(&r);
if (nr_parts < 0) {
WARNING("invalid parts count");