Manager's office safe scene stacks on top of manager's office scene, instead of replacing it.

master
immibis 2025-02-18 22:03:37 +01:00
parent bbac8ccc44
commit 6cf3dbd607
5 changed files with 101 additions and 48 deletions

View File

@ -1,26 +1,39 @@
#define MAX_GAME_OBJECTS 100 #define MAX_OBJECTS_PER_SCENE 30
extern struct object { #define MAX_SCRIPTS_PER_SCENE 30
#define MAX_STACKED_SCENES 5
struct object {
int id; // 0 if slot unused int id; // 0 if slot unused
int x; int x;
int y; int y;
int width; int width;
int height; int height;
const char *pixels; 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); typedef void (*script_wake_fn)(struct script *scr, int wakeupMode, int arg1, int arg2, int arg3, int arg4);
#define SCRIPT_HEADER \ #define SCRIPT_HEADER \
int id /* 0 if slot unused */; \ int id /* 0 if slot unused */; \
int wakeupMode; \ int wakeupMode; \
script_wake_fn wakeupFn; \ script_wake_fn wakeupFn; \
int wakeupArg1; /* for SCRIPT_WAKEUP_OTHER_SCRIPT */ int wakeupArg1; /* for SCRIPT_WAKEUP_OTHER_SCRIPT */
extern struct script { struct script {
SCRIPT_HEADER SCRIPT_HEADER
int vars[100]; 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 { struct script_player_walk {
SCRIPT_HEADER SCRIPT_HEADER
@ -36,10 +49,12 @@ struct script_player_walk {
// engine // 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_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_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 object *find_object_by_id(int id);
struct script *scene_add_script(int id, bool interrupt_existing); struct script *scene_add_script(int id, bool interrupt_existing);
struct script *scene_get_script(int id); struct script *scene_get_script(int id);

14
game control flow.txt Normal file
View File

@ -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.

View File

@ -30,6 +30,7 @@ extern struct navmesh navmesh_basement;
#define OBJID_CLOSE_MODAL 12 #define OBJID_CLOSE_MODAL 12
void scene_setup(int scene, int fromscene) { void scene_setup(int scene, int fromscene) {
top_scene.render_fn = standard_scene_render;
switch(scene) { switch(scene) {
case SCENE_LOBBY: case SCENE_LOBBY:
scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_lobby_raw_start); 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); scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start);
break; break;
} }
scene_set_navmesh(&navmesh_lobby); top_scene.navmesh = &navmesh_lobby;
break; break;
case SCENE_MANAGERS_OFFICE: case SCENE_MANAGERS_OFFICE:
scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_managers_office_raw_start); 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); scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start);
break; break;
} }
scene_set_navmesh(&navmesh_managers_office); top_scene.navmesh = &navmesh_managers_office;
break; break;
case SCENE_MANAGERS_OFFICE_SAFE: 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, 0, 0, 248, 800, nullptr);
scene_add_object(OBJID_CLOSE_MODAL, 1035, 0, 1280-1035, 800, nullptr); scene_add_object(OBJID_CLOSE_MODAL, 1035, 0, 1280-1035, 800, nullptr);
break; 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); scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start);
break; break;
} }
scene_set_navmesh(&navmesh_basement); top_scene.navmesh = &navmesh_basement;
break; break;
} }
} }
static void transition_scene_on_walk_finish(struct script *scr, int wakeupMode, int arg1, int arg2, int arg3, int arg4) { static void transition_scene_on_walk_finish(struct script *scr, int wakeupMode, int arg1, int arg2, int arg3, int arg4) {
scr->id = 0; scr->id = 0;
if(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT) if(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT) {
transition_scene(scr->vars[0]); if(scr->vars[0] == SCENE_MANAGERS_OFFICE_SAFE) {
push_scene(scr->vars[0]);
} else {
replace_scene(scr->vars[0]);
}
}
else else
assert(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED); assert(wakeupMode == SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED);
} }
@ -136,7 +142,7 @@ void onclick(int curscene, int objid) {
case SCENE_MANAGERS_OFFICE_SAFE: case SCENE_MANAGERS_OFFICE_SAFE:
switch(objid) { switch(objid) {
case OBJID_CLOSE_MODAL: case OBJID_CLOSE_MODAL:
transition_scene(SCENE_MANAGERS_OFFICE); pop_scene();
return; return;
} }
break; break;

View File

@ -26,9 +26,8 @@
static int touchscreen_fd; static int touchscreen_fd;
static struct navmesh null_navmesh = {0}; static struct navmesh null_navmesh = {0};
struct navmesh *cur_navmesh = &null_navmesh; struct scene scenes[MAX_STACKED_SCENES];
struct object objects[MAX_GAME_OBJECTS]; int scene_depth = 0;
struct script scripts[MAX_GAME_SCRIPTS];
bool need_rerender = false; 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) { static void scene_clear_and_setup(int scene, int fromscene) {
cur_navmesh = navmesh; memset(&top_scene, 0, sizeof(top_scene));
} top_scene.id = scene;
top_scene.navmesh = &null_navmesh;
static uint32_t scene_generation_count = 0; scene_setup(scene, fromscene);
static int curscene = -1; }
void transition_scene(int scene) { void push_scene(int scene) {
int prevscene = curscene; scene_depth++;
scene_generation_count++; if(scene_depth > MAX_STACKED_SCENES) error(1, 0, "maximum scene depth exceeded");
curscene = scene; scene_clear_and_setup(scene, -1);
memset(objects, 0, sizeof objects); }
memset(scripts, 0, sizeof scripts); // script state is local to scene, so no cancel notification void replace_scene(int scene) {
cur_navmesh = &null_navmesh; scene_clear_and_setup(scene, top_scene.id);
scene_setup(scene, prevscene); }
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) { 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"); 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) { if(objects[i].id == 0) {
objects[i].id = id; objects[i].id = id;
objects[i].x = x; 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) { 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) if(objects[i].id == id)
return &objects[i]; return &objects[i];
} }
return nullptr; return nullptr;
} }
struct script *scene_add_script(int id, bool interrupt_existing) { 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) { if(scripts[i].id == id && interrupt_existing) {
// interrupt existing script? // interrupt existing script?
// player walk script is safe to trivially cancel, at least // 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) { static int find_navmesh_tri(int x, int y, int tolerance) {
int best_tri = -1; int best_tri = -1;
double best_dist = tolerance; double best_dist = tolerance;
struct navmesh *cur_navmesh = top_scene.navmesh;
for(int i = 0; i < cur_navmesh->num_tris; i++) { for(int i = 0; i < cur_navmesh->num_tris; i++) {
double dist = is_point_in_tri(x, y, &cur_navmesh->tris[i]); double dist = is_point_in_tri(x, y, &cur_navmesh->tris[i]);
if(dist == 0) if(dist == 0)
@ -347,12 +355,14 @@ void start_player_walk_to_point(int targetX, int targetY) {
} }
void handle_tap(int x, int y) { void handle_tap(int x, int y) {
uint32_t gencount = scene_generation_count;
uint32_t walkgen = player_walk_generation; 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) { 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); onclick(sceneid, objects[i].id);
if(gencount != scene_generation_count) return; // early exit if scene changed, and don't walk the player either 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); deliver_script_wakeup(SCRIPT_WAKEUP_OTHER_SCRIPT, script_id, SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED, 0, 0, 0, 0);
return; return;
} }
struct navmesh *cur_navmesh = top_scene.navmesh;
if(player_walk_script->currentNavmeshTri == -1) { if(player_walk_script->currentNavmeshTri == -1) {
// something weird happened. just teleport player and end movement. // 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) { bool deliver_script_wakeup(int wakeupMode, int wakeupArg1, int wakeupType, int arg1, int arg2, int arg3, int arg4) {
uint32_t gen = scene_generation_count; int scene = top_scene.id;
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 && scripts[i].wakeupMode == wakeupMode && scripts[i].wakeupArg1 == wakeupArg1) { if(scripts[i].id && scripts[i].wakeupMode == wakeupMode && scripts[i].wakeupArg1 == wakeupArg1) {
scripts[i].wakeupFn(&scripts[i], wakeupType, arg1, arg2, arg3, arg4); 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; 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) { 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"); 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; 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 cur_buffer = 1-cur_buffer; // for testing, just keep writing into the same buffer
curfb = fbs[cur_buffer]; curfb = fbs[cur_buffer];
for(int i = 0; i < MAX_GAME_OBJECTS; i++) { for(int s = 0; s < scene_depth; s++) {
if(objects[i].id != 0 && objects[i].pixels) { scenes[s].render_fn(&scenes[s]);
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);
}
} }
struct drm_mode_crtc_page_flip flipcmd = { struct drm_mode_crtc_page_flip flipcmd = {

Binary file not shown.