changeset 382:43d155668a55

rpg: expose battle state functions
author David Demelier <markand@malikania.fr>
date Sun, 13 Feb 2022 10:35:26 +0100
parents f48367c5a92e
children b944cd41e8f9
files battle-state-selection.h examples/CMakeLists.txt examples/example-battle/CMakeLists.txt examples/example-battle/assets/images/battle-background.png examples/example-battle/assets/images/black-cat.png examples/example-battle/assets/images/haunted-wood.png examples/example-battle/assets/sounds/fire.wav examples/example-battle/assets/sprites/explosion.png examples/example-battle/assets/sprites/john-sword.png examples/example-battle/assets/sprites/john-walk.png examples/example-battle/assets/sprites/ui-cursor.png examples/example-battle/character-john.c examples/example-battle/registry.c examples/example-battle/spell-fire.c src/libmlk-core-js/core/js-music.c src/libmlk-core-js/core/js-sound.c src/libmlk-rpg/CMakeLists.txt src/libmlk-rpg/rpg/battle-state-ai.c src/libmlk-rpg/rpg/battle-state-ai.h src/libmlk-rpg/rpg/battle-state-attacking.c src/libmlk-rpg/rpg/battle-state-attacking.h src/libmlk-rpg/rpg/battle-state-check.c src/libmlk-rpg/rpg/battle-state-check.h src/libmlk-rpg/rpg/battle-state-closing.c src/libmlk-rpg/rpg/battle-state-closing.h src/libmlk-rpg/rpg/battle-state-item.c src/libmlk-rpg/rpg/battle-state-item.h src/libmlk-rpg/rpg/battle-state-lost.c src/libmlk-rpg/rpg/battle-state-lost.h src/libmlk-rpg/rpg/battle-state-menu.c src/libmlk-rpg/rpg/battle-state-menu.h src/libmlk-rpg/rpg/battle-state-opening.c src/libmlk-rpg/rpg/battle-state-opening.h src/libmlk-rpg/rpg/battle-state-selection.c src/libmlk-rpg/rpg/battle-state-selection.h src/libmlk-rpg/rpg/battle-state-sub.c src/libmlk-rpg/rpg/battle-state-sub.h src/libmlk-rpg/rpg/battle-state-victory.c src/libmlk-rpg/rpg/battle-state-victory.h src/libmlk-rpg/rpg/battle-state.h src/libmlk-rpg/rpg/battle.c
diffstat 41 files changed, 1133 insertions(+), 482 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/battle-state-selection.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,25 @@
+/*
+ * battle-state-selection.h -- battle state (selection)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_SELECTION_H
+#define MLK_RPG_BATTLE_STATE_SELECTION_H
+
+void
+battle_state_selection(struct battle *, const struct selection *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_SELECTION_H */
--- a/examples/CMakeLists.txt	Fri Jan 07 21:50:37 2022 +0100
+++ b/examples/CMakeLists.txt	Sun Feb 13 10:35:26 2022 +0100
@@ -23,7 +23,7 @@
 	example-action
 	example-animation
 	example-audio
-#	example-battle
+	example-battle
 	example-cursor
 	example-debug
 	example-drawable
--- a/examples/example-battle/CMakeLists.txt	Fri Jan 07 21:50:37 2022 +0100
+++ b/examples/example-battle/CMakeLists.txt	Sun Feb 13 10:35:26 2022 +0100
@@ -29,11 +29,24 @@
 	${example-battle_SOURCE_DIR}/spell-fire.h
 )
 
+set(
+	ASSETS
+	${example-battle_SOURCE_DIR}/assets/images/battle-background.png
+	${example-battle_SOURCE_DIR}/assets/images/black-cat.png
+	${example-battle_SOURCE_DIR}/assets/images/haunted-wood.png
+	${example-battle_SOURCE_DIR}/assets/sounds/fire.wav
+	${example-battle_SOURCE_DIR}/assets/sprites/explosion.png
+	${example-battle_SOURCE_DIR}/assets/sprites/john-sword.png
+	${example-battle_SOURCE_DIR}/assets/sprites/john-walk.png
+	${example-battle_SOURCE_DIR}/assets/sprites/ui-cursor.png
+)
+
 mlk_executable(
 	NAME example-battle
 	FOLDER examples
 	LIBRARIES libmlk-rpg
-	SOURCES ${SOURCES}
+	SOURCES ${ASSETS} ${SOURCES}
+	ASSETS ${ASSETS}
 )
 
-source_group("" FILES ${SOURCES})
+source_group(TREE ${example-battle_SOURCE_DIR} FILES ${ASSETS} ${SOURCES})
Binary file examples/example-battle/assets/images/battle-background.png has changed
Binary file examples/example-battle/assets/images/black-cat.png has changed
Binary file examples/example-battle/assets/images/haunted-wood.png has changed
Binary file examples/example-battle/assets/sounds/fire.wav has changed
Binary file examples/example-battle/assets/sprites/explosion.png has changed
Binary file examples/example-battle/assets/sprites/john-sword.png has changed
Binary file examples/example-battle/assets/sprites/john-walk.png has changed
Binary file examples/example-battle/assets/sprites/ui-cursor.png has changed
--- a/examples/example-battle/character-john.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/examples/example-battle/character-john.c	Sun Feb 13 10:35:26 2022 +0100
@@ -36,12 +36,14 @@
 
 const struct character character_john = {
 	.name = "John ",
-	.type = "Adventurer",
 	.level = 1,
 	.hp = 120,
 	.mp = 50,
 	.reset = adventurer_reset,
-	.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
+	.sprites = {
+		[CHARACTER_SPRITE_NORMAL] = &registry_sprites[REGISTRY_TEXTURE_JOHN_WALK],
+		[CHARACTER_SPRITE_SWORD] = &registry_sprites[REGISTRY_TEXTURE_JOHN_SWORD]
+	},
 	.spells = {
 		&spell_fire
 	}
--- a/examples/example-battle/registry.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/examples/example-battle/registry.c	Sun Feb 13 10:35:26 2022 +0100
@@ -23,50 +23,62 @@
 #include <core/util.h>
 #include <core/sys.h>
 
-#include "registry.h"
+#include <assets/images/battle-background.h>
+#include <assets/images/black-cat.h>
+#include <assets/images/haunted-wood.h>
+
+#include <assets/sounds/fire.h>
 
-#define PATH(r) util_pathf("%s/mlk-adventure/%s", sys_dir(SYS_DIR_DATA), r)
+#include <assets/sprites/explosion.h>
+#include <assets/sprites/john-sword.h>
+#include <assets/sprites/john-walk.h>
+#include <assets/sprites/ui-cursor.h>
+
+#include "registry.h"
 
 struct texture registry_images[REGISTRY_IMAGE_NUM];
 struct texture registry_textures[REGISTRY_TEXTURE_NUM];
 struct sprite registry_sprites[REGISTRY_TEXTURE_NUM];
 struct sound registry_sounds[REGISTRY_SOUND_NUM];
 
-#define REGISTRY_IMAGE(i, path) \
-	{ (i), (path) }
+#define REGISTRY_IMAGE(i, data) \
+	{ (i), (data), sizeof (data) }
 
 static const struct {
 	enum registry_image index;
-	const char *path;
+	const unsigned char *data;
+	size_t datasz;
 } images[] = {
-	REGISTRY_IMAGE(REGISTRY_IMAGE_BATTLE_BACKGROUND, "images/battle-background.png")
+	REGISTRY_IMAGE(REGISTRY_IMAGE_BATTLE_BACKGROUND, assets_images_battle_background)
 };
 
-#define REGISTRY_TEXTURE(s, path, cw, ch) \
-	{ (s), (path), (cw), (ch) }
+#define REGISTRY_TEXTURE(s, data, cw, ch) \
+	{ (s), (data), sizeof (data), (cw), (ch) }
 
 static const struct {
 	enum registry_texture index;
-	const char *path;
+	const unsigned char *data;
+	size_t datasz;
 	unsigned int cellw;
 	unsigned int cellh;
 } textures[] = {
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_CURSOR, "sprites/ui-cursor.png", 24, 24),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_EXPLOSION, "sprites/explosion.png", 256, 256),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_JOHN_SWORD, "sprites/john-sword.png", 256, 256),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_JOHN_WALK, "sprites/john-walk.png", 256, 256),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_HAUNTED_WOOD, "images/haunted-wood.png", 0, 0),
-	REGISTRY_TEXTURE(REGISTRY_TEXTURE_BLACK_CAT, "images/black-cat.png", 0, 0)
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_CURSOR, assets_sprites_ui_cursor, 24, 24),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_EXPLOSION, assets_sprites_explosion, 256, 256),
+	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)
 };
 
-#define REGISTRY_SOUND(s, path) \
-	{ (s), (path) }
+#define REGISTRY_SOUND(s, data) \
+	{ (s), (data), sizeof (data) }
 
 static const struct {
 	enum registry_sound index;
-	const char *path;
+	const unsigned char *data;
+	size_t datasz;
 } sounds[] = {
-	REGISTRY_SOUND(REGISTRY_SOUND_FIRE, "sounds/fire.wav")
+	REGISTRY_SOUND(REGISTRY_SOUND_FIRE, assets_sounds_fire)
 };
 
 static void
@@ -75,7 +87,7 @@
 	for (size_t i = 0; i < UTIL_SIZE(images); ++i) {
 		struct texture *texture = &registry_images[images[i].index];
 
-		if (image_open(texture, PATH(images[i].path)) < 0)
+		if (image_openmem(texture, images[i].data, images[i].datasz) < 0)
 			panic();
 	}
 }
@@ -87,7 +99,7 @@
 		struct texture *texture = &registry_textures[textures[i].index];
 		struct sprite *sprite = &registry_sprites[textures[i].index];
 
-		if (image_open(texture, PATH(textures[i].path)) < 0)
+		if (image_openmem(texture, textures[i].data, textures[i].datasz) < 0)
 			panic();
 
 		if (textures[i].cellw == 0 || textures[i].cellh == 0)
@@ -103,7 +115,7 @@
 	for (size_t i = 0; i < UTIL_SIZE(sounds); ++i) {
 		struct sound *sound = &registry_sounds[sounds[i].index];
 
-		if (sound_open(sound, PATH(sounds[i].path)) < 0)
+		if (sound_openmem(sound, sounds[i].data, sounds[i].datasz) < 0)
 			panic();
 	}
 }
--- a/examples/example-battle/spell-fire.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/examples/example-battle/spell-fire.c	Sun Feb 13 10:35:26 2022 +0100
@@ -112,7 +112,7 @@
 	animation_init(&data->animation, &registry_sprites[REGISTRY_TEXTURE_EXPLOSION], 12);
 	animation_start(&data->animation);
 
-	sound_play(&registry_sounds[REGISTRY_SOUND_FIRE], -1, 0);
+	sound_play(&registry_sounds[REGISTRY_SOUND_FIRE]);
 
 	action_stack_add(&bt->actions[0], &data->action);
 }
--- a/src/libmlk-core-js/core/js-music.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-core-js/core/js-music.c	Sun Feb 13 10:35:26 2022 +0100
@@ -77,28 +77,17 @@
 Music_play(duk_context *ctx)
 {
 	const int flags = duk_get_int_default(ctx, 0, MUSIC_NONE);
-	const unsigned int fadein = duk_get_uint_default(ctx, 0, 0);
 
-	if (music_play(self(ctx), flags, fadein) < 0)
+	if (music_play(self(ctx), flags) < 0)
 		return duk_error(ctx, DUK_ERR_ERROR, "%s", error());
 
 	return 0;
 }
 
 static duk_ret_t
-Music_playing(duk_context *ctx)
-{
-	duk_push_int(ctx, music_playing());
-
-	return 0;
-}
-
-static duk_ret_t
 Music_pause(duk_context *ctx)
 {
-	(void)ctx;
-
-	music_pause();
+	music_pause(self(ctx));
 
 	return 0;
 }
@@ -106,9 +95,7 @@
 static duk_ret_t
 Music_resume(duk_context *ctx)
 {
-	(void)ctx;
-
-	music_resume();
+	music_resume(self(ctx));
 
 	return 0;
 }
@@ -116,7 +103,7 @@
 static duk_ret_t
 Music_stop(duk_context *ctx)
 {
-	music_stop(duk_get_uint_default(ctx, 0, 0));
+	music_stop(self(ctx));
 
 	return 0;
 }
@@ -139,11 +126,6 @@
 
 static const duk_function_list_entry methods[] = {
 	{ "play",               Music_play,             DUK_VARARGS     },
-	{ NULL,                 NULL,                   0               }
-};
-
-static const duk_function_list_entry functions[] = {
-	{ "playing",            Music_playing,          0               },
 	{ "pause",              Music_pause,            0               },
 	{ "resume",             Music_resume,           0               },
 	{ "stop",               Music_stop,             DUK_VARARGS     },
@@ -151,9 +133,9 @@
 };
 
 static const duk_number_list_entry flags[] = {
-	{ "NONE",       MUSIC_NONE              },
-	{ "LOOP",       MUSIC_LOOP              },
-	{ NULL,         0                       }
+	{ "NONE",              MUSIC_NONE                               },
+	{ "LOOP",              MUSIC_LOOP                               },
+	{ NULL,                0                                        }
 };
 
 void
@@ -162,12 +144,10 @@
 	assert(ctx);
 
 	duk_push_c_function(ctx, Music_new, 1);
-	duk_put_function_list(ctx, -1, functions);
 	duk_push_object(ctx);
 	duk_put_number_list(ctx, -1, flags);
 	duk_put_prop_string(ctx, -2, "Flags");
 	duk_push_object(ctx);
-	duk_put_function_list(ctx, -1, functions);
 	duk_put_function_list(ctx, -1, methods);
 	duk_push_c_function(ctx, Music_destructor, 1);
 	duk_set_finalizer(ctx, -2);
--- a/src/libmlk-core-js/core/js-sound.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-core-js/core/js-sound.c	Sun Feb 13 10:35:26 2022 +0100
@@ -94,10 +94,7 @@
 static duk_ret_t
 Sound_proto_play(duk_context *ctx)
 {
-	const int channel = duk_get_int_default(ctx, 0, 0);
-	const unsigned int fadein = duk_get_uint_default(ctx, 1, 0);
-
-	if (sound_play(self(ctx), channel, fadein) < 0)
+	if (sound_play(self(ctx)) < 0)
 		return duk_error(ctx, DUK_ERR_ERROR, "%s", error());
 
 	return 0;
@@ -122,37 +119,7 @@
 static duk_ret_t
 Sound_proto_stop(duk_context *ctx)
 {
-	const unsigned int fadeout = duk_get_uint_default(ctx, 0, 0);
-
-	sound_stop(self(ctx), fadeout);
-
-	return 0;
-}
-
-static duk_ret_t
-Sound_pause(duk_context *ctx)
-{
-	(void)ctx;
-
-	sound_pause(NULL);
-
-	return 0;
-}
-
-static duk_ret_t
-Sound_resume(duk_context *ctx)
-{
-	(void)ctx;
-
-	sound_resume(NULL);
-
-	return 0;
-}
-
-static duk_ret_t
-Sound_stop(duk_context *ctx)
-{
-	sound_stop(NULL, duk_get_uint_default(ctx, 0, 0));
+	sound_stop(self(ctx));
 
 	return 0;
 }
@@ -165,20 +132,12 @@
 	{ NULL,                 NULL,                   0               }
 };
 
-static const duk_function_list_entry functions[] = {
-	{ "pause",              Sound_pause,            0               },
-	{ "resume",             Sound_resume,           0               },
-	{ "stop",               Sound_stop,             DUK_VARARGS     },
-	{ NULL,                 NULL,                   0               }
-};
-
 void
 js_sound_bind(duk_context *ctx)
 {
 	assert(ctx);
 
 	duk_push_c_function(ctx, Sound_constructor, 1);
-	duk_put_function_list(ctx, -1, functions);
 	duk_push_object(ctx);
 	duk_put_function_list(ctx, -1, methods);
 	duk_push_c_function(ctx, Sound_destructor, 1);
--- a/src/libmlk-rpg/CMakeLists.txt	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/CMakeLists.txt	Sun Feb 13 10:35:26 2022 +0100
@@ -35,14 +35,23 @@
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-message.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-message.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-ai.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-ai.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-attacking.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-attacking.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-check.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-check.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-closing.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-closing.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-item.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-item.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-lost.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-lost.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-menu.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-menu.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-opening.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-opening.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-selection.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-selection.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-sub.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-victory.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state.c
--- a/src/libmlk-rpg/rpg/battle-state-ai.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-ai.c	Sun Feb 13 10:35:26 2022 +0100
@@ -17,9 +17,13 @@
  */
 
 #include <assert.h>
+#include <stdlib.h>
+
+#include <core/alloc.h>
 
 #include "battle.h"
 #include "battle-state.h"
+#include "battle-state-ai.h"
 #include "character.h"
 
 static int
@@ -28,6 +32,22 @@
 	(void)st;
 	(void)ticks;
 
+	return battle_state_ai_update(bt);
+}
+
+static void
+finish(struct battle_state *st, struct battle *bt)
+{
+	(void)bt;
+
+	free(st);
+}
+
+int
+battle_state_ai_update(struct battle *bt)
+{
+	assert(bt);
+
 	struct character *ch = bt->order_cur->ch;
 
 	/*
@@ -44,9 +64,12 @@
 {
 	assert(bt);
 
-	static struct battle_state self = {
-		.update = update
-	};
+	struct battle_state *self;
 
-	battle_switch(bt, &self);
+	self = alloc_new0(sizeof (*self));
+	self->data = bt;
+	self->update = update;
+	self->finish = finish;
+
+	battle_switch(bt, self);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-ai.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,30 @@
+/*
+ * battle-state-ai.h -- battle state (enemy is playing)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_AI_H
+#define MLK_RPG_BATTLE_STATE_AI_H
+
+struct battle;
+
+int
+battle_state_ai_update(struct battle *);
+
+void
+battle_state_ai(struct battle *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_AI_H */
--- a/src/libmlk-rpg/rpg/battle-state-attacking.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-attacking.c	Sun Feb 13 10:35:26 2022 +0100
@@ -1,5 +1,5 @@
 /*
- * battle-state-attacking.h -- battle state (entity is moving for attacking)
+ * battle-state-attacking.c -- battle state (entity is moving for attacking)
  *
  * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
  *
@@ -25,33 +25,14 @@
 
 #include "battle.h"
 #include "battle-state.h"
+#include "battle-state-attacking.h"
+#include "battle-state-check.h"
 #include "battle-entity-state.h"
 #include "character.h"
 
-enum substate {
-	/* For team. */
-	SUBSTATE_ADVANCING,
-	SUBSTATE_ATTACKING,
-	SUBSTATE_RETURNING,
-
-	/* For enemies. */
-	SUBSTATE_BLINKING,
-};
-
-/*
- * This state is split into three parts:
- *
- * 1. entity walks a bit in front of its original position.
- * 2. entity animate itself while attacking.
- * 3. entity goes back to its original position.
- */
-struct data {
-	enum substate substate;
-	struct battle_entity *source;
-	struct battle_entity *target;
+struct self {
+	struct battle_state_attacking data;
 	struct battle_state state;
-	int origin_x;
-	int origin_y;
 };
 
 static void
@@ -71,43 +52,9 @@
 static int
 update(struct battle_state *st, struct battle *bt, unsigned int ticks)
 {
-	(void)bt;
 	(void)ticks;
 
-	struct data *data = st->data;
-
-	if (!battle_entity_update(data->source, 0))
-		return 0;
-
-	switch (data->substate) {
-	case SUBSTATE_ADVANCING:
-		/*
-		 * Current entity state is battle-entity-state-moving but it is
-		 * already updated from the game itself so pass 0 just to check
-		 * if it has finished moving.
-		 */
-		data->substate = SUBSTATE_ATTACKING;
-		/* TODO: determine sprite to use about equipment. */
-		battle_entity_state_attacking(data->source, data->source->ch->sprites[CHARACTER_SPRITE_SWORD]);
-		break;
-	case SUBSTATE_ATTACKING:
-		/* Move back to original position. */
-		data->substate = SUBSTATE_RETURNING;
-		damage(data->source, data->target, bt);
-		battle_entity_state_moving(data->source, data->origin_x, data->origin_y);
-		break;
-	case SUBSTATE_RETURNING:
-	case SUBSTATE_BLINKING:
-		/* Just wait. */
-		battle_entity_state_normal(data->source);
-		damage(data->source, data->target, bt);
-		battle_state_check(bt);
-		break;
-	default:
-		break;
-	}
-
-	return 0;
+	return battle_state_attacking_update(st->data, bt);
 }
 
 static void
@@ -119,36 +66,86 @@
 }
 
 void
-battle_state_attacking(struct battle *bt, struct character *source, struct character *target)
+battle_state_attacking_init(struct battle_state_attacking *atk,
+                            struct battle_entity *source,
+                            struct battle_entity *target)
 {
-	assert(bt);
+	assert(atk);
+	assert(source);
+	assert(target);
 
-	struct data *data;
 	int x, y;
 
-	if (!(data = alloc_new0(sizeof (*data))))
-		panic();
-
 	/* Starts this state with advancing. */
-	data->source = battle_find(bt, source);
-	data->target = battle_find(bt, target);
-	data->origin_x = data->source->x;
-	data->origin_y = data->source->y;
+	atk->source = source;
+	atk->target = target;
+	atk->origin_x = source->x;
+	atk->origin_y = source->y;
 
 	/* We go to the enemy. */
-	x = data->target->x + data->target->ch->sprites[CHARACTER_SPRITE_NORMAL]->cellw;
-	y = data->target->y;
+	x = target->x + target->ch->sprites[CHARACTER_SPRITE_NORMAL]->cellw;
+	y = target->y;
 
 	/* If it is an enemy we don't move it but blink instead. */
-	if (data->source->ch->exec) {
-		data->substate = SUBSTATE_BLINKING;
-		battle_entity_state_blinking(data->source);
+	if (source->ch->exec) {
+		atk->status = BATTLE_STATE_ATTACKING_BLINKING;
+		battle_entity_state_blinking(source);
 	} else
-		battle_entity_state_moving(data->source, x, y);
+		battle_entity_state_moving(source, x, y);
+}
+
+int
+battle_state_attacking_update(struct battle_state_attacking *atk, struct battle *bt)
+{
+	if (!battle_entity_update(atk->source, 0))
+		return 0;
+
+	switch (atk->status) {
+	case BATTLE_STATE_ATTACKING_ADVANCING:
+		/*
+		 * Current entity state is battle-entity-state-moving but it is
+		 * already updated from the game itself so pass 0 just to check
+		 * if it has finished moving.
+		 */
+		atk->status = BATTLE_STATE_ATTACKING_DAMAGING;
 
-	data->state.data = data;
-	data->state.update = update;
-	data->state.finish = finish;
+		/* TODO: determine sprite to use about equipment. */
+		battle_entity_state_attacking(atk->source, atk->source->ch->sprites[CHARACTER_SPRITE_SWORD]);
+		break;
+	case BATTLE_STATE_ATTACKING_DAMAGING:
+		/* Move back to original position. */
+		atk->status = BATTLE_STATE_ATTACKING_RETURNING;
+		damage(atk->source, atk->target, bt);
+		battle_entity_state_moving(atk->source, atk->origin_x, atk->origin_y);
+		break;
+	case BATTLE_STATE_ATTACKING_RETURNING:
+	case BATTLE_STATE_ATTACKING_BLINKING:
+		/* Just wait. */
+		battle_entity_state_normal(atk->source);
+		damage(atk->source, atk->target, bt);
+		battle_state_check(bt);
+		break;
+	default:
+		break;
+	}
 
-	battle_switch(bt, &data->state);
+	return 0;
 }
+
+void
+battle_state_attacking(struct battle_entity *source, struct battle_entity *target, struct battle *bt)
+{
+	assert(source);
+	assert(target);
+	assert(bt);
+
+	struct self *self;
+
+	self = alloc_new0(sizeof (*self));
+	self->state.data = self;
+	self->state.update = update;
+	self->state.finish = finish;
+
+	battle_state_attacking_init(&self->data, source, target);
+	battle_switch(bt, &self->state);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-attacking.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,54 @@
+/*
+ * battle-state-attacking.h -- battle state (entity is moving for attacking)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_ATTACKING_H
+#define MLK_RPG_BATTLE_STATE_ATTACKING_H
+
+struct battle;
+struct battle_entity;
+
+enum battle_state_attacking_status {
+	/* For team. */
+	BATTLE_STATE_ATTACKING_ADVANCING,
+	BATTLE_STATE_ATTACKING_DAMAGING,
+	BATTLE_STATE_ATTACKING_RETURNING,
+
+	/* For enemies. */
+	BATTLE_STATE_ATTACKING_BLINKING,
+};
+
+struct battle_state_attacking {
+	enum battle_state_attacking_status status;
+	struct battle_entity *source;
+	struct battle_entity *target;
+	int origin_x;
+	int origin_y;
+};
+
+void
+battle_state_attacking_init(struct battle_state_attacking *,
+                            struct battle_entity *,
+			    struct battle_entity *);
+
+int
+battle_state_attacking_update(struct battle_state_attacking *, struct battle *);
+
+void
+battle_state_attacking(struct battle_entity *, struct battle_entity *, struct battle *);
+
+#endif /* !#define MLK_RPG_BATTLE_STATE_ATTACKING_H */
--- a/src/libmlk-rpg/rpg/battle-state-check.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-check.c	Sun Feb 13 10:35:26 2022 +0100
@@ -27,6 +27,9 @@
 
 #include "battle.h"
 #include "battle-state.h"
+#include "battle-state-check.h"
+#include "battle-state-lost.h"
+#include "battle-state-victory.h"
 #include "character.h"
 
 struct fadeout {
@@ -140,6 +143,22 @@
 	(void)st;
 	(void)ticks;
 
+	return battle_state_check_update(bt);
+}
+
+static void
+finish(struct battle_state *st, struct battle *bt)
+{
+	(void)bt;
+
+	free(st);
+}
+
+int
+battle_state_check_update(struct battle *bt)
+{
+	assert(bt);
+
 	clean(bt);
 
 	if (is_dead(bt))
@@ -157,9 +176,12 @@
 {
 	assert(bt);
 
-	static struct battle_state self = {
-		.update = update
-	};
+	struct battle_state *self;
 
-	battle_switch(bt, &self);
+	self = alloc_new0(sizeof (*self));
+	self->data = bt;
+	self->update = update;
+	self->finish = finish;
+
+	battle_switch(bt, self);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-check.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,30 @@
+/*
+ * battle-state-check.h -- battle state (check status)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_CHECK_H
+#define MLK_RPG_BATTLE_STATE_CHECK_H
+
+struct battle;
+
+int
+battle_state_check_update(struct battle *);
+
+void
+battle_state_check(struct battle *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_CHECK_H */
--- a/src/libmlk-rpg/rpg/battle-state-closing.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-closing.c	Sun Feb 13 10:35:26 2022 +0100
@@ -18,6 +18,7 @@
 
 #include <assert.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include <core/alloc.h>
 #include <core/music.h>
@@ -27,12 +28,11 @@
 #include <core/window.h>
 
 #include "battle.h"
+#include "battle-state-closing.h"
 
-struct closing {
-	struct battle_state self;
-	struct texture texture;
-	unsigned int alpha;
-	unsigned int elapsed;
+struct self {
+	struct battle_state_closing data;
+	struct battle_state state;
 };
 
 static int
@@ -40,23 +40,7 @@
 {
 	(void)bt;
 
-	struct closing *closing = st->data;
-
-	closing->elapsed += ticks;
-
-	if (closing->elapsed > 8) {
-		closing->elapsed = 0;
-
-		if (closing->alpha == 255) {
-			music_stop(0);
-			return 1;
-		}
-
-		closing->alpha += 5;
-		texture_set_alpha_mod(&closing->texture, closing->alpha);
-	}
-
-	return 0;
+	return battle_state_closing_update(st->data, ticks);
 }
 
 static void
@@ -64,9 +48,7 @@
 {
 	(void)bt;
 
-	const struct closing *closing = st->data;
-
-	texture_draw(&closing->texture, 0, 0);
+	battle_state_closing_draw(st->data);
 }
 
 static void
@@ -74,10 +56,69 @@
 {
 	(void)bt;
 
-	struct closing *closing = st->data;
+	battle_state_closing_finish(st->data);
+	free(st->data);
+}
+
+void
+battle_state_closing_init(struct battle_state_closing *cls)
+{
+	assert(cls);
+
+	if (texture_new(&cls->texture, window.w, window.h) < 0)
+		panic();
+
+	PAINTER_BEGIN(&cls->texture);
+	texture_set_blend_mode(&cls->texture, TEXTURE_BLEND_BLEND);
+	painter_set_color(0x000000ff);
+	painter_clear();
+	painter_draw_rectangle(0, 0, window.w, window.h);
+	PAINTER_END();
+	
+	texture_set_alpha_mod(&cls->texture, 0);
+}
+
+int
+battle_state_closing_update(struct battle_state_closing *cls, unsigned int ticks)
+{
+	assert(cls);
+
+	cls->elapsed += ticks;
 
-	texture_finish(&closing->texture);
-	free(closing);
+	/* TODO: ??? */
+	if (cls->elapsed > 8) {
+		cls->elapsed = 0;
+
+		if (cls->alpha == 255) {
+			/* TODO: since OpenAL, no notion of global music. */
+#if 0
+			music_stop(0);
+#endif
+			return 1;
+		}
+
+		cls->alpha += 5;
+		texture_set_alpha_mod(&cls->texture, cls->alpha);
+	}
+
+	return 0;
+}
+
+void
+battle_state_closing_draw(struct battle_state_closing *cls)
+{
+	assert(cls);
+
+	texture_draw(&cls->texture, 0, 0);
+}
+
+void
+battle_state_closing_finish(struct battle_state_closing *cls)
+{
+	assert(cls);
+
+	texture_finish(&cls->texture);
+	memset(cls, 0, sizeof (*cls));
 }
 
 void
@@ -85,25 +126,14 @@
 {
 	assert(bt);
 
-	struct closing *closing;
-
-	if (!(closing = alloc_new0(sizeof (*closing))) ||
-	    texture_new(&closing->texture, window.w, window.h) < 0)
-		panic();
+	struct self *self;
 
-	PAINTER_BEGIN(&closing->texture);
-	texture_set_blend_mode(&closing->texture, TEXTURE_BLEND_BLEND);
-	painter_set_color(0x000000ff);
-	painter_clear();
-	painter_draw_rectangle(0, 0, window.w, window.h);
-	PAINTER_END();
+	self = alloc_new0(sizeof (*self));
+	self->state.data = self;
+	self->state.update = update;
+	self->state.draw = draw;
+	self->state.finish = finish;
 
-	texture_set_alpha_mod(&closing->texture, 0);
-
-	closing->self.data = closing;
-	closing->self.update = update;
-	closing->self.draw = draw;
-	closing->self.finish = finish;
-
-	battle_switch(bt, &closing->self);
+	battle_state_closing_init(&self->data);
+	battle_switch(bt, &self->state);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-closing.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,47 @@
+/*
+ * battle-state-closing.h -- battle state (closing)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_CLOSING_H
+#define MLK_RPG_BATTLE_STATE_CLOSING_H
+
+#include <core/texture.h>
+
+struct battle;
+
+struct battle_state_closing {
+	struct texture texture;
+	unsigned int alpha;
+	unsigned int elapsed;
+};
+
+void
+battle_state_closing_init(struct battle_state_closing *);
+
+int
+battle_state_closing_update(struct battle_state_closing *, unsigned int);
+
+void
+battle_state_closing_draw(struct battle_state_closing *);
+
+void
+battle_state_closing_finish(struct battle_state_closing *);
+
+void
+battle_state_closing(struct battle *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_CLOSING_H */
--- a/src/libmlk-rpg/rpg/battle-state-item.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-item.c	Sun Feb 13 10:35:26 2022 +0100
@@ -28,52 +28,18 @@
 #include "battle-entity-state.h"
 #include "battle-message.h"
 #include "battle-state.h"
+#include "battle-state-item.h"
 #include "battle.h"
 
-enum substate {
-	SUBSTATE_ADVANCING,
-	SUBSTATE_MESSAGE,
-	SUBSTATE_RETURNING
-};
-
 struct self {
-	enum substate substate;
-	struct battle_message msg;
-	struct battle_entity *source;
-	struct battle_entity *target;
+	struct battle_state_item data;
 	struct battle_state state;
-	struct inventory_slot *slot;
-	int origin_x;
 };
 
 static int
 update(struct battle_state *st, struct battle *bt, unsigned int ticks)
 {
-	struct self *self = st->data;
-
-	switch (self->substate) {
-	case SUBSTATE_ADVANCING:
-		/* Entity is updating from battle, so just inspect its status. */
-		if (battle_entity_update(self->source, 0)) {
-			self->substate = SUBSTATE_MESSAGE;
-			battle_entity_state_normal(self->source);
-		}
-		break;
-	case SUBSTATE_MESSAGE:
-		if (battle_message_update(&self->msg, ticks)) {
-			self->substate = SUBSTATE_RETURNING;
-			battle_entity_state_moving(self->source, self->origin_x, self->source->y);
-		}
-		break;
-	default:
-		if (battle_entity_update(self->source, 0)) {
-			battle_entity_state_normal(self->source);
-			battle_use(bt, self->slot->item, self->source->ch, self->target->ch);
-		}
-		break;
-	}
-
-	return 0;
+	return battle_state_item_update(st->data, bt, ticks);
 }
 
 static void
@@ -81,10 +47,7 @@
 {
 	(void)bt;
 
-	struct self *self = st->data;
-
-	if (self->substate == SUBSTATE_MESSAGE)
-		battle_message_draw(&self->msg);
+	battle_state_item_draw(st->data);
 }
 
 static void
@@ -96,34 +59,88 @@
 }
 
 void
-battle_state_item(struct battle *bt,
-                  struct character *source,
-                  struct character *target,
-                  struct inventory_slot *slot)
+battle_state_item_init(struct battle_state_item *it,
+                       struct battle *bt,
+                       struct battle_entity *source,
+                       struct battle_entity *target,
+                       struct inventory_slot *slot)
 {
+	assert(it);
 	assert(bt);
 	assert(source);
 	assert(target);
 	assert(slot);
 
+	it->source = source;
+	it->target = target;
+	it->slot = slot;
+	it->origin_x = it->source->x;
+
+	it->msg.text = slot->item->name;
+	it->msg.theme = bt->theme;
+
+	battle_entity_state_moving(it->source, it->origin_x - 100, it->source->y);
+}
+
+int
+battle_state_item_update(struct battle_state_item *it, struct battle *bt, unsigned int ticks)
+{
+	assert(it);
+	assert(bt);
+
+	switch (it->status) {
+	case BATTLE_STATE_ITEM_ADVANCING:
+		/* Entity is updating from battle, so just inspect its status. */
+		if (battle_entity_update(it->source, 0)) {
+			it->status = BATTLE_STATE_ITEM_MESSAGE;
+			battle_entity_state_normal(it->source);
+		}
+		break;
+	case BATTLE_STATE_ITEM_MESSAGE:
+		if (battle_message_update(&it->msg, ticks)) {
+			it->status = BATTLE_STATE_ITEM_RETURNING;
+			battle_entity_state_moving(it->source, it->origin_x, it->source->y);
+		}
+		break;
+	default:
+		if (battle_entity_update(it->source, 0)) {
+			battle_entity_state_normal(it->source);
+			battle_use(bt, it->slot->item, it->source->ch, it->target->ch);
+		}
+		break;
+	}
+
+	return 0;
+}
+
+void
+battle_state_item_draw(struct battle_state_item *it)
+{
+	assert(it);
+
+	if (it->status == BATTLE_STATE_ITEM_MESSAGE)
+		battle_message_draw(&it->msg);
+}
+
+void
+battle_state_item(struct battle *bt,
+                  struct battle_entity *source,
+                  struct battle_entity *target,
+                  struct inventory_slot *slot)
+{
+	assert(source);
+	assert(target);
+	assert(slot);
+	assert(bt);
+
 	struct self *self;
 
-	if (!(self = alloc_new0(sizeof (*self))))
-		panic();
-
-	self->source = battle_find(bt, source);
-	self->target = battle_find(bt, target);
-	self->slot = slot;
-	self->origin_x = self->source->x;
-
-	self->msg.text = slot->item->name;
-	self->msg.theme = bt->theme;
-
+	self = alloc_new0(sizeof (*self));
 	self->state.data = self;
 	self->state.update = update;
 	self->state.draw = draw;
 	self->state.finish = finish;
 
-	battle_entity_state_moving(self->source, self->origin_x - 100, self->source->y);
+	battle_state_item_init(&self->data, bt, source, target, slot);
 	battle_switch(bt, &self->state);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-item.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,64 @@
+/*
+ * battle-state-item.h -- battle state (using item)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_ITEM_H
+#define MLK_RPG_BATTLE_STATE_ITEM_H
+
+#include <rpg/battle-message.h>
+
+struct battle;
+struct battle_entity;
+struct inventory_slot;
+
+enum battle_state_item_status {
+	BATTLE_STATE_ITEM_ADVANCING,
+	BATTLE_STATE_ITEM_MESSAGE,
+	BATTLE_STATE_ITEM_RETURNING
+};
+
+struct battle_state_item {
+	enum battle_state_item_status status;
+	struct battle_message msg;
+	struct battle_entity *source;
+	struct battle_entity *target;
+	struct battle_state state;
+	struct inventory_slot *slot;
+	int origin_x;
+};
+
+void
+battle_state_item_init(struct battle_state_item *,
+                       struct battle *,
+                       struct battle_entity *,
+                       struct battle_entity *,
+                       struct inventory_slot *);
+
+int
+battle_state_item_update(struct battle_state_item *, struct battle *, unsigned int);
+
+void
+battle_state_item_draw(struct battle_state_item *);
+
+void
+battle_state_item(struct battle *,
+                  struct battle_entity *,
+                  struct battle_entity *,
+                  struct inventory_slot *);
+
+
+#endif /* !MLK_RPG_BATTLE_STATE_ITEM_H */
--- a/src/libmlk-rpg/rpg/battle-state-lost.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-lost.c	Sun Feb 13 10:35:26 2022 +0100
@@ -23,15 +23,15 @@
 #include <core/panic.h>
 #include <core/window.h>
 
-#include <rpg/message.h>
-
 #include "battle.h"
 #include "battle-state.h"
+#include "battle-state-closing.h"
+#include "battle-state-lost.h"
 #include "rpg_p.h"
 
-struct lost {
-	struct battle_state self;
-	struct message msg;
+struct self {
+	struct battle_state_lost data;
+	struct battle_state state;
 };
 
 static void
@@ -39,22 +39,13 @@
 {
 	(void)bt;
 
-	struct lost *lost = st->data;
-
-	message_handle(&lost->msg, ev);
+	battle_state_lost_handle(st->data, ev);
 }
 
 static int
 update(struct battle_state *st, struct battle *bt, unsigned int ticks)
 {
-	(void)bt;
-
-	struct lost *lost = st->data;
-
-	if (message_update(&lost->msg, ticks))
-		battle_state_closing(bt);
-
-	return 0;
+	return battle_state_lost_update(st->data, bt, ticks);
 }
 
 static void
@@ -62,31 +53,20 @@
 {
 	(void)bt;
 
-	const struct lost *lost = st->data;
-
-	message_draw(&lost->msg);
+	battle_state_lost_draw(st->data);
 }
 
 void
-battle_state_lost(struct battle *bt)
+battle_state_lost_init(struct battle_state_lost *lost, struct battle *bt)
 {
+	assert(lost);
 	assert(bt);
 
-	struct lost *lost;
-
-	if (!(lost = alloc_new0(sizeof (*lost))))
-		panic();
-
-	lost->self.data = lost;
-	lost->self.handle = handle;
-	lost->self.update = update;
-	lost->self.draw = draw;
-
 	lost->msg.text[0] = _("You have been defeated...");
 	lost->msg.theme = bt->theme;
 	lost->msg.flags = MESSAGE_FLAGS_AUTOMATIC |
-			 MESSAGE_FLAGS_FADEIN |
-			 MESSAGE_FLAGS_FADEOUT;
+	                  MESSAGE_FLAGS_FADEIN |
+	                  MESSAGE_FLAGS_FADEOUT;
 	lost->msg.timeout = MESSAGE_TIMEOUT_DEFAULT;
 	lost->msg.delay = MESSAGE_DELAY_DEFAULT;
 
@@ -98,8 +78,53 @@
 	lost->msg.x = (window.w - lost->msg.w) / 2;
 
 	bt->status = BATTLE_STATUS_LOST;
-	battle_switch(bt, &lost->self);
 
 	if (bt->music[2])
 		music_play(bt->music[2], MUSIC_NONE);
 }
+
+void
+battle_state_lost_handle(struct battle_state_lost *lost, const union event *ev)
+{
+	assert(lost);
+	assert(ev);
+
+	message_handle(&lost->msg, ev);
+}
+
+int
+battle_state_lost_update(struct battle_state_lost *lost, struct battle *bt, unsigned int ticks)
+{
+	assert(lost);
+	assert(bt);
+
+	if (message_update(&lost->msg, ticks))
+		battle_state_closing(bt);
+
+	return 0;
+}
+
+void
+battle_state_lost_draw(struct battle_state_lost *lost)
+{
+	assert(lost);
+
+	message_draw(&lost->msg);
+}
+
+void
+battle_state_lost(struct battle *bt)
+{
+	assert(bt);
+
+	struct self *self;
+
+	self = alloc_new0(sizeof (*self));
+	self->state.data = self;
+	self->state.handle = handle;
+	self->state.update = update;
+	self->state.draw = draw;
+
+	battle_state_lost_init(&self->data, bt);
+	battle_switch(bt, &self->state);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-lost.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,47 @@
+/*
+ * battle-state-lost.h -- battle state (lost)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_LOST_H
+#define MLK_RPG_BATTLE_STATE_LOST_H
+
+#include <rpg/message.h>
+
+union event;
+
+struct battle;
+
+struct battle_state_lost {
+	struct message msg;
+};
+
+void
+battle_state_lost_init(struct battle_state_lost *, struct battle *);
+
+void
+battle_state_lost_handle(struct battle_state_lost *, const union event *);
+
+int
+battle_state_lost_update(struct battle_state_lost *, struct battle *, unsigned int);
+
+void
+battle_state_lost_draw(struct battle_state_lost *);
+
+void
+battle_state_lost(struct battle *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_LOST_H */
--- a/src/libmlk-rpg/rpg/battle-state-menu.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-menu.c	Sun Feb 13 10:35:26 2022 +0100
@@ -17,10 +17,16 @@
  */
 
 #include <assert.h>
+#include <stdlib.h>
+
+#include <core/alloc.h>
 
 #include "battle.h"
 #include "battle-bar.h"
 #include "battle-state.h"
+#include "battle-state-menu.h"
+#include "battle-state-selection.h"
+#include "battle-state-sub.h"
 #include "character.h"
 #include "spell.h"
 
@@ -54,6 +60,22 @@
 {
 	(void)st;
 
+	battle_state_menu_handle(bt, ev);
+}
+
+static void
+finish(struct battle_state *st, struct battle *bt)
+{
+	(void)bt;
+
+	free(st);
+}
+
+void
+battle_state_menu_handle(struct battle *bt, const union event *ev)
+{
+	assert(bt);
+
 	if (battle_bar_handle(&bt->bar, bt, ev)) {
 		switch (bt->bar.menu) {
 		case BATTLE_BAR_MENU_ATTACK:
@@ -78,10 +100,13 @@
 {
 	assert(bt);
 
-	static struct battle_state self = {
-		.handle = handle,
-	};
+	struct battle_state *state;
+
+	state = alloc_new0(sizeof (*state));
+	state->data = bt;
+	state->handle = handle;
+	state->finish = finish;
 
 	battle_bar_open_menu(&bt->bar);
-	battle_switch(bt, &self);
+	battle_switch(bt, state);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-menu.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,32 @@
+/*
+ * battle-state-menu.c -- battle state (menu)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_MENU_H
+#define MLK_RPG_BATTLE_STATE_MENU_H
+
+struct battle;
+
+union event;
+
+void
+battle_state_menu_handle(struct battle *, const union event *);
+
+void
+battle_state_menu(struct battle *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_MENU_H */
--- a/src/libmlk-rpg/rpg/battle-state-opening.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-opening.c	Sun Feb 13 10:35:26 2022 +0100
@@ -25,8 +25,9 @@
 #include <core/window.h>
 
 #include "battle.h"
+#include "battle-state.h"
+#include "battle-state-check.h"
 #include "battle-state-opening.h"
-#include "battle-state.h"
 
 #define DELAY (1000U)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-opening.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,37 @@
+/*
+ * battle-state-opening.h -- battle state (opening)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_OPENING_H
+#define MLK_RPG_BATTLE_STATE_OPENING_H
+
+struct battle;
+
+struct battle_state_opening {
+	unsigned int elapsed;
+};
+
+int
+battle_state_opening_update(struct battle_state_opening *, struct battle *, unsigned int);
+
+void
+battle_state_opening_draw(const struct battle_state_opening *, const struct battle *);
+
+void
+battle_state_opening(struct battle *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_OPENING_H */
--- a/src/libmlk-rpg/rpg/battle-state-selection.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-selection.c	Sun Feb 13 10:35:26 2022 +0100
@@ -1,5 +1,5 @@
 /*
- * battle-state-selection.h -- battle state (selection)
+ * battle-state-selection.c -- battle state (selection)
  *
  * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
  *
@@ -31,75 +31,79 @@
 #include "battle.h"
 #include "battle-bar.h"
 #include "battle-state.h"
+#include "battle-state-item.h"
+#include "battle-state-menu.h"
+#include "battle-state-selection.h"
+#include "battle-state-sub.h"
 #include "character.h"
 #include "inventory.h"
 #include "selection.h"
 #include "spell.h"
 
-struct select {
+struct self {
+	struct battle_state_selection data;
 	struct battle_state state;
-	struct selection slt;
 };
 
 static void
-use(const struct select *select, struct battle *bt)
+use(const struct battle_state_selection *slt, struct battle *bt)
 {
 	struct inventory_slot *slot;
-	struct character *source, *target;
+	struct battle_entity *source, *target;
 
 	if (bt->bar.sub_grid.selected >= INVENTORY_ITEM_MAX)
 		return;
 	if (!(slot = &bt->inventory->items[bt->bar.sub_grid.selected]))
 		return;
 
-	source = bt->order_cur->ch;
-	target = select->slt.index_side == 0
-		? bt->enemies[select->slt.index_character].ch
-		: bt->team[select->slt.index_character].ch;
+	source = bt->order_cur;
+	target = slt->select.index_side == 0
+		? &bt->enemies[slt->select.index_character]
+		: &bt->team[slt->select.index_character];
 
 	battle_state_item(bt, source, target, slot);
 }
 
 static void
-attack(struct select *select, struct battle *bt)
+attack(struct battle_state_selection *slt, struct battle *bt)
 {
 	struct character *target;
 
-	if (select->slt.index_side == 0)
-		target = bt->enemies[select->slt.index_character].ch;
+	if (slt->select.index_side == 0)
+		target = bt->enemies[slt->select.index_character].ch;
 	else
-		target = bt->team[select->slt.index_character].ch;
+		target = bt->team[slt->select.index_character].ch;
 
 	battle_attack(bt, bt->order_cur->ch, target);
 }
 
 static void
-cast(struct select *select, struct battle *bt)
+cast(struct battle_state_selection *slt, struct battle *bt)
 {
 	struct character *source = bt->order_cur->ch;
 	const struct spell *spell = source->spells[bt->bar.sub_grid.selected];
 
-	battle_cast(bt, source, spell, &select->slt);
+	battle_cast(bt, source, spell, &slt->select);
 }
 
 static void
-select_adj_in(struct select *select, const struct battle_entity *entities, size_t entitiesz, int step)
+select_adj_in(struct battle_state_selection *slt, const struct battle_entity *entities, size_t entitiesz, int step)
 {
-	assert(select->slt.index_character != (unsigned int)-1);
+	assert(slt->select.index_character != (unsigned int)-1);
 
-	unsigned int newselection = select->slt.index_character;
+	unsigned int newselection = slt->select.index_character;
 
 	if (step < 0) {
 		while (newselection > 0) {
 			if (character_ok(entities[--newselection].ch)) {
-				select->slt.index_character = newselection;
+				slt->select.index_character = newselection;
 				break;
 			}
 		}
 	} else {
 		while (newselection < entitiesz) {
 			if (character_ok(entities[++newselection].ch)) {
-				select->slt.index_character = newselection;
+				slt->select.index_character = newselection;
 				break;
 			}
 		}
@@ -107,21 +111,19 @@
 }
 
 static void
-select_adj(struct select *select, const struct battle *bt, int step)
+select_adj(struct battle_state_selection *slt, const struct battle *bt, int step)
 {
-	if (select->slt.index_side == 0)
-		select_adj_in(select, bt->enemies, UTIL_SIZE(bt->enemies), step);
+	if (slt->select.index_side == 0)
+		select_adj_in(slt, bt->enemies, UTIL_SIZE(bt->enemies), step);
 	else
-		select_adj_in(select, bt->team, UTIL_SIZE(bt->team), step);
+		select_adj_in(slt, bt->team, UTIL_SIZE(bt->team), step);
 }
 
 static void
-handle_keydown(struct battle_state *st, struct battle *bt, const union event *ev)
+handle_keydown(struct battle_state_selection *stl, struct battle *bt, const union event *ev)
 {
 	assert(ev->type == EVENT_KEYDOWN);
 
-	struct select *select = st->data;
-
 	switch (ev->key.key) {
 	case KEY_ESCAPE:
 		switch (bt->bar.menu) {
@@ -137,35 +139,35 @@
 	case KEY_ENTER:
 		switch (bt->bar.menu) {
 		case BATTLE_BAR_MENU_ATTACK:
-			attack(select, bt);
+			attack(stl, bt);
 			break;
 		case BATTLE_BAR_MENU_MAGIC:
-			cast(select, bt);
+			cast(stl, bt);
 			break;
 		case BATTLE_BAR_MENU_OBJECTS:
-			use(select, bt);
+			use(stl, bt);
 			break;
 		default:
 			break;
 		}
 		break;
 	case KEY_LEFT:
-		if (select->slt.allowed_sides & SELECTION_SIDE_ENEMY)
-			select->slt.index_side = 0;
+		if (stl->select.allowed_sides & SELECTION_SIDE_ENEMY)
+			stl->select.index_side = 0;
 		break;
 	case KEY_RIGHT:
-		if (select->slt.allowed_sides & SELECTION_SIDE_TEAM)
-			select->slt.index_side = 1;
+		if (stl->select.allowed_sides & SELECTION_SIDE_TEAM)
+			stl->select.index_side = 1;
 		break;
 	case KEY_UP:
-		select_adj(select, bt, -1);
+		select_adj(stl, bt, -1);
 		break;
 	case KEY_DOWN:
-		select_adj(select, bt, +1);
+		select_adj(stl, bt, +1);
 		break;
 	case KEY_TAB:
-		if (select->slt.allowed_kinds == SELECTION_KIND_BOTH)
-			select->slt.index_character = -1;
+		if (stl->select.allowed_kinds == SELECTION_KIND_BOTH)
+			stl->select.index_character = -1;
 		break;
 	default:
 		break;
@@ -192,13 +194,10 @@
 }
 
 static void
-draw_cursors(const struct battle_state *st,
-             const struct battle *bt,
+draw_cursors(const struct battle *bt,
              const struct battle_entity *entities,
              size_t entitiesz)
 {
-	(void)st;
-
 	for (size_t i = 0; i < entitiesz; ++i) {
 		const struct battle_entity *et = &entities[i];
 
@@ -210,35 +209,13 @@
 static void
 handle(struct battle_state *st, struct battle *bt, const union event *ev)
 {
-	(void)st;
-
-	switch (ev->type) {
-	case EVENT_KEYDOWN:
-		handle_keydown(st, bt, ev);
-		break;
-	default:
-		break;
-	}
+	battle_state_selection_handle(st->data, bt, ev);
 }
 
 static void
 draw(const struct battle_state *st, const struct battle *bt)
 {
-	const struct select *select = st->data;
-
-	if (select->slt.index_character == -1U) {
-		/* All selected. */
-		if (select->slt.index_side == 0)
-			draw_cursors(st, bt, bt->enemies, UTIL_SIZE(bt->enemies));
-		else
-			draw_cursors(st, bt, bt->team, UTIL_SIZE(bt->team));
-	} else {
-		/* Select one. */
-		if (select->slt.index_side == 0)
-			draw_cursor(bt, &bt->enemies[select->slt.index_character]);
-		else
-			draw_cursor(bt, &bt->team[select->slt.index_character]);
-	}
+	battle_state_selection_draw(st->data, bt);
 }
 
 static void
@@ -250,20 +227,61 @@
 }
 
 void
-battle_state_selection(struct battle *bt, const struct selection *slt)
+battle_state_selection_init(struct battle_state_selection *stl, const struct selection *select)
+{
+	assert(stl);
+	assert(select);
+
+	memcpy(&stl->select, select, sizeof (*select));
+}
+
+void
+battle_state_selection_handle(struct battle_state_selection *stl, struct battle *bt, const union event *ev)
+{
+	assert(stl);
+	assert(bt);
+	assert(ev);
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		handle_keydown(stl, bt, ev);
+		break;
+	default:
+		break;
+	}
+}
+
+void
+battle_state_selection_draw(struct battle_state_selection *stl, const struct battle *bt)
+{
+	if (stl->select.index_character == -1U) {
+		/* All selected. */
+		if (stl->select.index_side == 0)
+			draw_cursors(bt, bt->enemies, UTIL_SIZE(bt->enemies));
+		else
+			draw_cursors(bt, bt->team, UTIL_SIZE(bt->team));
+	} else {
+		/* Select one. */
+		if (stl->select.index_side == 0)
+			draw_cursor(bt, &bt->enemies[stl->select.index_character]);
+		else
+			draw_cursor(bt, &bt->team[stl->select.index_character]);
+	}
+}
+
+void
+battle_state_selection(struct battle *bt, const struct selection *select)
 {
 	assert(bt);
 
-	struct select *select;
-
-	if (!(select = alloc_new0(sizeof (*select))))
-		panic();
+	struct self *self;
 
-	select->state.data = select;
-	select->state.handle = handle;
-	select->state.draw = draw;
-	select->state.finish = finish;
-	memcpy(&select->slt, slt, sizeof (*slt));
+	self = alloc_new0(sizeof (*self));
+	self->state.data = self;
+	self->state.handle = handle;
+	self->state.draw = draw;
+	self->state.finish = finish;
 
-	battle_switch(bt, &select->state);
+	battle_state_selection_init(&self->data, select);
+	battle_switch(bt, &self->state);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-selection.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,44 @@
+/*
+ * battle-state-selection.h -- battle state (selection)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_SELECTION_H
+#define MLK_RPG_BATTLE_STATE_SELECTION_H
+
+#include <rpg/selection.h>
+
+struct battle;
+
+union event;
+
+struct battle_state_selection {
+	struct selection select;
+};
+
+void
+battle_state_selection_init(struct battle_state_selection *, const struct selection *);
+
+void
+battle_state_selection_handle(struct battle_state_selection *, struct battle *, const union event *);
+
+void
+battle_state_selection_draw(struct battle_state_selection *, const struct battle *);
+
+void
+battle_state_selection(struct battle *, const struct selection *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_SELECTION_H */
--- a/src/libmlk-rpg/rpg/battle-state-sub.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-sub.c	Sun Feb 13 10:35:26 2022 +0100
@@ -17,7 +17,9 @@
  */
 
 #include <assert.h>
+#include <stdlib.h>
 
+#include <core/alloc.h>
 #include <core/event.h>
 #include <core/sprite.h>
 #include <core/trace.h>
@@ -30,6 +32,9 @@
 #include "battle.h"
 #include "battle-bar.h"
 #include "battle-state.h"
+#include "battle-state-menu.h"
+#include "battle-state-selection.h"
+#include "battle-state-sub.h"
 #include "character.h"
 #include "spell.h"
 
@@ -118,6 +123,31 @@
 {
 	(void)st;
 
+	battle_state_sub_handle(bt, ev);
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	(void)st;
+
+	battle_state_sub_draw(bt);
+}
+
+static void
+finish(struct battle_state *st, struct battle *bt)
+{
+	(void)bt;
+
+	free(st);
+}
+
+void
+battle_state_sub_handle(struct battle *bt, const union event *ev)
+{
+	assert(bt);
+	assert(ev);
+
 	switch (ev->type) {
 	case EVENT_KEYDOWN:
 		switch (ev->key.key) {
@@ -144,10 +174,10 @@
 	}
 }
 
-static void
-draw(const struct battle_state *st, const struct battle *bt)
+void
+battle_state_sub_draw(const struct battle *bt)
 {
-	(void)st;
+	assert(bt);
 
 	battle_bar_draw(&bt->bar, bt);
 
@@ -162,10 +192,13 @@
 {
 	assert(bt);
 
-	static struct battle_state self = {
-		.handle = handle,
-		.draw = draw
-	};
+	struct battle_state *state;
 
-	battle_switch(bt, &self);
+	state = alloc_new0(sizeof (*state));
+	state->data = bt;
+	state->handle = handle;
+	state->draw = draw;
+	state->finish = finish;
+
+	battle_switch(bt, state);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-sub.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,35 @@
+/*
+ * battle-state-sub.h -- battle state (sub)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_SUB_H
+#define MLK_RPG_BATTLE_STATE_SUB_H
+
+struct battle;
+
+union event;
+
+void
+battle_state_sub_handle(struct battle *, const union event *);
+
+void
+battle_state_sub_draw(const struct battle *);
+
+void
+battle_state_sub(struct battle *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_SUB_H */
--- a/src/libmlk-rpg/rpg/battle-state-victory.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-victory.c	Sun Feb 13 10:35:26 2022 +0100
@@ -23,15 +23,15 @@
 #include <core/panic.h>
 #include <core/window.h>
 
-#include <rpg/message.h>
-
 #include "battle.h"
 #include "battle-state.h"
+#include "battle-state-closing.h"
+#include "battle-state-victory.h"
 #include "rpg_p.h"
 
-struct victory {
-	struct battle_state self;
-	struct message msg;
+struct self {
+	struct battle_state_victory data;
+	struct battle_state state;
 };
 
 static void
@@ -39,9 +39,7 @@
 {
 	(void)bt;
 
-	struct victory *vic = st->data;
-
-	message_handle(&vic->msg, ev);
+	battle_state_victory_handle(st->data, ev);
 }
 
 static int
@@ -49,12 +47,7 @@
 {
 	(void)bt;
 
-	struct victory *vic = st->data;
-
-	if (message_update(&vic->msg, ticks))
-		battle_state_closing(bt);
-
-	return 0;
+	return battle_state_victory_update(st->data, bt, ticks);
 }
 
 static void
@@ -62,27 +55,14 @@
 {
 	(void)bt;
 
-	const struct victory *vic = st->data;
-
-	message_draw(&vic->msg);
+	battle_state_victory_draw(st->data);
 }
 
 void
-battle_state_victory(struct battle *bt)
+battle_state_victory_init(struct battle_state_victory *vic, struct battle *bt)
 {
 	assert(bt);
 
-	struct victory *vic;
-
-	if (!(vic = alloc_new0(sizeof (*vic))))
-		panic();
-
-	/* TODO: compute money, xp and drop. */
-	vic->self.data = vic;
-	vic->self.handle = handle;
-	vic->self.update = update;
-	vic->self.draw = draw;
-
 	vic->msg.text[0] = _("Victory!");
 	vic->msg.theme = bt->theme;
 	vic->msg.flags = MESSAGE_FLAGS_AUTOMATIC |
@@ -99,8 +79,53 @@
 	vic->msg.x = (window.w - vic->msg.w) / 2;
 
 	bt->status = BATTLE_STATUS_WON;
-	battle_switch(bt, &vic->self);
 
 	if (bt->music[1])
-		music_play(bt->music[1], MUSIC_NONE, 0);
+		music_play(bt->music[1], MUSIC_NONE);
+}
+
+void
+battle_state_victory_handle(struct battle_state_victory *vic, const union event *ev)
+{
+	assert(vic);
+
+	message_handle(&vic->msg, ev);
+}
+
+int
+battle_state_victory_update(struct battle_state_victory *vic, struct battle *bt, unsigned int ticks)
+{
+	assert(vic);
+	assert(bt);
+
+	if (message_update(&vic->msg, ticks))
+		battle_state_closing(bt);
+
+	return 0;
 }
+
+void
+battle_state_victory_draw(struct battle_state_victory *vic)
+{
+	assert(vic);
+
+	message_draw(&vic->msg);
+}
+
+void
+battle_state_victory(struct battle *bt)
+{
+	assert(bt);
+
+	struct self *self;
+
+	/* TODO: compute money, xp and drop. */
+	self = alloc_new0(sizeof (*self));
+	self->state.data = self;
+	self->state.handle = handle;
+	self->state.update = update;
+	self->state.draw = draw;
+
+	battle_state_victory_init(&self->data, bt);
+	battle_switch(bt, &self->state);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-victory.h	Sun Feb 13 10:35:26 2022 +0100
@@ -0,0 +1,47 @@
+/*
+ * battle-state-victory.h -- battle state (victory)
+ *
+ * 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.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_VICTORY_H
+#define MLK_RPG_BATTLE_STATE_VICTORY_H
+
+#include <rpg/message.h>
+
+struct battle;
+
+union event;
+
+struct battle_state_victory {
+	struct message msg;
+};
+
+void
+battle_state_victory_init(struct battle_state_victory *, struct battle *);
+
+void
+battle_state_victory_handle(struct battle_state_victory *, const union event *);
+
+int
+battle_state_victory_update(struct battle_state_victory *, struct battle *, unsigned int);
+
+void
+battle_state_victory_draw(struct battle_state_victory *);
+
+void
+battle_state_victory(struct battle *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_VICTORY_H */
--- a/src/libmlk-rpg/rpg/battle-state.h	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state.h	Sun Feb 13 10:35:26 2022 +0100
@@ -50,43 +50,6 @@
 void
 battle_state_finish(struct battle_state *, struct battle *);
 
-/* States switchers, defined in their own files. */
-void
-battle_state_ai(struct battle *);
-
-void
-battle_state_attacking(struct battle *, struct character *, struct character *);
-
-void
-battle_state_item(struct battle *,
-                  struct character *,
-                  struct character *,
-                  struct inventory_slot *);
-
-void
-battle_state_check(struct battle *);
-
-void
-battle_state_closing(struct battle *);
-
-void
-battle_state_lost(struct battle *);
-
-void
-battle_state_menu(struct battle *);
-
-void
-battle_state_opening(struct battle *);
-
-void
-battle_state_selection(struct battle *, const struct selection *);
-
-void
-battle_state_sub(struct battle *);
-
-void
-battle_state_victory(struct battle *);
-
 CORE_END_DECLS
 
 #endif /* !MLK_RPG_BATTLE_STATE_H */
--- a/src/libmlk-rpg/rpg/battle.c	Fri Jan 07 21:50:37 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle.c	Sun Feb 13 10:35:26 2022 +0100
@@ -39,6 +39,11 @@
 #include "battle.h"
 #include "battle-indicator.h"
 #include "battle-state.h"
+#include "battle-state-ai.h"
+#include "battle-state-attacking.h"
+#include "battle-state-check.h"
+#include "battle-state-menu.h"
+#include "battle-state-opening.h"
 #include "character.h"
 #include "inventory.h"
 #include "item.h"
@@ -235,7 +240,7 @@
 
 	/* Play music if present. */
 	if (bt->music[0])
-		music_play(bt->music[0], MUSIC_LOOP, 0);
+		music_play(bt->music[0], MUSIC_LOOP);
 }
 
 void
@@ -285,7 +290,7 @@
 			target = random_select(bt->team, BATTLE_TEAM_MAX)->ch;
 	}
 
-	battle_state_attacking(bt, source, target);
+	battle_state_attacking(battle_find(bt, source), battle_find(bt, target), bt);
 }
 
 void