changeset 479:8e41ed5474cf

examples: resurrect example-action
author David Demelier <markand@malikania.fr>
date Mon, 27 Feb 2023 16:33:33 +0100
parents 8c1a965b3019
children c1f64d451230
files examples/CMakeLists.txt examples/example-action/CMakeLists.txt examples/example-action/example-action.c examples/example-animation/example-animation.c libmlk-example/CMakeLists.txt libmlk-example/mlk/example/example.c libmlk-example/mlk/example/example.h libmlk-example/mlk/example/registry.c libmlk-example/mlk/example/registry.h
diffstat 9 files changed, 658 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/examples/CMakeLists.txt	Mon Feb 27 12:49:39 2023 +0100
+++ b/examples/CMakeLists.txt	Mon Feb 27 16:33:33 2023 +0100
@@ -20,6 +20,7 @@
 
 set(
 	DIRS
+	example-action
 	example-animation
 	example-audio
 	example-battle
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-action/CMakeLists.txt	Mon Feb 27 16:33:33 2023 +0100
@@ -0,0 +1,33 @@
+#
+# CMakeLists.txt -- CMake build system for Molko's Engine
+#
+# Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(example-action)
+
+set(
+	SOURCES
+	${example-action_SOURCE_DIR}/example-action.c
+)
+
+mlk_executable(
+	NAME example-action
+	FOLDER examples
+	LIBRARIES libmlk-example
+	SOURCES ${SOURCES}
+)
+
+source_group(TREE ${example-action_SOURCE_DIR} FILES ${SOURCES})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-action/example-action.c	Mon Feb 27 16:33:33 2023 +0100
@@ -0,0 +1,509 @@
+/*
+ * example-action.c -- example on how to use automatic actions
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+
+#include <mlk/core/action-script.h>
+#include <mlk/core/action-stack.h>
+#include <mlk/core/action.h>
+#include <mlk/core/err.h>
+#include <mlk/core/event.h>
+#include <mlk/core/game.h>
+#include <mlk/core/maths.h>
+#include <mlk/core/painter.h>
+#include <mlk/core/panic.h>
+#include <mlk/core/sprite.h>
+#include <mlk/core/state.h>
+#include <mlk/core/texture.h>
+#include <mlk/core/trace.h>
+#include <mlk/core/util.h>
+
+#include <mlk/example/example.h>
+#include <mlk/example/registry.h>
+
+#include <mlk/ui/align.h>
+#include <mlk/ui/label.h>
+
+#include <mlk/rpg/message.h>
+
+/* Message width is 80% of window width and height is auto computed. */
+#define QMW (MLK_EXAMPLE_W * 0.8)
+
+/* Position of each message. */
+#define QMX ((MLK_EXAMPLE_W - QMW) / 2)
+#define QMY (MLK_EXAMPLE_H * 0.2)
+
+/* Fading time and spacing. */
+#define QMD 250
+#define QMS 10
+
+static struct mlk_state *states[8];
+
+/*
+ * Those are two chests shown on the script that the user can click. They will
+ * fill up the script scene.
+ *
+ * For convenience, we arrange those two chests into a mlk_action_stack to
+ * avoid calling all mlk_action functions for every chests.
+ */
+static void chest_handle(struct mlk_action *, const union mlk_event *);
+static void chest_draw(struct mlk_action *);
+
+static struct chest {
+	int x;
+	int y;
+	int open;
+	struct mlk_sprite *sprite;
+	struct mlk_action action;
+} chests[2] = {
+	[0] = {
+		.sprite = &registry_sprites[REGISTRY_TEXTURE_CHEST],
+		.action = {
+			.data = &chests[0],
+			.draw = chest_draw,
+			.handle = chest_handle,
+		}
+	},
+	[1] = {
+		.sprite = &registry_sprites[REGISTRY_TEXTURE_CHEST],
+		.action = {
+			.data = &chests[1],
+			.draw = chest_draw,
+			.handle = chest_handle,
+		}
+	}
+};
+
+/*
+ * This structure is the item that will be pushed into the mlk_action_script
+ * to create a sequence of message the user has to complete.
+ */
+struct item {
+	struct message msg;
+	struct mlk_action act;
+};
+
+static struct mlk_action *chests_stack_actions[16] = {
+	[0] = &chests[0].action,
+	[1] = &chests[1].action
+};
+static struct mlk_action_stack chests_stack = {
+	.actions = chests_stack_actions,
+	.actionsz = MLK_UTIL_SIZE(chests_stack_actions)
+};
+
+/*
+ * Just a label to explain to the user what to do.
+ */
+static struct label label = {
+	.flags = LABEL_FLAGS_SHADOW,
+	.text = "Select your destiny."
+};
+
+static void     item_handle(struct mlk_action *, const union mlk_event *);
+static int      item_update(struct mlk_action *, unsigned int);
+static void     item_draw(struct mlk_action *);
+
+static void     script_init_quest1(void);
+static void     script_init_quest2(void);
+
+static void     terminate_quest1(struct mlk_action *);
+static void     terminate_quest2(struct mlk_action *);
+
+static struct item quest1[] = {
+	{
+		.msg = {
+			.flags = MESSAGE_FLAGS_FADEIN,
+			.linesz = 1,
+			.lines = (const char *[]) {
+				"Welcome to this game."
+			}
+		},
+		.act = {
+			.data = &quest1[0].msg,
+			.handle = item_handle,
+			.update = item_update,
+			.draw = item_draw
+		}
+	},
+	{
+		.msg = {
+			.linesz = 3,
+			.lines = (const char *[]) {
+				"Your journey will begin in this dangerous world,",
+				"you must take this before going any further",
+				"Are you ready?"
+			}
+		},
+		.act = {
+			.data = &quest1[1].msg,
+			.handle = item_handle,
+			.update = item_update,
+			.draw = item_draw
+		}
+	},
+	{
+		.msg = {
+			.flags = MESSAGE_FLAGS_QUESTION,
+			.linesz = 2,
+			.lines = (const char *[]) {
+				"Of course I am",
+				"No I'm not sure"
+			}
+		},
+		.act = {
+			.data = &quest1[2].msg,
+			.handle = item_handle,
+			.update = item_update,
+			.draw = item_draw,
+			.end = terminate_quest1
+		}
+	},
+
+	/*
+	 * This final action is a placeholder that will be updated once the
+	 * last message question is completed through the end callback.
+	 */
+	{
+		.msg = {
+			.flags = MESSAGE_FLAGS_FADEOUT
+		},
+		.act = {
+			.data = &quest1[3].msg,
+			.handle = item_handle,
+			.update = item_update,
+			.draw = item_draw
+		}
+	}
+};
+
+static struct item quest2[] = {
+	{
+		.msg = {
+			.flags = MESSAGE_FLAGS_FADEIN,
+			.linesz = 1,
+			.lines = (const char *[]) {
+				"Why did you select this chest?"
+			}
+		},
+		.act = {
+			.data = &quest2[0].msg,
+			.handle = item_handle,
+			.update = item_update,
+			.draw = item_draw
+		}
+	},
+	{
+		.msg = {
+			.flags = MESSAGE_FLAGS_QUESTION,
+			.linesz = 2,
+			.lines = (const char *[]) {
+				"Because I think there was some gold",
+				"I have no freaking idea"
+			}
+		},
+		.act = {
+			.data = &quest2[1].msg,
+			.handle = item_handle,
+			.update = item_update,
+			.draw = item_draw,
+			.end = terminate_quest2
+		}
+	},
+
+	/* See comment in quest 1. */
+	{
+		.msg = {
+			.flags = MESSAGE_FLAGS_FADEOUT
+		},
+		.act = {
+			.data = &quest2[2].msg,
+			.handle = item_handle,
+			.update = item_update,
+			.draw = item_draw
+		}
+	}
+};
+
+/*
+ * This is the main script that the loop will execute, we will initialize it
+ * depending on which chest the player clicks.
+ */
+static struct mlk_action *script_actions[16];
+static struct mlk_action_script script;
+
+static void
+chest_handle(struct mlk_action *act, const union mlk_event *ev)
+{
+	struct chest *chest = act->data;
+	unsigned int cw, ch;
+
+	/* Make sure that we don't operate on a already opened chest. */
+	if (chest->open)
+		return;
+
+	/*
+	 * We are only interested if the event is a click on the chest itself
+	 * so we have to test because actions have no notion of geometry.
+	 */
+	if (ev->type != MLK_EVENT_CLICKDOWN)
+		return;
+
+	cw = registry_sprites[REGISTRY_TEXTURE_CHEST].cellw;
+	ch = registry_sprites[REGISTRY_TEXTURE_CHEST].cellh;
+
+	if (!mlk_maths_is_boxed(chest->x, chest->y, cw, ch, ev->click.x, ev->click.y))
+		return;
+
+	/* Also, make sure that we don't operate on a already opened chest. */
+	chest->open = 1;
+
+	/*
+	 * Now depending on the chest, update the main script with actual code
+	 * we're interested in.
+	 */
+	memset(script_actions, 0, sizeof (script_actions));
+	mlk_action_script_init(&script, script_actions, MLK_UTIL_SIZE(script_actions));
+
+	if (chest == &chests[0])
+		script_init_quest1();
+	else
+		script_init_quest2();
+}
+
+static void
+chest_draw(struct mlk_action *act)
+{
+	const struct chest *chest = act->data;
+	int column;
+
+	if (chest->open)
+		column = 3;
+	else
+		column = 0;
+
+	mlk_sprite_draw(chest->sprite, 0, column, chest->x, chest->y);
+}
+
+static void
+item_handle(struct mlk_action *act, const union mlk_event *ev)
+{
+	message_handle(act->data, ev);
+}
+
+static int
+item_update(struct mlk_action *act, unsigned int ticks)
+{
+	return message_update(act->data, ticks);
+}
+
+static void
+item_draw(struct mlk_action *act)
+{
+	message_draw(act->data);
+}
+
+static void
+script_init_quest(struct item *m, size_t n)
+{
+	int err;
+
+	for (size_t i = 0; i < n; ++i) {
+		m[i].msg.x = QMX;
+		m[i].msg.y = QMY;
+		m[i].msg.w = QMW;
+		m[i].msg.delay = QMD;
+		m[i].msg.spacing = QMS;
+
+		message_start(&m[i].msg);
+		message_query(&m[i].msg, NULL, &m[i].msg.h);
+
+		if ((err = mlk_action_script_append(&script, &m[i].act)) < 0)
+			mlk_panicf("%s", mlk_err_string(err));
+	}
+}
+
+static void
+script_init_quest1(void)
+{
+	script_init_quest(quest1, MLK_UTIL_SIZE(quest1));
+}
+
+static void
+terminate_quest1(struct mlk_action *act)
+{
+	static const char *line;
+
+	struct message *cur = act->data;
+	struct message *nxt = &quest1[3].msg;
+
+	nxt->linesz = 1;
+	nxt->lines = &line;
+
+	if (cur->index == 0)
+		 line = "Don't be so confident";
+	else
+		 line = "Nevermind, I'll do it myself";
+
+	message_query(nxt, NULL, &nxt->h);
+}
+
+static void
+terminate_quest2(struct mlk_action *act)
+{
+	static const char *line;
+
+	struct message *cur = act->data;
+	struct message *nxt = &quest2[2].msg;
+
+	nxt->linesz = 1;
+	nxt->lines = &line;
+
+	if (cur->index == 0)
+		line = "Go away!";
+	else
+		line = "Install OpenBSD then";
+
+	message_query(nxt, NULL, &nxt->h);
+}
+
+static void
+script_init_quest2(void)
+{
+	script_init_quest(quest2, MLK_UTIL_SIZE(quest2));
+}
+
+static void
+chests_init(void)
+{
+	/*
+	 * Chest #0 is at center 1st half of the screen.
+	 * Chest #1 is at center of 2nd half of the sreen.
+	 */
+	const unsigned int cw = registry_sprites[REGISTRY_TEXTURE_CHEST].cellw;
+	const unsigned int ch = registry_sprites[REGISTRY_TEXTURE_CHEST].cellh;
+
+	align(ALIGN_CENTER, &chests[0].x, &chests[0].y, cw, ch,
+	    0, 0, MLK_EXAMPLE_W / 2, MLK_EXAMPLE_H);
+	align(ALIGN_CENTER, &chests[1].x, &chests[1].y, cw, ch,
+	    MLK_EXAMPLE_W / 2, 0, MLK_EXAMPLE_W / 2, MLK_EXAMPLE_H);
+}
+
+static void
+label_init(void)
+{
+	unsigned int lw, lh;
+
+	label_query(&label, &lw, &lh);
+	align(ALIGN_CENTER, &label.x, &label.y, lw, lh,
+	    0, 0, MLK_EXAMPLE_W, MLK_EXAMPLE_H / 2);
+}
+
+static void
+init(void)
+{
+	int err;
+
+	if ((err = mlk_example_init("example-action")) < 0)
+		mlk_panicf("example_init: %s", mlk_err_string(err));
+
+	chests_init();
+	label_init();
+}
+
+static void
+handle(struct mlk_state *st, const union mlk_event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case MLK_EVENT_QUIT:
+		mlk_game_quit();
+		break;
+	default:
+		/*
+		 * The main script is "modal", we must not update anything else than
+		 * it until it's complete.
+		 */
+		if (mlk_action_script_completed(&script))
+			mlk_action_stack_handle(&chests_stack, ev);
+		else
+			mlk_action_script_handle(&script, ev);
+		break;
+	}
+}
+
+static void
+update(struct mlk_state *st, unsigned int ticks)
+{
+	(void)st;
+
+	mlk_action_stack_update(&chests_stack, ticks);
+
+	if (mlk_action_script_update(&script, ticks))
+		mlk_action_script_finish(&script);
+}
+
+static void
+draw(struct mlk_state *st)
+{
+	(void)st;
+
+	mlk_painter_set_color(0x4f8fbaff);
+	mlk_painter_clear();
+	label_draw(&label);
+	mlk_action_stack_draw(&chests_stack);
+
+	/*
+	 * Always draw the script in the end in case it overlaps everything
+	 * else.
+	 */
+	mlk_action_script_draw(&script);
+	mlk_painter_present();
+}
+
+static void
+run(void)
+{
+	struct mlk_state state = {
+		.handle = handle,
+		.update = update,
+		.draw = draw
+	};
+
+	mlk_game_init(states, MLK_UTIL_SIZE(states));
+	mlk_game_push(&state);
+	mlk_game_loop();
+}
+
+static void
+quit(void)
+{
+	mlk_example_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	(void)argc;
+	(void)argv;
+
+	init();
+	run();
+	quit();
+}
--- a/examples/example-animation/example-animation.c	Mon Feb 27 12:49:39 2023 +0100
+++ b/examples/example-animation/example-animation.c	Mon Feb 27 16:33:33 2023 +0100
@@ -145,4 +145,3 @@
 	run();
 	quit();
 }
-
--- a/libmlk-example/CMakeLists.txt	Mon Feb 27 12:49:39 2023 +0100
+++ b/libmlk-example/CMakeLists.txt	Mon Feb 27 16:33:33 2023 +0100
@@ -22,6 +22,8 @@
 	SOURCES
 	${libmlk-example_SOURCE_DIR}/mlk/example/character-john.c
 	${libmlk-example_SOURCE_DIR}/mlk/example/character-john.h
+	${libmlk-example_SOURCE_DIR}/mlk/example/example.c
+	${libmlk-example_SOURCE_DIR}/mlk/example/example.h
 	${libmlk-example_SOURCE_DIR}/mlk/example/registry.c
 	${libmlk-example_SOURCE_DIR}/mlk/example/registry.h
 	${libmlk-example_SOURCE_DIR}/mlk/example/spell-fire.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-example/mlk/example/example.c	Mon Feb 27 16:33:33 2023 +0100
@@ -0,0 +1,60 @@
+/*
+ * example.c -- libmlk-example entry point
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include <mlk/core/core.h>
+#include <mlk/core/err.h>
+#include <mlk/core/window.h>
+
+#include <mlk/ui/ui.h>
+
+#include <mlk/rpg/rpg.h>
+
+#include "example.h"
+#include "registry.h"
+
+int
+mlk_example_init(const char *name)
+{
+	assert(name);
+
+	int err;
+
+	if ((err = mlk_core_init("fr.malikania", name)) < 0)
+		return err;
+	if ((err = ui_init()) < 0)
+		return err;
+	if ((err = rpg_init()) < 0)
+		return err;
+	if (window_open(name, MLK_EXAMPLE_W, MLK_EXAMPLE_H) < 0)
+		return err;
+
+	registry_init();
+
+	return 0;
+}
+
+void
+mlk_example_finish(void)
+{
+	window_finish();
+	rpg_finish();
+	ui_finish();
+	mlk_core_finish();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-example/mlk/example/example.h	Mon Feb 27 16:33:33 2023 +0100
@@ -0,0 +1,31 @@
+/*
+ * example.h -- libmlk-example entry point
+ *
+ * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MLK_EXAMPLE_EXAMPLE_H
+#define MLK_EXAMPLE_EXAMPLE_H
+
+#define MLK_EXAMPLE_W 960
+#define MLK_EXAMPLE_H 540
+
+int
+mlk_example_init(const char *);
+
+void
+mlk_example_finish(void);
+
+#endif /* !MLK_EXAMPLE_EXAMPLE_H */
--- a/libmlk-example/mlk/example/registry.c	Mon Feb 27 12:49:39 2023 +0100
+++ b/libmlk-example/mlk/example/registry.c	Mon Feb 27 16:33:33 2023 +0100
@@ -29,6 +29,7 @@
 
 #include <assets/sounds/fire.h>
 
+#include <assets/sprites/chest.h>
 #include <assets/sprites/explosion.h>
 #include <assets/sprites/john-sword.h>
 #include <assets/sprites/john-walk.h>
@@ -36,10 +37,10 @@
 
 #include "registry.h"
 
-struct mlk_texture registry_images[REGISTRY_IMAGE_NUM];
-struct mlk_texture registry_textures[REGISTRY_TEXTURE_NUM];
-struct mlk_sprite registry_sprites[REGISTRY_TEXTURE_NUM];
-struct mlk_sound registry_sounds[REGISTRY_SOUND_NUM];
+struct mlk_texture registry_images[REGISTRY_IMAGE_LAST] = {0};
+struct mlk_texture registry_textures[REGISTRY_TEXTURE_LAST] = {0};
+struct mlk_sprite registry_sprites[REGISTRY_TEXTURE_LAST] = {0};
+struct mlk_sound registry_sounds[REGISTRY_SOUND_LAST] = {0};
 
 #define REGISTRY_IMAGE(i, data) \
 	{ (i), (data), sizeof (data) }
@@ -67,7 +68,8 @@
 	REGISTRY_TEXTURE(REGISTRY_TEXTURE_JOHN_SWORD, assets_sprites_john_sword, 256, 256),
 	REGISTRY_TEXTURE(REGISTRY_TEXTURE_JOHN_WALK, assets_sprites_john_walk, 256, 256),
 	REGISTRY_TEXTURE(REGISTRY_TEXTURE_HAUNTED_WOOD, assets_images_haunted_wood, 0, 0),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_BLACK_CAT, assets_images_black_cat, 0, 0)
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_BLACK_CAT, assets_images_black_cat, 0, 0),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_CHEST, assets_sprites_chest, 32, 32)
 };
 
 #define REGISTRY_SOUND(s, data) \
@@ -95,9 +97,12 @@
 static void
 load_textures_and_sprites(void)
 {
+	struct mlk_texture *texture;
+	struct mlk_sprite *sprite;
+
 	for (size_t i = 0; i < MLK_UTIL_SIZE(textures); ++i) {
-		struct mlk_texture *texture = &registry_textures[textures[i].index];
-		struct mlk_sprite *sprite = &registry_sprites[textures[i].index];
+		texture = &registry_textures[textures[i].index];
+		sprite = &registry_sprites[textures[i].index];
 
 		if (mlk_image_openmem(texture, textures[i].data, textures[i].datasz) < 0)
 			mlk_panic();
--- a/libmlk-example/mlk/example/registry.h	Mon Feb 27 12:49:39 2023 +0100
+++ b/libmlk-example/mlk/example/registry.h	Mon Feb 27 16:33:33 2023 +0100
@@ -38,24 +38,27 @@
 	REGISTRY_TEXTURE_HAUNTED_WOOD,
 	REGISTRY_TEXTURE_BLACK_CAT,
 
+	/* Objects. */
+	REGISTRY_TEXTURE_CHEST,
+
 	/* Unused.*/
-	REGISTRY_TEXTURE_NUM
+	REGISTRY_TEXTURE_LAST
 };
 
 enum registry_image {
 	REGISTRY_IMAGE_BATTLE_BACKGROUND,
-	REGISTRY_IMAGE_NUM
+	REGISTRY_IMAGE_LAST
 };
 
 enum registry_sound {
 	REGISTRY_SOUND_FIRE,
-	REGISTRY_SOUND_NUM
+	REGISTRY_SOUND_LAST
 };
 
-extern struct mlk_texture registry_images[REGISTRY_IMAGE_NUM];
-extern struct mlk_texture registry_textures[REGISTRY_TEXTURE_NUM];
-extern struct mlk_sprite registry_sprites[REGISTRY_TEXTURE_NUM];
-extern struct mlk_sound registry_sounds[REGISTRY_SOUND_NUM];
+extern struct mlk_texture registry_images[REGISTRY_IMAGE_LAST];
+extern struct mlk_texture registry_textures[REGISTRY_TEXTURE_LAST];
+extern struct mlk_sprite registry_sprites[REGISTRY_TEXTURE_LAST];
+extern struct mlk_sound registry_sounds[REGISTRY_SOUND_LAST];
 
 void
 registry_init(void);