changeset 162:629f55f3961e

core: rework states
author David Demelier <markand@malikania.fr>
date Sun, 18 Oct 2020 12:01:59 +0200
parents 31d7f23c0588
children 4bbfcd9180a8
files libadventure/CMakeLists.txt libadventure/adventure/assets/fonts/cubic.ttf libadventure/adventure/mainmenu_state.c libadventure/adventure/mainmenu_state.h libadventure/adventure/panic_state.c libadventure/adventure/panic_state.h libadventure/adventure/splashscreen_state.c libadventure/adventure/splashscreen_state.h libadventure/adventure/state/mainmenu.c libadventure/adventure/state/mainmenu.h libadventure/adventure/state/panic.c libadventure/adventure/state/panic.h libadventure/adventure/state/splashscreen.c libadventure/adventure/state/splashscreen.h libcore/CMakeLists.txt libcore/core/game.c libcore/core/panic.c libcore/core/state.c libcore/core/state.h librpg/rpg/map_state.c molko/main.c
diffstat 21 files changed, 941 insertions(+), 727 deletions(-) [+]
line wrap: on
line diff
--- a/libadventure/CMakeLists.txt	Sat Oct 17 10:12:41 2020 +0200
+++ b/libadventure/CMakeLists.txt	Sun Oct 18 12:01:59 2020 +0200
@@ -19,19 +19,24 @@
 project(libadventure)
 
 set(
+	STATE_SOURCES
+	${libadventure_SOURCE_DIR}/adventure/state/mainmenu.c
+	${libadventure_SOURCE_DIR}/adventure/state/mainmenu.h
+	${libadventure_SOURCE_DIR}/adventure/state/panic.c
+	${libadventure_SOURCE_DIR}/adventure/state/panic.h
+	${libadventure_SOURCE_DIR}/adventure/state/splashscreen.c
+	${libadventure_SOURCE_DIR}/adventure/state/splashscreen.h
+)
+
+set(
 	SOURCES
-	${libadventure_SOURCE_DIR}/adventure/mainmenu_state.c
-	${libadventure_SOURCE_DIR}/adventure/mainmenu_state.h
-	${libadventure_SOURCE_DIR}/adventure/panic_state.c
-	${libadventure_SOURCE_DIR}/adventure/panic_state.h
-	${libadventure_SOURCE_DIR}/adventure/splashscreen_state.c
-	${libadventure_SOURCE_DIR}/adventure/splashscreen_state.h
 	${libadventure_SOURCE_DIR}/adventure/trace_hud.c
 	${libadventure_SOURCE_DIR}/adventure/trace_hud.h
 )
 
 set(
 	ASSETS
+	${libadventure_SOURCE_DIR}/adventure/assets/fonts/cubic.ttf
 	${libadventure_SOURCE_DIR}/adventure/assets/fonts/lato.ttf
 	${libadventure_SOURCE_DIR}/adventure/assets/fonts/teutonic.ttf
 	${libadventure_SOURCE_DIR}/adventure/assets/fonts/pirata-one.ttf
@@ -42,7 +47,9 @@
 
 molko_define_library(
 	TARGET libadventure
-	SOURCES ${SOURCES}
+	SOURCES
+		${STATE_SOURCES}
+		${SOURCES}
 	ASSETS ${ASSETS}
 	LIBRARIES libcore libui librpg
 	PUBLIC_INCLUDES
@@ -50,3 +57,4 @@
 )
 
 source_group(adventure FILES ${SOURCES})
+source_group(adventure/state FILES ${STATE_SOURCES})
Binary file libadventure/adventure/assets/fonts/cubic.ttf has changed
--- a/libadventure/adventure/mainmenu_state.c	Sat Oct 17 10:12:41 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-/*
- * mainmenu_state.c -- game main menu
- *
- * Copyright (c) 2020 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 <stdlib.h>
-#include <string.h>
-
-#include <core/event.h>
-#include <core/font.h>
-#include <core/game.h>
-#include <core/image.h>
-#include <core/painter.h>
-#include <core/panic.h>
-#include <core/state.h>
-#include <core/sys.h>
-#include <core/texture.h>
-#include <core/window.h>
-
-#include <adventure/assets/fonts/pirata-one.h>
-#include <adventure/assets/maps/overworld.h>
-#include <adventure/assets/tilesets/world.h>
-#include <adventure/assets/sprites/john.h>
-
-#include <rpg/map_state.h>
-
-#include "mainmenu_state.h"
-#include "splashscreen_state.h"
-
-#define SPEED 120
-#define SEC   1000
-
-enum substate {
-	SUBSTATE_MOVING,
-	SUBSTATE_WAITING
-};
-
-static int x;
-static int y;
-static unsigned int selection;
-static int destination;
-static enum substate substate;
-static struct texture image;
-static struct texture tileset;
-
-/* Menu items. */
-static struct {
-	struct texture texture;
-	int x;
-	int y;
-} items[3];
-
-static void
-enter(void)
-{
-	struct font font;
-
-	if (!font_openmem(&font, fonts_pirata_one, sizeof (fonts_pirata_one), 30))
-		panic();
-
-	substate = SUBSTATE_MOVING;
-	x = splashscreen_state_data.x;
-	y = splashscreen_state_data.y;
-	destination = window.h / 4;
-
-	/* TODO: change continue color if no game exists. */
-	font_render(&font, &items[0].texture, "New game");
-	items[0].x = (window.w / 2) - (items[0].texture.w / 2);
-	items[0].y = window.h * 0.75;
-
-	font_render(&font, &items[1].texture, "Continue");
-	items[1].x = items[0].x;
-	items[1].y = items[0].y + items[0].texture.h;
-
-	font_render(&font, &items[2].texture, "Quit");
-	items[2].x = items[0].x;
-	items[2].y = items[1].y + items[1].texture.h;
-
-	font_finish(&font);
-}
-
-static void
-new(void)
-{
-	/* Prepare map. */
-	if (!map_data_openmem(&map_state_data.map.data, maps_overworld, sizeof (maps_overworld)))
-		panicf("Unable to open map 'test'");
-
-	// TODO: this is temporary.
-	if (!image_openmem(&tileset, tilesets_world, sizeof (tilesets_world)))
-		panic();
-	
-	if (!map_init(&map_state_data.map.map, &map_state_data.map.data, &tileset))
-		panic();
-
-	/* Prepare image and sprite. */
-	if (!(image_openmem(&image, sprites_john, sizeof (sprites_john))))
-		panic();
-
-	sprite_init(&map_state_data.player.sprite, &image, 48, 48);
-	game_switch(&map_state, false);
-}
-
-static void
-resume(void)
-{
-}
-
-static void
-quit(void)
-{
-	game_quit();
-}
-
-static void
-perform(void)
-{
-	assert(selection < 3);
-
-	static void (*handlers[])(void) = {
-		[0] = new,
-		[1] = resume,
-		[2] = quit
-	};
-
-	handlers[selection]();
-}
-
-static void
-handle(const union event *event)
-{
-	if (substate != SUBSTATE_WAITING)
-		return;
-
-	switch (event->type) {
-	case EVENT_KEYDOWN:
-		switch (event->key.key) {
-		case KEY_UP:
-			selection = selection == 0 ? 2 : selection - 1;
-			break;
-		case KEY_DOWN:
-			selection = (selection + 1) % 3;
-			break;
-		case KEY_ENTER:
-			perform();
-		default:
-			break;
-		}
-		break;
-	default:
-		break;
-	}
-}
-
-static void
-update(unsigned int ticks)
-{
-	switch (substate) {
-	case SUBSTATE_MOVING:
-		y -= SPEED * ticks / SEC;
-
-		if (y <= destination) {
-			y = destination;
-			substate = SUBSTATE_WAITING;
-		}
-
-		break;
-	default:
-		break;
-	}
-}
-
-static void
-draw(void)
-{
-	painter_set_color(0xffffffff);
-	painter_clear();
-	texture_draw(&splashscreen_state_data.text, x, y);
-
-	if (substate == SUBSTATE_WAITING) {
-		texture_draw(&items[0].texture, items[0].x, items[0].y);
-		texture_draw(&items[1].texture, items[1].x, items[1].y);
-		texture_draw(&items[2].texture, items[2].x, items[2].y);
-
-		/* Selection cursor. */
-
-		/* TODO: a sword here. */
-		painter_set_color(0x000000ff);
-		painter_draw_rectangle(items[selection].x - 30,
-		    items[selection].y + 11, 15, 15);
-	}
-}
-
-static void
-leave(void)
-{
-	texture_finish(&items[0].texture);
-	texture_finish(&items[1].texture);
-	memset(items, 0, sizeof (items));
-}
-
-struct state mainmenu_state = {
-	.enter = enter,
-	.handle = handle,
-	.update = update,
-	.draw = draw,
-	.leave = leave
-};
--- a/libadventure/adventure/mainmenu_state.h	Sat Oct 17 10:12:41 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-/*
- * mainmenu_state.h -- game main menu
- *
- * Copyright (c) 2020 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 MOLKO_MAINMENU_STATE_H
-#define MOLKO_MAINMENU_STATE_H
-
-/**
- * \file mainmenu_state.h
- * \brief Game main menu.
- */
-
-/**
- * \brief Main menu state.
- */
-extern struct state mainmenu_state;
-
-#endif /* !MOLKO_MAINMENU_STATE_H */
--- a/libadventure/adventure/panic_state.c	Sat Oct 17 10:12:41 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,228 +0,0 @@
-/*
- * panic_state.c -- panic state
- *
- * Copyright (c) 2020 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 <stdio.h>
-#include <stdlib.h>
-
-#include <core/error.h>
-#include <core/event.h>
-#include <core/font.h>
-#include <core/game.h>
-#include <core/painter.h>
-#include <core/sys.h>
-#include <core/texture.h>
-#include <core/util.h>
-#include <core/window.h>
-
-#include <rpg/map_state.h>
-
-#include <adventure/assets/fonts/lato.h>
-
-#include "panic_state.h"
-
-#define BACKGROUND 0x4f5070ff
-#define FOREGROUND 0xffffffff
-
-#define SIZE 16
-#define PADDING 20
-
-#define OUT "molko-adventure.txt"
-
-struct label {
-	const char *text;
-	struct texture texture;
-};
-
-static struct {
-	struct font font;
-} data;
-
-static struct label headers[] = {
-	{ .text = "An unrecoverable error occured and the game cannot continue." },
-	{ .text = "Please report the detailed error as provided below." },
-};
-
-static struct label bottom[] = {
-	{ .text = "Press <s> to save information and generate a core dump." },
-	{ .text = "Press <q> to quit without saving information." }
-};
-
-static struct label lerror;
-
-static void
-die(const char *fmt, ...)
-{
-	assert(fmt);
-
-	va_list ap;
-
-	va_start(ap, fmt);
-	fprintf(stderr, "abort: ");
-	vfprintf(stderr, fmt, ap);
-	va_end(ap);
-	exit(1);
-}
-
-static void
-enter(void)
-{
-}
-
-static void
-dump(void)
-{
-	FILE *fp;
-
-	if (!(fp = fopen(OUT, "w")))
-		goto dump;
-
-	/* Print various information. */
-	fprintf(fp, "Molko's Adventure crash dump report\n");
-	fprintf(fp, "== state map dump ==\n");
-	fprintf(fp, "map:\n");
-	fprintf(fp, "  w     %u\n", map_state_data.map.data.w);
-	fprintf(fp, "  h     %u\n", map_state_data.map.data.h);
-	fprintf(fp, "player:\n");
-	fprintf(fp, "  x:    %u\n", map_state_data.player.x);
-	fprintf(fp, "  y:    %u\n", map_state_data.player.y);
-	fprintf(fp, "view:\n");
-	fprintf(fp, "  w:    %u\n", map_state_data.view.w);
-	fprintf(fp, "  h:    %u\n", map_state_data.view.h);
-	fprintf(fp, "  x:    %u\n", map_state_data.view.x);
-	fprintf(fp, "  y:    %u\n", map_state_data.view.y);
-
-dump:
-	if (fp)
-		fclose(fp);
-
-	abort();
-}
-
-static void
-handle_keydown(const struct event_key *ev)
-{
-	assert(ev);
-
-	switch (ev->key) {
-	case KEY_q:
-		game_quit();
-		break;
-	case KEY_s:
-		dump();
-		break;
-	default:
-		break;
-	}
-}
-
-static void
-handle(const union event *ev)
-{
-	assert(ev);
-
-	switch (ev->type) {
-	case EVENT_KEYDOWN:
-		handle_keydown(&ev->key);
-		break;
-	default:
-		break;
-	}
-}
-
-static void
-generate(struct label labels[], size_t labelsz)
-{
-	assert(labels);
-
-	for (size_t i = 0; i < labelsz; ++i) {
-		if (texture_ok(&labels[i].texture))
-			continue;
-
-		data.font.color = FOREGROUND;
-		font_render(&data.font, &labels[i].texture, labels[i].text);
-
-		if (!texture_ok(&labels[i].texture))
-			die("%s\n", error());
-	}
-}
-
-static void
-update(unsigned int ticks)
-{
-	(void)ticks;
-
-	lerror.text = error();
-
-	generate(headers, NELEM(headers));
-	generate(&lerror, 1);
-	generate(bottom, NELEM(bottom));
-}
-
-static void
-draw(void)
-{
-	int y = PADDING;
-
-	painter_set_target(NULL);
-	painter_set_color(BACKGROUND);
-	painter_clear();
-
-	/* Header. */
-	for (size_t i = 0; i < NELEM(headers); ++i) {
-		texture_draw(&headers[i].texture, PADDING, y);
-		y += headers[i].texture.h + 2;
-	}
-
-	/* Error message. */
-	texture_draw(&lerror.texture, PADDING, y + PADDING);
-
-	/* Bottom. */
-	y = window.h - PADDING;
-	y -= bottom[0].texture.h;
-
-	for (size_t i = 0; i < NELEM(bottom); ++i) {
-		texture_draw(&bottom[i].texture, PADDING, y);
-		y -= bottom[i].texture.h + 2;
-	}
-}
-
-static void
-leave(void)
-{
-}
-
-struct state panic_state = {
-	.enter = enter,
-	.handle = handle,
-	.update = update,
-	.draw = draw,
-	.leave = leave
-};
-
-void
-panic_state_init(void)
-{
-	/*
-	 * If the panic state can not be loaded we're unable to show any
-	 * useful information to the screen so as last resort print them
-	 * on the console.
-	 */
-	if (!(font_openmem(&data.font, fonts_lato, sizeof (fonts_lato), SIZE)))
-		die("%s", error());
-}
--- a/libadventure/adventure/panic_state.h	Sat Oct 17 10:12:41 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/*
- * panic_state.h -- panic state
- *
- * Copyright (c) 2020 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 MOLKO_PANIC_STATE_H
-#define MOLKO_PANIC_STATE_H
-
-/**
- * \file panic_state.h
- * \brief Panic state.
- */
-
-#include <core/state.h>
-
-/**
- * \brief Global panic state structure.
- */
-extern struct state panic_state;
-
-/**
- * Call this function as early as possible to be able to use this state even on
- * memory allocation errors.
- *
- * \note You must still initialize the system before.
- * \see \ref core_init
- * \see \ref ui_init
- * \see \ref window_open
- */
-void
-panic_state_init(void);
-
-#endif /* !MOLKO_PANIC_STATE_H */
--- a/libadventure/adventure/splashscreen_state.c	Sat Oct 17 10:12:41 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/*
- * splashscreen_state.c -- splash screen state
- *
- * Copyright (c) 2020 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 <core/font.h>
-#include <core/game.h>
-#include <core/image.h>
-#include <core/painter.h>
-#include <core/panic.h>
-#include <core/state.h>
-#include <core/sys.h>
-#include <core/texture.h>
-#include <core/window.h>
-
-#include <rpg/map.h>
-#include <rpg/map_state.h>
-
-#include <adventure/assets/fonts/teutonic.h>
-
-#include "splashscreen_state.h"
-#include "mainmenu_state.h"
-
-#define DELAY 2000
-
-struct splashscreen_state_data splashscreen_state_data;
-
-static void
-enter(void)
-{
-	struct font font = {
-		.color = 0x000000ff
-	};
-
-	if (!(font_openmem(&font, fonts_teutonic, sizeof (fonts_teutonic), 130)))
-		panic();
-	if (!(font_render(&font, &splashscreen_state_data.text, "Molko's Adventure")))
-		panic();
-
-	/* Compute position. */
-	const unsigned int w = splashscreen_state_data.text.w;
-	const unsigned int h = splashscreen_state_data.text.h;
-
-	splashscreen_state_data.x = (window.w / 2) - (w / 2);
-	splashscreen_state_data.y = (window.h / 2) - (h / 2);
-
-	font_finish(&font);
-}
-
-static void
-leave(void)
-{
-	/* We don't delete the texture because it is used by mainmenu_state. */
-}
-
-static void
-handle(const union event *event)
-{
-	(void)event;
-}
-
-static void
-update(unsigned int ticks)
-{
-	splashscreen_state_data.elapsed += ticks;
-
-	if (splashscreen_state_data.elapsed >= DELAY)
-		game_switch(&mainmenu_state, false);
-}
-
-static void
-draw(void)
-{
-	painter_set_color(0xffffffff);
-	painter_clear();
-	texture_draw(&splashscreen_state_data.text,
-		splashscreen_state_data.x,
-		splashscreen_state_data.y);
-}
-
-struct state splashscreen_state = {
-	.enter = enter,
-	.leave = leave,
-	.handle = handle,
-	.update = update,
-	.draw = draw
-};
--- a/libadventure/adventure/splashscreen_state.h	Sat Oct 17 10:12:41 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/*
- * splashscreen_state.h -- splash screen state
- *
- * Copyright (c) 2020 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 MOLKO_SPLASHSCREEN_ADVENTURE_H
-#define MOLKO_SPLASHSCREEN_ADVENTURE_H
-
-/**
- * \file splashscreen_state.h
- * \brief Splash screen state.
- */
-
-#include <core/texture.h>
-
-/**
- * \brief Data for splashscreen.
- */
-extern struct splashscreen_state_data {
-	struct texture text;            /*!< (+) Texture for the text. */
-	int x;                          /*!< (+) Position in x. */
-	int y;                          /*!< (+) Position in y. */
-	unsigned int elapsed;           /*!< (+) Time elapsed. */
-} splashscreen_state_data;              /*!< (+) Global state data. */
-
-/**
- * \brief Splash screen state.
- */
-extern struct state splashscreen_state;
-
-#endif /* !MOLKO_SPLASHSCREEN_ADVENTURE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libadventure/adventure/state/mainmenu.c	Sun Oct 18 12:01:59 2020 +0200
@@ -0,0 +1,207 @@
+/*
+ * mainmenu.c -- game main menu
+ *
+ * Copyright (c) 2020 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 <stdlib.h>
+#include <string.h>
+
+#include <core/event.h>
+#include <core/font.h>
+#include <core/game.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/texture.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/align.h>
+#include <ui/label.h>
+#include <ui/theme.h>
+
+#include <adventure/assets/fonts/teutonic.h>
+#include <adventure/assets/fonts/pirata-one.h>
+
+#include "mainmenu.h"
+
+struct mainmenu {
+	struct {
+		struct texture tex;
+		int x;
+		int y;
+	} texts[4];
+
+	unsigned int itemsel;           /* Selected item. */
+};
+
+static void
+new(void)
+{
+	/* TODO: implement here. */
+}
+
+static void
+resume(void)
+{
+	/* TODO: implement here. */
+}
+
+static void
+quit(void)
+{
+	game_quit();
+}
+
+static void
+perform(struct mainmenu *main)
+{
+	assert(main->itemsel < 3);
+
+	static void (*handlers[])(void) = {
+		[0] = new,
+		[1] = resume,
+		[2] = quit
+	};
+
+	handlers[main->itemsel]();
+}
+
+static void
+init_title(struct mainmenu *main, struct font *font)
+{
+	if (!font_render(font, &main->texts[3].tex, "Molko's Adventure"))
+		panic();
+	
+	/* Align header. */
+	align(ALIGN_CENTER, &main->texts[3].x, NULL, main->texts[3].tex.w, main->texts[3].tex.h,
+	    0, 0, window.w, window.h);
+
+	main->texts[3].y = main->texts[3].x;
+}
+
+static void
+init_items(struct mainmenu *main, struct font *font)
+{
+	if (!font_render(font, &main->texts[0].tex, "New") ||
+	    !font_render(font, &main->texts[1].tex, "Continue") ||
+	    !font_render(font, &main->texts[2].tex, "Quit"))
+		panic();
+
+	main->texts[0].x = (window.w / 2) - (main->texts[0].tex.w / 2);
+	main->texts[0].y = window.h * 0.75;
+
+	main->texts[1].x = main->texts[0].x;
+	main->texts[1].y = main->texts[0].y + main->texts[0].tex.h;
+
+	main->texts[2].x = main->texts[0].x;
+	main->texts[2].y = main->texts[1].y + main->texts[1].tex.h;
+}
+
+static void
+start(struct state *state)
+{
+	struct mainmenu *main;
+	struct font fonts[2];
+
+	/* Allocate the main menu data. */
+	main = (state->data = ecalloc(1, sizeof (*main)));
+
+	if (!font_openmem(&fonts[0], fonts_teutonic, sizeof (fonts_teutonic), 130) ||
+	    !font_openmem(&fonts[1], fonts_pirata_one, sizeof (fonts_pirata_one), 30))
+		panic();
+
+	fonts[0].color = fonts[1].color = 0x000000ff;
+	fonts[0].style = fonts[1].style = FONT_STYLE_ANTIALIASED;
+
+	init_title(main, &fonts[0]);
+	init_items(main, &fonts[1]);
+
+	font_finish(&fonts[0]);
+	font_finish(&fonts[1]);
+}
+
+static void
+handle(struct state *state, const union event *event)
+{
+	struct mainmenu *main = state->data;
+
+	switch (event->type) {
+	case EVENT_KEYDOWN:
+		switch (event->key.key) {
+		case KEY_UP:
+			main->itemsel = main->itemsel == 0 ? 2 : main->itemsel - 1;
+			break;
+		case KEY_DOWN:
+			main->itemsel = (main->itemsel + 1) % 3;
+			break;
+		case KEY_ENTER:
+			perform(main);
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *state)
+{
+	struct mainmenu *main = state->data;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+
+	for (size_t i = 0; i < NELEM(main->texts); ++i)
+		texture_draw(&main->texts[i].tex, main->texts[i].x, main->texts[i].y);
+
+	/* TODO: a sword here. */
+	painter_set_color(0x000000ff);
+	painter_draw_rectangle(
+	    main->texts[main->itemsel].x - 30,
+	    main->texts[main->itemsel].y + 11, 15, 15);
+}
+
+static void
+finish(struct state *state)
+{
+	struct mainmenu *main = state->data;
+
+	if (!main)
+		return;
+
+	for (size_t i = 0; i < NELEM(main->texts); ++i)
+		texture_finish(&main->texts[i].tex);
+
+	free(main);
+	memset(state, 0, sizeof (*state));
+}
+
+void
+mainmenu_state(struct state *state)
+{
+	assert(state);
+
+	memset(state, 0, sizeof (*state));
+	state->start = start;
+	state->handle = handle;
+	state->draw = draw;
+	state->finish = finish;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libadventure/adventure/state/mainmenu.h	Sun Oct 18 12:01:59 2020 +0200
@@ -0,0 +1,41 @@
+/*
+ * mainmenu.h -- game main menu
+ *
+ * Copyright (c) 2020 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 MOLKO_STATE_MAINMENU_H
+#define MOLKO_STATE_MAINMENU_H
+
+/**
+ * \file mainmenu.h
+ * \brief Game main menu.
+ */
+
+/**
+ * Create a state about the main game menu.
+ *
+ * \pre state != NULL
+ * \param state the state to initialize
+ * \post state->data is set internal data
+ * \post state->handle is set
+ * \post state->draw is set
+ * \post state->update is set
+ * \post state->finish is set
+ */
+void
+mainmenu_state(struct state *state);
+
+#endif /* !MOLKO_STATE_PANIC_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libadventure/adventure/state/panic.c	Sun Oct 18 12:01:59 2020 +0200
@@ -0,0 +1,225 @@
+/*
+ * panic_state.c -- panic state
+ *
+ * Copyright (c) 2020 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 <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <string.h>
+
+#include <core/error.h>
+#include <core/event.h>
+#include <core/font.h>
+#include <core/game.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/texture.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/align.h>
+#include <ui/theme.h>
+
+#include <rpg/map_state.h>
+
+#include "panic.h"
+
+#define BACKGROUND 0x4f5070ff
+#define FOREGROUND 0xffffffff
+
+#define OUT "molko-adventure.txt"
+
+struct view {
+	struct {
+		struct texture tex;
+		int x;
+		int y;
+	} texts[4];
+};
+
+static noreturn void
+die(const char *fmt, ...)
+{
+	assert(fmt);
+
+	va_list ap;
+
+	va_start(ap, fmt);
+	fprintf(stderr, "abort: ");
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	abort();
+	exit(1);
+}
+
+static noreturn void
+stop(void)
+{
+	die("%s", error());
+}
+
+static void
+dump(void)
+{
+	FILE *fp;
+
+	if ((fp = fopen(OUT, "w"))) {
+		/* TODO: add more info here. */
+		fprintf(fp, "Molko's Adventure crash dump report\n");
+		fclose(fp);
+	}
+
+	abort();
+}
+
+static struct view *
+init(void)
+{
+	struct theme *theme;
+	struct view *view;
+	struct font font;
+
+	theme = theme_default();
+	view = ecalloc(1, sizeof (*view));
+
+	/* Generate the texts. */
+	font_shallow(&font, theme->fonts[THEME_FONT_INTERFACE]);
+	font.style = FONT_STYLE_ANTIALIASED,
+	font.color = FOREGROUND;
+
+	if (!font_render(&font, &view->texts[0].tex, "An unrecoverable error occured and the game cannot continue.") ||
+	    !font_render(&font, &view->texts[1].tex, "Please report the detailed error as provided below.") ||
+	    !font_render(&font, &view->texts[2].tex, "Press <s> to save information and generate a core dump.") ||
+	    !font_render(&font, &view->texts[3].tex, "Press <q> to quit without saving information."))
+		die("%s", error());
+
+	/* All align x the same. */
+	for (size_t i = 0; i < NELEM(view->texts); ++i)
+		view->texts[i].x = theme->padding;
+
+	/* Header (0-1). */
+	view->texts[0].y = theme->padding;
+	view->texts[1].y = view->texts[0].y + view->texts[0].tex.h + theme->padding;
+
+	/* Footer. (2-3). */
+	view->texts[3].y = window.h - view->texts[2].tex.h - theme->padding;
+	view->texts[2].y = view->texts[3].y - view->texts[3].tex.h - theme->padding;
+
+	return view;
+}
+
+static void
+handle_keydown(const struct event_key *ev)
+{
+	assert(ev);
+
+	switch (ev->key) {
+	case KEY_q:
+		game_quit();
+		break;
+	case KEY_s:
+		dump();
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+start(struct state *state)
+{
+	(void)state;
+
+	/* We remove the panic handler to avoid infinite recursion. */
+	panic_handler = stop;
+}
+
+static void
+handle(struct state *state, const union event *ev)
+{
+	assert(ev);
+
+	(void)state;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		handle_keydown(&ev->key);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw(struct state *state)
+{
+	struct theme *theme = theme_default();
+	struct view *view = state->data;
+	struct texture tex;
+	struct font font;
+	int x, y;
+
+	painter_set_color(BACKGROUND);
+	painter_clear();
+
+	for (size_t i = 0; i < NELEM(view->texts); ++i)
+		texture_draw(&view->texts[i].tex, view->texts[i].x, view->texts[i].y);
+
+	/* The error is only available here. */
+	font_shallow(&font, theme->fonts[THEME_FONT_INTERFACE]);
+	font.color = FOREGROUND;
+	font.style = FONT_STYLE_ANTIALIASED;
+
+	if (!font_render(&font, &tex, error()))
+		die("%s\n", error());
+
+	align(ALIGN_LEFT, &x, &y, tex.w, tex.h, 0, 0, window.w, window.h);
+
+	texture_draw(&tex, x + theme->padding, y);
+	texture_finish(&tex);
+}
+
+static void
+finish(struct state *state)
+{
+	struct view *view = state->data;
+
+	if (!view)
+		return;
+
+	for (size_t i = 0; i < NELEM(view->texts); ++i)
+		texture_finish(&view->texts[i].tex);
+
+	free(view);
+	memset(state, 0, sizeof (*state));
+}
+
+void
+panic_state(struct state *state)
+{
+	assert(state);
+
+	memset(state, 0, sizeof (*state));
+	state->data = init();
+	state->start = start;
+	state->handle = handle;
+	state->draw = draw;
+	state->finish = finish;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libadventure/adventure/state/panic.h	Sun Oct 18 12:01:59 2020 +0200
@@ -0,0 +1,38 @@
+/*
+ * panic_state.h -- panic state
+ *
+ * Copyright (c) 2020 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 MOLKO_STATE_PANIC_H
+#define MOLKO_STATE_PANIC_H
+
+/**
+ * \file panic.h
+ * \brief Panic state.
+ */
+
+struct state;
+
+/**
+ * Create a state in case of panic.
+ *
+ * \pre state != NULL
+ * \param state the state to initialize
+ */
+void
+panic_state(struct state *state);
+
+#endif /* !MOLKO_STATE_PANIC_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libadventure/adventure/state/splashscreen.c	Sun Oct 18 12:01:59 2020 +0200
@@ -0,0 +1,122 @@
+/*
+ * splashscreen.c -- splash screen state
+ *
+ * Copyright (c) 2020 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 <stdlib.h>
+#include <string.h>
+
+#include <core/font.h>
+#include <core/game.h>
+#include <core/image.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/state.h>
+#include <core/sys.h>
+#include <core/texture.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/align.h>
+
+#include <adventure/assets/fonts/cubic.h>
+
+#include "splashscreen.h"
+#include "mainmenu.h"
+
+#define DELAY 3000
+
+struct splashscreen {
+	struct texture tex;
+	int x;
+	int y;
+	struct state *next;
+	unsigned int elapsed;
+};
+
+static struct splashscreen *
+init(struct state *next)
+{
+	struct splashscreen *splash;
+	struct font font;
+
+	splash = ecalloc(1, sizeof (*splash));
+	splash->next = next;
+
+	if (!font_openmem(&font, fonts_cubic, sizeof (fonts_cubic), 80))
+		panic();
+
+	font.color = 0x19332dff;
+	font.style = FONT_STYLE_ANTIALIASED;
+
+	if (!font_render(&font, &splash->tex, "malikania"))
+		panic();
+
+	align(ALIGN_CENTER, &splash->x, &splash->y, splash->tex.w, splash->tex.h,
+	    0, 0, window.w, window.h);
+	font_finish(&font);
+
+	return splash;
+}
+
+static void
+update(struct state *state, unsigned int ticks)
+{
+	struct splashscreen *splash = state->data;
+
+	splash->elapsed += ticks;
+
+	if (splash->elapsed >= DELAY)
+		game_switch(splash->next, false);
+}
+
+static void
+draw(struct state *state)
+{
+	struct splashscreen *splash = state->data;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+	texture_draw(&splash->tex, splash->x, splash->y);
+}
+
+static void
+finish(struct state *state)
+{
+	struct splashscreen *splash = state->data;
+
+	if (!splash)
+		return;
+
+	texture_finish(&splash->tex);
+
+	free(splash);
+	memset(state, 0, sizeof (*state));
+}
+
+void
+splashscreen_state(struct state *state, struct state *next)
+{
+	assert(state);
+	assert(next);
+
+	memset(state, 0, sizeof (*state));
+	state->data = init(next);
+	state->update = update;
+	state->draw = draw;
+	state->finish = finish;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libadventure/adventure/state/splashscreen.h	Sun Oct 18 12:01:59 2020 +0200
@@ -0,0 +1,42 @@
+/*
+ * splashscreen.h -- splash screen state
+ *
+ * Copyright (c) 2020 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 MOLKO_STATE_SPLASHSCREEN_H
+#define MOLKO_STATE_SPLASHSCREEN_H
+
+/**
+ * \file splashscreen.h
+ * \brief Splash screen state.
+ */
+
+/**
+ * Create a state for showing a splashcreen.
+ *
+ * \pre state != NULL
+ * \pre next != NULL
+ * \param state the state to initialize
+ * \param next the next state once it's finished
+ * \post state->data is set internal data
+ * \post state->draw is set
+ * \post state->update is set
+ * \post state->finish is set
+ */
+void
+splashscreen_state(struct state *state, struct state *next);
+
+#endif /* !MOLKO_STATE_SPLASHSCREEN_H */
--- a/libcore/CMakeLists.txt	Sat Oct 17 10:12:41 2020 +0200
+++ b/libcore/CMakeLists.txt	Sun Oct 18 12:01:59 2020 +0200
@@ -63,6 +63,7 @@
 	${libcore_SOURCE_DIR}/core/sound.h
 	${libcore_SOURCE_DIR}/core/sprite.c
 	${libcore_SOURCE_DIR}/core/sprite.h
+	${libcore_SOURCE_DIR}/core/state.c
 	${libcore_SOURCE_DIR}/core/state.h
 	${libcore_SOURCE_DIR}/core/sys.c
 	${libcore_SOURCE_DIR}/core/sys.h
--- a/libcore/core/game.c	Sat Oct 17 10:12:41 2020 +0200
+++ b/libcore/core/game.c	Sun Oct 18 12:01:59 2020 +0200
@@ -32,47 +32,63 @@
 	assert(state);
 
 	if (quick) {
+		if (game.state_next)
+			state_finish(game.state_next);
+
 		game.state_next = NULL;
 		game.state = state;
-		game.state->enter();
+		state_start(game.state);
 	} else
 		game.state_next = state;
 }
 
 void
-game_handle(const union event *event)
+game_handle(const union event *ev)
 {
-	assert(event);
+	assert(ev);
 
 	if (game.state && !(game.inhibit & INHIBIT_STATE_INPUT))
-		game.state->handle(event);
+		state_handle(game.state, ev);
 }
 
 void
 game_update(unsigned int ticks)
 {
-	if (!(game.inhibit & INHIBIT_STATE_UPDATE)) {
-		/* Change state if any. */
-		if (game.state_next) {
-			/* Inform the current state we're gonna leave it. */
-			if (game.state)
-				game.state->leave();
+	if (game.inhibit & INHIBIT_STATE_UPDATE)
+		return;
+
+	/* Change state if any. */
+	if (game.state_next) {
+		struct state *previous;
+
+		/* Inform the current state we're gonna leave it. */
+		if ((previous = game.state))
+			state_end(previous);
 
-			game.state = game.state_next;
-			game.state->enter();
-			game.state_next = NULL;
-		}
+		/* Change the state and tell we're starting it. */
+		if ((game.state = game.state_next))
+			state_start(game.state);
+
+		game.state_next = NULL;
 
-		if (game.state)
-			game.state->update(ticks);
+		/*
+		 * Only call finish at the end of the process because
+		 * the user may still use resources from it during the
+		 * transition.
+		 */
+		if (previous)
+			state_finish(previous);
 	}
+
+	if (game.state)
+		state_update(game.state, ticks);
 }
 
 void
 game_draw(void)
 {
 	if (game.state && !(game.inhibit & INHIBIT_STATE_DRAW))
-		game.state->draw();
+		state_draw(game.state);
 
 	painter_present();
 }
@@ -80,8 +96,15 @@
 void
 game_quit(void)
 {
-	if (game.state && game.state->leave)
-		game.state->leave();
-
-	game.state = NULL;
+	/* Close the next state if any. */
+	if (game.state_next) {
+		state_finish(game.state_next);
+		game.state_next = NULL;
+	}
+	
+	if (game.state) {
+		state_end(game.state);
+		state_finish(game.state);
+		game.state = NULL;
+	}
 }
--- a/libcore/core/panic.c	Sat Oct 17 10:12:41 2020 +0200
+++ b/libcore/core/panic.c	Sun Oct 18 12:01:59 2020 +0200
@@ -26,7 +26,8 @@
 static noreturn void
 terminate(void)
 {
-	fprintf(stderr, "abort: %s", error());
+	fprintf(stderr, "abort: %s\n", error());
+	abort();
 	exit(1);
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcore/core/state.c	Sun Oct 18 12:01:59 2020 +0200
@@ -0,0 +1,76 @@
+/*
+ * state.c -- abstract state
+ *
+ * Copyright (c) 2020 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 "state.h"
+
+void
+state_start(struct state *state)
+{
+	assert(state);
+
+	if (state->start)
+		state->start(state);
+}
+
+void
+state_handle(struct state *state, const union event *ev)
+{
+	assert(state);
+	assert(ev);
+
+	if (state->handle)
+		state->handle(state, ev);
+}
+
+void
+state_update(struct state *state, unsigned int ticks)
+{
+	assert(state);
+
+	if (state->update)
+		state->update(state, ticks);
+}
+
+void
+state_draw(struct state *state)
+{
+	assert(state);
+
+	if (state->draw)
+		state->draw(state);
+}
+
+void
+state_end(struct state *state)
+{
+	assert(state);
+
+	if (state->end)
+		state->end(state);
+}
+
+void
+state_finish(struct state *state)
+{
+	assert(state);
+
+	if (state->finish)
+		state->finish(state);
+}
--- a/libcore/core/state.h	Sat Oct 17 10:12:41 2020 +0200
+++ b/libcore/core/state.h	Sun Oct 18 12:01:59 2020 +0200
@@ -23,6 +23,13 @@
  * \file state.h
  * \brief Abstract state.
  * \ingroup states
+ *
+ * The state module is a facility that allows changing game context with ease
+ * using a single \ref game_switch routine.
+ *
+ * The user creates any state required, set appropriate functions if needed and
+ * place them in the game using \ref game_switch. Then function \ref game_handle
+ * \ref game_update and finally \ref game_draw.
  */
 
 union event;
@@ -32,32 +39,117 @@
  */
 struct state {
 	/**
-	 * (+?) This function is called when the state is entered.
+	 * (+&?) Optional user data.
 	 */
-	void (*enter)(void);
+	void *data;
 
 	/**
-	 * (+?) This function is called when the state is about to be left.
+	 * (+?) This function is called when the state is about to begin.
+	 *
+	 * \param state this state
 	 */
-	void (*leave)(void);
+	void (*start)(struct state *state);
 
 	/**
 	 * (+) This function is called for each event that happened.
+	 *
+	 * \param state this state
+	 * \param ev the event
 	 */
-	void (*handle)(const union event *);
+	void (*handle)(struct state *state, const union event *);
 
 	/**
 	 * (+) Update the state.
 	 *
 	 * This function is called to update the game, with the number of
 	 * milliseconds since the last frame.
+	 *
+	 * \param state this state
+	 * \param ev the event
 	 */
-	void (*update)(unsigned int ticks);
+	void (*update)(struct state *state, unsigned int ticks);
 
 	/**
 	 * (+) This function is supposed to draw the game.
+	 *
+	 * \param state this state
 	 */
-	void (*draw)(void);
+	void (*draw)(struct state *state);
+
+	/**
+	 * (+?) This function is called when the state is about to be switched
+	 * away from.
+	 *
+	 * This function is not called in case `quick` is set to true when
+	 * calling \ref game_switch function.
+	 *
+	 * \param state this state
+	 */
+	void (*end)(struct state *state);
+
+	/**
+	 * (+?) This function is called to close resources if necessary.
+	 *
+	 * \param state the state
+	 */
+	void (*finish)(struct state *state);
 };
 
+/**
+ * Shortcut for state->start (if not NULL)
+ *
+ * \pre state != NULL
+ * \param state the state
+ */
+void
+state_start(struct state *state);
+
+/**
+ * Shortcut for state->handle (if not NULL)
+ *
+ * \pre state != NULL
+ * \pre ev != NULL
+ * \param state the state
+ * \param ev the event
+ */
+void
+state_handle(struct state *state, const union event *ev);
+
+/**
+ * Shortcut for state->update (if not NULL)
+ *
+ * \pre state != NULL
+ * \param state the state
+ * \param ticks elapsed milliseconds since last frame
+ */
+void
+state_update(struct state *state, unsigned int ticks);
+
+/**
+ * Shortcut for state->draw (if not NULL)
+ *
+ * \pre state != NULL
+ * \param state the state
+ */
+void
+state_draw(struct state *state);
+
+/**
+ * Shortcut for state->end (if not NULL)
+ *
+ * \pre state != NULL
+ * \param state the state
+ */
+void
+state_end(struct state *state);
+
+/**
+ * Shortcut for state->finish (if not NULL)
+ *
+ * \pre state != NULL
+ * \param state the state
+ */
+void
+state_finish(struct state *state);
+
 #endif /* !MOLKO_STATE_H */
--- a/librpg/rpg/map_state.c	Sat Oct 17 10:12:41 2020 +0200
+++ b/librpg/rpg/map_state.c	Sun Oct 18 12:01:59 2020 +0200
@@ -126,8 +126,10 @@
 }
 
 static void
-enter(void)
+start(struct state *st)
 {
+	(void)st;
+
 	/* Adjust map properties. */
 	struct map *m = &map_state_data.map.map;
 
@@ -151,11 +153,6 @@
 }
 
 static void
-leave(void)
-{
-}
-
-static void
 handle_keydown(const union event *event)
 {
 	switch (event->key.key) {
@@ -303,8 +300,10 @@
 }
 
 static void
-handle(const union event *event)
+handle(struct state *st, const union event *event)
 {
+	(void)st;
+
 	switch (event->type) {
 	case EVENT_KEYDOWN:
 		handle_keydown(event);
@@ -318,14 +317,18 @@
 }
 
 static void
-update(unsigned int ticks)
+update(struct state *st, unsigned int ticks)
 {
+	(void)st;
+
 	move(ticks);
 }
 
 static void
-draw(void)
+draw(struct state *st)
 {
+	(void)st;
+
 	struct debug_report report = {0};
 
 	map_draw(&map_state_data.map.map, VIEW()->x, VIEW()->y);
@@ -344,8 +347,7 @@
 struct map_state_data map_state_data;
 
 struct state map_state = {
-	.enter = enter,
-	.leave = leave,
+	.start = start,
 	.update = update,
 	.handle = handle,
 	.draw = draw
--- a/molko/main.c	Sat Oct 17 10:12:41 2020 +0200
+++ b/molko/main.c	Sun Oct 18 12:01:59 2020 +0200
@@ -24,6 +24,7 @@
 #include <core/event.h>
 #include <core/game.h>
 #include <core/panic.h>
+#include <core/state.h>
 #include <core/sys.h>
 #include <core/util.h>
 #include <core/window.h>
@@ -33,12 +34,19 @@
 
 #include <rpg/rpg.h>
 
-#include <adventure/panic_state.h>
-#include <adventure/splashscreen_state.h>
+#include <adventure/state/panic.h>
+#include <adventure/state/splashscreen.h>
+#include <adventure/state/mainmenu.h>
 
 #define WINDOW_WIDTH 1280
 #define WINDOW_HEIGHT 720
 
+static struct {
+	struct state splash;
+	struct state mainmenu;
+	struct state panic;
+} states;
+
 static jmp_buf panic_buf;
 
 static noreturn void
@@ -61,14 +69,14 @@
 	 */
 
 	/* Init unrecoverable panic state. */
-	panic_state_init();
+	panic_state(&states.panic);
 	panic_handler = unrecoverable;
 
-	if (!theme_init())
-		panic();
+	/* Init states. */
+	splashscreen_state(&states.splash, &states.mainmenu);
+	mainmenu_state(&states.mainmenu);
 
-	/* Default state is splash screen */
-	game_switch(&splashscreen_state, true);
+	game_switch(&states.splash, true);
 }
 
 static void
@@ -123,7 +131,7 @@
 		for (union event ev; event_poll(&ev); )
 			continue;
 
-		game_switch(&panic_state, true);
+		game_switch(&states.panic, true);
 		run();
 	}