diff --git a/engine.h b/engine.h index ebc7ff7..62d2459 100644 --- a/engine.h +++ b/engine.h @@ -1,26 +1,39 @@ -#define MAX_GAME_OBJECTS 100 -extern struct object { +#define MAX_OBJECTS_PER_SCENE 30 +#define MAX_SCRIPTS_PER_SCENE 30 +#define MAX_STACKED_SCENES 5 + +struct object { int id; // 0 if slot unused int x; int y; int width; int height; const char *pixels; -} objects[MAX_GAME_OBJECTS]; +}; -#define MAX_GAME_SCRIPTS 100 typedef void (*script_wake_fn)(struct script *scr, int wakeupMode, int arg1, int arg2, int arg3, int arg4); #define SCRIPT_HEADER \ int id /* 0 if slot unused */; \ int wakeupMode; \ script_wake_fn wakeupFn; \ int wakeupArg1; /* for SCRIPT_WAKEUP_OTHER_SCRIPT */ -extern struct script { +struct script { SCRIPT_HEADER - int vars[100]; +}; + +typedef void (*scene_render_fn)(struct scene *s); +void standard_scene_render(struct scene *s); +extern struct scene { + int id; + struct object objects[MAX_OBJECTS_PER_SCENE]; + struct script scripts[MAX_SCRIPTS_PER_SCENE]; + scene_render_fn render_fn; + struct navmesh *navmesh; // defaults to non-null pointer to empty navmesh +} scenes[MAX_STACKED_SCENES]; +extern int scene_depth; // number of stacked scenes +#define top_scene scenes[scene_depth-1] -} scripts[MAX_GAME_SCRIPTS]; struct script_player_walk { SCRIPT_HEADER @@ -36,10 +49,12 @@ struct script_player_walk { // engine -void transition_scene(int scene); // Immediately stops all event processing for current scene, which is unloaded before this function returns +// Scene changes - even push - may discard remaining events for the scene. +void push_scene(int scene); +void replace_scene(int scene); +void pop_scene(); void scene_add_clickrect(int id, int x, int y, int width, int height); void scene_add_object(int id, int x, int y, int width, int height, const char *pixels); -void scene_set_navmesh(struct navmesh *navmesh); struct object *find_object_by_id(int id); struct script *scene_add_script(int id, bool interrupt_existing); struct script *scene_get_script(int id); diff --git a/game control flow.txt b/game control flow.txt new file mode 100644 index 0000000..baf0ef4 --- /dev/null +++ b/game control flow.txt @@ -0,0 +1,14 @@ +At first we had a single scene at a time. This can't support some features we want. + +We can have a stack of scenes. +A scene is often a room but it can also be a modal dialog. +When moving from one room to another it replaces the topmost scene but modal interactions push onto the stack. +Example: Manager's office -> Access the safe -> Textbox -> Pause/quit menu. + +Hypothetically we could also have something like a dream world which keeps the outside-dream state on the stack. + +Scenes come in different types. A room is a typical scene. But we have minigames like the safe, textboxes and menus. +All rooms should inherit some common behaviour like moving the player character. +All menus should inherit menu behaviour. Neither applies to the safe cracking. + +Things from all scenes are rendered in scene stacking order. \ No newline at end of file diff --git a/game.cpp b/game.cpp index 25ed8fb..68979e3 100644 --- a/game.cpp +++ b/game.cpp @@ -30,6 +30,7 @@ extern struct navmesh navmesh_basement; #define OBJID_CLOSE_MODAL 12 void scene_setup(int scene, int fromscene) { + top_scene.render_fn = standard_scene_render; switch(scene) { case SCENE_LOBBY: scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_lobby_raw_start); @@ -42,7 +43,7 @@ void scene_setup(int scene, int fromscene) { scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start); break; } - scene_set_navmesh(&navmesh_lobby); + top_scene.navmesh = &navmesh_lobby; break; case SCENE_MANAGERS_OFFICE: scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_managers_office_raw_start); @@ -63,10 +64,10 @@ void scene_setup(int scene, int fromscene) { scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start); break; } - scene_set_navmesh(&navmesh_managers_office); + top_scene.navmesh = &navmesh_managers_office; break; case SCENE_MANAGERS_OFFICE_SAFE: - scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_managers_office_safe_raw_start); + scene_add_object(OBJID_BACKGROUND, 248, 0, 787, BGHEIGHT, _binary_sprite_managers_office_safe_raw_start); scene_add_object(OBJID_CLOSE_MODAL, 0, 0, 248, 800, nullptr); scene_add_object(OBJID_CLOSE_MODAL, 1035, 0, 1280-1035, 800, nullptr); break; @@ -81,15 +82,20 @@ void scene_setup(int scene, int fromscene) { scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start); break; } - scene_set_navmesh(&navmesh_basement); + top_scene.navmesh = &navmesh_basement; break; } } static void transition_scene_on_walk_finish(struct script *scr, int wakeupMode, int arg1, int arg2, int arg3, int arg4) { scr->id = 0; - if(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT) - transition_scene(scr->vars[0]); + if(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT) { + if(scr->vars[0] == SCENE_MANAGERS_OFFICE_SAFE) { + push_scene(scr->vars[0]); + } else { + replace_scene(scr->vars[0]); + } + } else assert(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED); } @@ -136,7 +142,7 @@ void onclick(int curscene, int objid) { case SCENE_MANAGERS_OFFICE_SAFE: switch(objid) { case OBJID_CLOSE_MODAL: - transition_scene(SCENE_MANAGERS_OFFICE); + pop_scene(); return; } break; diff --git a/pinetab2_framework.cpp b/pinetab2_framework.cpp index 8dd4852..1217f9b 100644 --- a/pinetab2_framework.cpp +++ b/pinetab2_framework.cpp @@ -26,9 +26,8 @@ static int touchscreen_fd; static struct navmesh null_navmesh = {0}; -struct navmesh *cur_navmesh = &null_navmesh; -struct object objects[MAX_GAME_OBJECTS]; -struct script scripts[MAX_GAME_SCRIPTS]; +struct scene scenes[MAX_STACKED_SCENES]; +int scene_depth = 0; bool need_rerender = false; @@ -162,26 +161,32 @@ static void blit(int x, int y, const struct sprite *spr) { } } -void scene_set_navmesh(struct navmesh *navmesh) { - cur_navmesh = navmesh; -} +static void scene_clear_and_setup(int scene, int fromscene) { + memset(&top_scene, 0, sizeof(top_scene)); + top_scene.id = scene; + top_scene.navmesh = &null_navmesh; -static uint32_t scene_generation_count = 0; -static int curscene = -1; -void transition_scene(int scene) { - int prevscene = curscene; - scene_generation_count++; - curscene = scene; - memset(objects, 0, sizeof objects); - memset(scripts, 0, sizeof scripts); // script state is local to scene, so no cancel notification - cur_navmesh = &null_navmesh; - scene_setup(scene, prevscene); + scene_setup(scene, fromscene); +} +void push_scene(int scene) { + scene_depth++; + if(scene_depth > MAX_STACKED_SCENES) error(1, 0, "maximum scene depth exceeded"); + scene_clear_and_setup(scene, -1); +} +void replace_scene(int scene) { + scene_clear_and_setup(scene, top_scene.id); +} +void pop_scene() { + if(scene_depth == 1) error(1, 0, "no scenes left"); + scene_depth--; + // any cleanup? } void scene_add_object(int id, int x, int y, int width, int height, const char *pixels) { if(id == 0) error(1, 0, "scene_add_object: id 0 is invalid"); - for(int i = 0; i < MAX_GAME_OBJECTS; i++) { + struct object *objects = top_scene.objects; + for(int i = 0; i < MAX_OBJECTS_PER_SCENE; i++) { if(objects[i].id == 0) { objects[i].id = id; objects[i].x = x; @@ -196,14 +201,16 @@ void scene_add_object(int id, int x, int y, int width, int height, const char *p } struct object *find_object_by_id(int id) { - for(int i = 0; i < MAX_GAME_OBJECTS; i++) { + struct object *objects = top_scene.objects; + for(int i = 0; i < MAX_OBJECTS_PER_SCENE; i++) { if(objects[i].id == id) return &objects[i]; } return nullptr; } struct script *scene_add_script(int id, bool interrupt_existing) { - for(int i = 0; i < MAX_GAME_SCRIPTS; i++) { + struct script *scripts = top_scene.scripts; + for(int i = 0; i < MAX_SCRIPTS_PER_SCENE; i++) { if(scripts[i].id == id && interrupt_existing) { // interrupt existing script? // player walk script is safe to trivially cancel, at least @@ -310,6 +317,7 @@ static double is_point_in_tri(int x, int y, const struct navmesh_tri *tri) { static int find_navmesh_tri(int x, int y, int tolerance) { int best_tri = -1; double best_dist = tolerance; + struct navmesh *cur_navmesh = top_scene.navmesh; for(int i = 0; i < cur_navmesh->num_tris; i++) { double dist = is_point_in_tri(x, y, &cur_navmesh->tris[i]); if(dist == 0) @@ -347,12 +355,14 @@ void start_player_walk_to_point(int targetX, int targetY) { } void handle_tap(int x, int y) { - uint32_t gencount = scene_generation_count; uint32_t walkgen = player_walk_generation; - for(int i = 0; i < MAX_GAME_OBJECTS; i++) { + int sceneid = top_scene.id; + struct navmesh *cur_navmesh = top_scene.navmesh; + struct object *objects = top_scene.objects; + for(int i = 0; i < MAX_OBJECTS_PER_SCENE; i++) { if(objects[i].id && (unsigned int)(x - objects[i].x) < objects[i].width && (unsigned int)(y - objects[i].y) < objects[i].height) { - onclick(curscene, objects[i].id); - if(gencount != scene_generation_count) return; // early exit if scene changed, and don't walk the player either + onclick(sceneid, objects[i].id); + if(top_scene.id != sceneid) return; // early exit if scene changed, and don't walk the player either } } @@ -385,6 +395,7 @@ static void update_player_walk_script_on_frame(struct script *scr, int wakeupMod deliver_script_wakeup(SCRIPT_WAKEUP_OTHER_SCRIPT, script_id, SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED, 0, 0, 0, 0); return; } + struct navmesh *cur_navmesh = top_scene.navmesh; if(player_walk_script->currentNavmeshTri == -1) { // something weird happened. just teleport player and end movement. @@ -438,16 +449,26 @@ static void update_player_walk_script_on_frame(struct script *scr, int wakeupMod } bool deliver_script_wakeup(int wakeupMode, int wakeupArg1, int wakeupType, int arg1, int arg2, int arg3, int arg4) { - uint32_t gen = scene_generation_count; - for(int i = 0; i < MAX_GAME_SCRIPTS; i++) { + int scene = top_scene.id; + struct script *scripts = top_scene.scripts; + for(int i = 0; i < MAX_SCRIPTS_PER_SCENE; i++) { if(scripts[i].id && scripts[i].wakeupMode == wakeupMode && scripts[i].wakeupArg1 == wakeupArg1) { scripts[i].wakeupFn(&scripts[i], wakeupType, arg1, arg2, arg3, arg4); - if(scene_generation_count != gen) return true; // scene changed - abort early + if(scene_depth == 0 || top_scene.id != scene) return true; // scene changed - abort early } } return false; } +void standard_scene_render(scene *s) { + struct object *objects = s->objects; + for(int i = 0; i < MAX_OBJECTS_PER_SCENE; i++) { + if(objects[i].id != 0 && objects[i].pixels) { + struct sprite spr = {.width = objects[i].width, .height = objects[i].height, .pixels = (uint32_t*)objects[i].pixels}; + blit(objects[i].x, objects[i].y, &spr); + } + } +} int main(int argc, char **argv) { @@ -520,7 +541,7 @@ int main(int argc, char **argv) { if(timerfd_settime(timerfd, 0, &tspec, NULL) < 0) error(1, errno, "timerfd_settime"); } - transition_scene(SCENE_LOBBY); + push_scene(SCENE_LOBBY); int cur_buffer = 0; @@ -588,11 +609,8 @@ int main(int argc, char **argv) { cur_buffer = 1-cur_buffer; // for testing, just keep writing into the same buffer curfb = fbs[cur_buffer]; - for(int i = 0; i < MAX_GAME_OBJECTS; i++) { - if(objects[i].id != 0 && objects[i].pixels) { - struct sprite spr = {.width = objects[i].width, .height = objects[i].height, .pixels = (uint32_t*)objects[i].pixels}; - blit(objects[i].x, objects[i].y, &spr); - } + for(int s = 0; s < scene_depth; s++) { + scenes[s].render_fn(&scenes[s]); } struct drm_mode_crtc_page_flip flipcmd = { diff --git a/sprites/managers_office_safe.xcf b/sprites/managers_office_safe.xcf index 8e1e923..1a75047 100644 Binary files a/sprites/managers_office_safe.xcf and b/sprites/managers_office_safe.xcf differ