changeset 192:4ad7420ab678

rpg: add minimalist battle system, continue #2477 @60h - Implement battle as states, - Add basic support for spells (no calculation yet), - Add won/lost state, - Add animations and messages, - Add order.
author David Demelier <markand@malikania.fr>
date Sat, 07 Nov 2020 16:00:39 +0100
parents 633a25df450e
children 78774cc2cc6b
files examples/CMakeLists.txt examples/assets/images/black-cat.png examples/assets/images/haunted-wood.png examples/assets/sounds/fire.wav examples/assets/sprites/cursor.png examples/battle/character-john.c examples/battle/character-john.h examples/battle/registry.c examples/battle/registry.h examples/battle/spell-fire.c examples/battle/spell-fire.h examples/example-battle.c examples/example-gridmenu.c libcore/core/spell.c libcore/core/spell.h libcore/core/sprite.c libcore/core/sprite.h librpg/CMakeLists.txt librpg/rpg/battle-bar.c librpg/rpg/battle-bar.h librpg/rpg/battle-indicator.c librpg/rpg/battle-indicator.h librpg/rpg/battle-state-ai.c librpg/rpg/battle-state-ai.h librpg/rpg/battle-state-check.c librpg/rpg/battle-state-check.h librpg/rpg/battle-state-closing.c librpg/rpg/battle-state-closing.h librpg/rpg/battle-state-lost.c librpg/rpg/battle-state-lost.h librpg/rpg/battle-state-menu.c librpg/rpg/battle-state-menu.h librpg/rpg/battle-state-opening.c librpg/rpg/battle-state-opening.h librpg/rpg/battle-state-selection.c librpg/rpg/battle-state-selection.h librpg/rpg/battle-state-sub.c librpg/rpg/battle-state-sub.h librpg/rpg/battle-state-victory.c librpg/rpg/battle-state-victory.h librpg/rpg/battle-state.c librpg/rpg/battle-state.h librpg/rpg/battle.c librpg/rpg/battle.h librpg/rpg/character.c librpg/rpg/character.h librpg/rpg/selection.h librpg/rpg/spell.c librpg/rpg/spell.h libui/ui/gridmenu.c libui/ui/gridmenu.h libui/ui/theme.c libui/ui/theme.h
diffstat 53 files changed, 4338 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/examples/CMakeLists.txt	Sat Nov 07 15:40:34 2020 +0100
+++ b/examples/CMakeLists.txt	Sat Nov 07 16:00:39 2020 +0100
@@ -19,6 +19,24 @@
 project(examples)
 
 molko_define_executable(
+	TARGET example-battle
+	SOURCES
+		${examples_SOURCE_DIR}/example-battle.c
+		${examples_SOURCE_DIR}/battle/spell-fire.c
+		${examples_SOURCE_DIR}/battle/spell-fire.h
+		${examples_SOURCE_DIR}/battle/registry.c
+		${examples_SOURCE_DIR}/battle/registry.h
+	FOLDER examples
+	ASSETS
+		${examples_SOURCE_DIR}/assets/images/haunted-wood.png
+		${examples_SOURCE_DIR}/assets/images/black-cat.png
+		${examples_SOURCE_DIR}/assets/sprites/cursor.png
+		${examples_SOURCE_DIR}/assets/sprites/explosion.png
+		${examples_SOURCE_DIR}/assets/sounds/fire.wav
+	LIBRARIES librpg libadventure
+)
+
+molko_define_executable(
 	TARGET example-action
 	SOURCES example-action.c
 	FOLDER examples
Binary file examples/assets/images/black-cat.png has changed
Binary file examples/assets/images/haunted-wood.png has changed
Binary file examples/assets/sounds/fire.wav has changed
Binary file examples/assets/sprites/cursor.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/battle/character-john.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,48 @@
+/*
+ * character-john.c -- john character
+ *
+ * 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 <rpg/character.h>
+
+#include "character-john.h"
+#include "spell-fire.h"
+#include "registry.h"
+
+static void
+adventurer_reset(struct character *ch)
+{
+	/* TODO: this function should compute the attack thanks to the level. */
+	ch->hpmax = 120;
+	ch->mpmax = 50;
+	ch->atk = 50;
+	ch->def = 50;
+	ch->agt = 50;
+	ch->luck = 50;
+}
+
+const struct character character_john = {
+	.name = "John ",
+	.type = "Adventurer",
+	.level = 1,
+	.hp = 120,
+	.mp = 50,
+	.reset = adventurer_reset,
+	.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
+	.spells = {
+		&spell_fire
+	}
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/battle/character-john.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,24 @@
+/*
+ * character-john.h -- john character
+ *
+ * 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 EXAMPLES_BATTLE_CHARACTER_JOHN_H
+#define EXAMPLES_BATTLE_CHARACTER_JOHN_H
+
+const struct character character_john;
+
+#endif /* !EXAMPLES_BATTLE_CHARACTER_JOHN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/battle/registry.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,111 @@
+/*
+ * registry.h -- registry of resources
+ *
+ * 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 <stddef.h>
+
+#include <core/image.h>
+#include <core/panic.h>
+#include <core/util.h>
+
+#include <adventure/assets/sprites/john.h>
+
+#include <assets/images/haunted-wood.h>
+#include <assets/images/black-cat.h>
+
+#include <assets/sprites/cursor.h>
+#include <assets/sprites/explosion.h>
+
+#include <assets/sounds/fire.h>
+
+#include "registry.h"
+
+struct texture registry_textures[REGISTRY_TEXTURE_NUM];
+struct sprite registry_sprites[REGISTRY_TEXTURE_NUM];
+struct sound registry_sounds[REGISTRY_SOUND_NUM];
+
+#define REGISTRY_TEXTURE(s, ptr, cw, ch) \
+	{ (s), (ptr), sizeof ((ptr)), (cw), (ch) }
+
+static const struct {
+	enum registry_texture index;
+	const void *data;
+	size_t datasz;
+	unsigned int cellw;
+	unsigned int cellh;
+} textures[] = {
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_CURSOR, sprites_cursor, 24, 24),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_EXPLOSION, sprites_explosion, 256, 256),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_JOHN, sprites_john, 48, 48),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_HAUNTED_WOOD, images_haunted_wood, 0, 0),
+	REGISTRY_TEXTURE(REGISTRY_TEXTURE_BLACK_CAT, images_black_cat, 0, 0)
+};
+
+#define REGISTRY_SOUND(s, ptr) \
+	{ (s), (ptr), sizeof ((ptr)) }
+
+static const struct {
+	enum registry_sound index;
+	const void *data;
+	size_t datasz;
+} sounds[] = {
+	REGISTRY_SOUND(REGISTRY_SOUND_FIRE, sounds_fire)
+};
+
+static void
+load_textures_and_sprites(void)
+{
+	for (size_t i = 0; i < NELEM(textures); ++i) {
+		struct texture *texture = &registry_textures[textures[i].index];
+		struct sprite *sprite = &registry_sprites[textures[i].index];
+
+		if (!image_openmem(texture, textures[i].data, textures[i].datasz))
+			panic();
+
+		if (textures[i].cellw == 0 || textures[i].cellh == 0)
+			sprite_init(sprite, texture, texture->w, texture->h);
+		else
+			sprite_init(sprite, texture, textures[i].cellw, textures[i].cellh);
+	}
+}
+
+static void
+load_sounds(void)
+{
+	for (size_t i = 0; i < NELEM(sounds); ++i) {
+		struct sound *sound = &registry_sounds[sounds[i].index];
+
+		if (!sound_openmem(sound, sounds[i].data, sounds[i].datasz))
+			panic();
+	}
+}
+
+void
+registry_init(void)
+{
+	load_textures_and_sprites();
+	load_sounds();
+}
+
+void
+registry_finish(void)
+{
+	for (size_t i = 0; i < NELEM(registry_textures); ++i)
+		texture_finish(&registry_textures[i]);
+	for (size_t i = 0; i < NELEM(registry_sounds); ++i)
+		sound_finish(&registry_sounds[i]);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/battle/registry.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,59 @@
+/*
+ * registry.h -- registry of resources
+ *
+ * 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 EXAMPLES_BATTLE_REGISTRY_H
+#define EXAMPLES_BATTLE_REGISTRY_H
+
+#include <core/sound.h>
+#include <core/sprite.h>
+#include <core/texture.h>
+
+enum registry_texture {
+	/* UI. */
+	REGISTRY_TEXTURE_CURSOR,
+
+	/* Animations. */
+	REGISTRY_TEXTURE_EXPLOSION,
+
+	/* Characters. */
+	REGISTRY_TEXTURE_JOHN,
+
+	/* Enemies. */
+	REGISTRY_TEXTURE_HAUNTED_WOOD,
+	REGISTRY_TEXTURE_BLACK_CAT,
+
+	/* Unused.*/
+	REGISTRY_TEXTURE_NUM
+};
+
+enum registry_sound {
+	REGISTRY_SOUND_FIRE,
+	REGISTRY_SOUND_NUM
+};
+
+extern struct texture registry_textures[REGISTRY_TEXTURE_NUM];
+extern struct sprite registry_sprites[REGISTRY_TEXTURE_NUM];
+extern struct sound registry_sounds[REGISTRY_SOUND_NUM];
+
+void
+registry_init(void);
+
+void
+registry_finish(void);
+
+#endif /* !EXAMPLES_BATTLE_REGISTRY_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/battle/spell-fire.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,116 @@
+/*
+ * spell-fire.c -- example of spell: fire
+ *
+ * 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 <stdlib.h>
+
+#include <core/action.h>
+#include <core/animation.h>
+#include <core/alloc.h>
+
+#include <ui/align.h>
+
+#include <rpg/battle.h>
+#include <rpg/character.h>
+#include <rpg/spell.h>
+
+#include "registry.h"
+#include "spell-fire.h"
+
+struct data {
+	struct battle *battle;
+	struct animation animation;
+	struct action action;
+	unsigned int selection;
+};
+
+static bool
+update(struct action *act, unsigned int ticks)
+{
+	struct data *data = act->data;
+
+	return animation_update(&data->animation, ticks);
+}
+
+static void
+draw(struct action *act)
+{
+	const struct data *data = act->data;
+	const struct battle_entity *et = &data->battle->enemies[data->selection];
+	int x, y;
+
+	align(ALIGN_CENTER,
+	    &x, &y, data->animation.sprite->cellw, data->animation.sprite->cellh,
+	    et->x, et->y, et->ch->sprite->cellw, et->ch->sprite->cellh);
+
+	animation_draw(&data->animation, x, y);
+}
+
+static void
+end(struct action *act)
+{
+	struct data *data = act->data;
+	struct character *ch = data->battle->enemies[data->selection].ch;
+
+	/* TODO: compute damage. */
+	const unsigned int damage = 100;
+	if ((unsigned int)ch->hp < damage)
+		ch->hp = 0;
+	else
+		ch->hp -= damage;
+
+	battle_indicator_hp(data->battle, data->battle->enemies[data->selection].ch, 100);
+}
+
+static void
+finish(struct action *act)
+{
+	free(act->data);
+}
+
+static void
+fire_action(struct battle *bt, struct character *owner, unsigned int selection)
+{
+	struct data *data;
+
+	(void)owner;
+
+	data = alloc_zero(1, sizeof (*data));
+	data->battle = bt;
+	data->selection = selection;
+	data->action.data = data;
+	data->action.update = update;
+	data->action.draw = draw;
+	data->action.finish = finish;
+	data->action.end = end;
+
+	animation_init(&data->animation, &registry_sprites[REGISTRY_TEXTURE_EXPLOSION], 12);
+	animation_start(&data->animation);
+
+	sound_play(&registry_sounds[REGISTRY_SOUND_FIRE]);
+
+	action_stack_add(&bt->actions[0], &data->action);
+}
+
+const struct spell spell_fire = {
+	.name = "Fire",
+	.description = "A delicate fire.",
+	.mp = 5,
+	.type = SPELL_TYPE_FIRE,
+	.selection = SELECTION_ENEMY_ONE,
+	.action = fire_action
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/battle/spell-fire.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,24 @@
+/*
+ * spell-fire.c -- example of spell: fire
+ *
+ * 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 EXAMPLES_BATTLE_SPELL_FIRE_H
+#define EXAMPLES_BATTLE_SPELL_FIRE_H
+
+extern const struct spell spell_fire;
+
+#endif /* !EXAMPLES_BATTLE_SPELL_FIRE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/example-battle.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,266 @@
+/*
+ * example-battle.c -- show how to use battle
+ *
+ * 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 <string.h>
+
+#include <core/clock.h>
+#include <core/core.h>
+#include <core/event.h>
+#include <core/image.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/sprite.h>
+#include <core/sys.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 <ui/ui.h>
+
+#include <rpg/character.h>
+#include <rpg/battle.h>
+#include <rpg/rpg.h>
+#include <rpg/spell.h>
+
+#include "battle/registry.h"
+#include "battle/spell-fire.h"
+
+#define W 1280
+#define H 720
+
+static void
+adventurer_reset(struct character *ch)
+{
+	/* TODO: this function should compute the attack thanks to the level. */
+	ch->hpmax = 120;
+	ch->mpmax = 50;
+	ch->atk = 50;
+	ch->def = 50;
+	ch->agt = 50;
+	ch->luck = 50;
+}
+
+static void
+haunted_wood_reset(struct character *ch)
+{
+	ch->hpmax = ch->hp = 2000;
+	ch->mpmax = ch->mp = 250;
+	ch->atk = 178;
+	ch->def = 80;
+	ch->agt = 80;
+	ch->luck = 100;
+}
+
+static void
+black_cat_reset(struct character *ch)
+{
+	ch->hpmax = ch->hp = 126;
+	ch->mpmax = ch->mp = 38;
+	ch->atk = 22;
+	ch->def = 19;
+	ch->agt = 21;
+	ch->luck = 14;
+}
+
+static struct character team[] = {
+	{
+		.name = "Molko",
+		.type = "Adventurer",
+		.level = 1,
+		.hp = 120,
+		.mp = 50,
+		.reset = adventurer_reset,
+		.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
+		.spells = {
+			&spell_fire
+		}
+	},
+	{
+		.name = "Fake Molko",
+		.type = "Adventurer",
+		.level = 1,
+		.hp = 120,
+		.mp = 50,
+		.reset = adventurer_reset,
+		.sprite = &registry_sprites[REGISTRY_TEXTURE_JOHN],
+		.spells = {
+			&spell_fire
+		}
+	}
+};
+
+static void
+haunted_wood_strat(struct character *ch, struct battle *bt)
+{
+	(void)ch;
+
+	/* TODO: Select randomly. */
+	battle_attack(bt, bt->order_cur->ch, bt->team[0].ch);
+}
+
+static void
+black_cat_strat(struct character *ch, struct battle *bt)
+{
+	(void)ch;
+
+	/* TODO: Select randomly. */
+	battle_attack(bt, bt->order_cur->ch, bt->team[0].ch);
+}
+
+static struct character haunted_wood = {
+	.name = "Haunted Wood",
+	.type = "Wood",
+	.level = 30,
+	.reset = haunted_wood_reset,
+	.sprite = &registry_sprites[REGISTRY_TEXTURE_HAUNTED_WOOD],
+	.exec = haunted_wood_strat
+};
+
+static struct character black_cat = {
+	.name = "Black Cat",
+	.type = "Cat",
+	.level = 6,
+	.reset = black_cat_reset,
+	.sprite = &registry_sprites[REGISTRY_TEXTURE_BLACK_CAT],
+	.exec = black_cat_strat
+};
+
+static void
+init(void)
+{
+	struct clock clock = {0};
+
+	if (!core_init() || !ui_init() || !rpg_init())
+		panic();
+	if (!window_open("Example - Battle", W, H))
+		panic();
+
+	/* Wait 1 second to let all events processed. */
+	while (clock_elapsed(&clock) < 1000)
+		for (union event ev; event_poll(&ev); )
+			if (ev.type == EVENT_QUIT)
+				return;
+
+	registry_init();
+
+	/* Set cursor in default theme. */
+	theme_default()->sprites[THEME_SPRITE_CURSOR] = &registry_sprites[REGISTRY_TEXTURE_CURSOR];
+}
+
+static void
+prepare_to_fight(struct battle *bt)
+{
+	assert(bt);
+
+//	bt->enemies[0].ch = &haunted_wood;
+	bt->team[0].ch = &team[0];
+	bt->team[1].ch = &team[1];
+
+	/* Positionate the single ennemy to the left. */
+	align(ALIGN_LEFT,
+	    &bt->enemies[0].x, &bt->enemies[0].y, haunted_wood.sprite->cellw, haunted_wood.sprite->cellh,
+	    0, 0, window.w, window.h);
+
+	/* Black cat is near the previous monster. */
+	bt->enemies[1].ch = &black_cat;
+	bt->enemies[1].x = 500;
+	bt->enemies[1].y = 100;
+
+	battle_start(bt);
+}
+
+static void
+run(void)
+{
+	struct clock clock;
+	struct battle battle = {0};
+	struct label info = {
+		.text = "Press <Space> to start a battle.",
+		.x = 10,
+		.y = 10,
+		.flags = LABEL_FLAGS_SHADOW
+	};
+
+	clock_start(&clock);
+
+	for (;;) {
+		unsigned int elapsed = clock_elapsed(&clock);
+
+		clock_start(&clock);
+
+		for (union event ev; event_poll(&ev); ) {
+			switch (ev.type) {
+			case EVENT_QUIT:
+				return;
+			case EVENT_KEYDOWN:
+				if (ev.key.key == KEY_SPACE && !battle.state)
+					prepare_to_fight(&battle);
+				else if (battle.state)
+					battle_handle(&battle, &ev);
+				break;
+			default:
+				if (battle.state)
+					battle_handle(&battle, &ev);
+				break;
+			}
+		}
+
+		if (battle.state) {
+			if (battle_update(&battle, elapsed))
+				battle_finish(&battle);
+			else
+				battle_draw(&battle);
+		} else {
+			painter_set_color(0x4f8fbaff);
+			painter_clear();
+			label_draw(&info);
+		}
+
+		painter_present();
+
+		if ((elapsed = clock_elapsed(&clock)) < 20)
+			delay(1);
+	}
+}
+
+static void
+quit(void)
+{
+	registry_finish();
+	theme_finish();
+	window_finish();
+	sys_finish();
+}
+
+int
+main(int argc, char **argv)
+{
+	--argc;
+	++argv;
+
+	init();
+	run();
+	quit();
+
+	return 0;
+}
--- a/examples/example-gridmenu.c	Sat Nov 07 15:40:34 2020 +0100
+++ b/examples/example-gridmenu.c	Sat Nov 07 16:00:39 2020 +0100
@@ -99,7 +99,7 @@
 			}
 		}
 
-		if (menu.state == GRIDMENU_STATE_SELECTED) {
+		if (menu.state == GRIDMENU_STATE_ACTIVATED) {
 			tracef("selected index: %u", (unsigned int)menu.selected);
 			gridmenu_reset(&menu);
 		}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcore/core/spell.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,19 @@
+/*
+ * character.h -- character definition
+ *
+ * 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 "spell.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcore/core/spell.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,92 @@
+/*
+ * spell.h -- magic spells
+ *
+ * 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_SPELL_H
+#define MOLKO_SPELL_H
+
+/**
+ * \file spell.h
+ * \brief Magic spells.
+ */
+
+struct character;
+struct battle;
+
+/**
+ * \brief Kind of spell.
+ */
+enum spell_type {
+	SPELL_TYPE_NEUTRAL,     /*!< No type. */
+	SPELL_TYPE_FIRE,        /*!< Fire (affected by attack). */
+	SPELL_TYPE_WIND,        /*!< Wind (affected by agility). */
+	SPELL_TYPE_WATER,       /*!< Water (affected by luck). */
+	SPELL_TYPE_EARTH,       /*!< Earth (affected by defense). */
+	SPELL_TYPE_CHAOS,       /*!< Chaotic. */
+	SPELL_TYPE_HOLY,        /*!< Holy. */
+	SPELL_TYPE_TIME         /*!< Chrono. */
+};
+
+/**
+ * \brief Kind of selection.
+ */
+enum spell_selection {
+	SPELL_SELECTION_SELF,           /*!< Owner only. */
+	SPELL_SELECTION_TEAM_ONE,       /*!< One member of the team. */
+	SPELL_SELECTION_TEAM_ALL,       /*!< All members of the team. */
+	SPELL_SELECTION_TEAM_COMBINED,  /*!< One or all members of the team. */
+	SPELL_SELECTION_ENEMY_ONE,      /*!< One enemy. */
+	SPELL_SELECTION_ENEMY_ALL,      /*!< All enemies. */
+	SPELL_SELECTION_ENEMY_COMBINED  /*!< One or all enemies. */
+};
+
+/**
+ * \brief Spell structure.
+ *
+ * A spell is a magical object owned by a character and can be used in a battle
+ * and/or outside of a battle. It costs a certain amount of magic points and is
+ * typed into a category (earth, fire, etc, …).
+ *
+ * A spell can select one character or all.
+ */
+struct spell {
+	const char *name;               /*!< (RO) Spell name. */
+	const char *description;        /*!< (RO) Long description. */
+	unsigned int mp;                /*!< (RO) Number of MP required. */
+	enum spell_type type;           /*!< (RO) Kind of spell. */
+
+	/**
+	 * Execute the spell in a battle.
+	 *
+	 * \param bt the current battle
+	 * \param owner the spell owner
+	 * \param selection the selection flags
+	 */
+	void (*action)(struct battle *bt, struct character *owner, int selection);
+
+	/**
+	 * Use the spell outside of a battle.
+	 *
+	 * This function is optional.
+	 *
+	 * \param owner the spell owner
+	 * \param selection the selection flags
+	 */
+	void (*use)(struct character *owner, int selection);
+};
+
+#endif /* !MOLKO_SPELL_H */
--- a/libcore/core/sprite.c	Sat Nov 07 15:40:34 2020 +0100
+++ b/libcore/core/sprite.c	Sat Nov 07 16:00:39 2020 +0100
@@ -47,7 +47,7 @@
 }
 
 bool
-sprite_draw(struct sprite *sprite, unsigned int r, unsigned int c, int x, int y)
+sprite_draw(const struct sprite *sprite, unsigned int r, unsigned int c, int x, int y)
 {
 	assert(sprite_ok(sprite));
 	assert(r < sprite->nrows);
--- a/libcore/core/sprite.h	Sat Nov 07 15:40:34 2020 +0100
+++ b/libcore/core/sprite.h	Sat Nov 07 16:00:39 2020 +0100
@@ -106,6 +106,6 @@
  * \return False in case of rendering error.
  */
 bool
-sprite_draw(struct sprite *sprite, unsigned int r, unsigned int c, int x, int y);
+sprite_draw(const struct sprite *sprite, unsigned int r, unsigned int c, int x, int y);
 
 #endif /* !MOLKO_SPRITE_H */
--- a/librpg/CMakeLists.txt	Sat Nov 07 15:40:34 2020 +0100
+++ b/librpg/CMakeLists.txt	Sat Nov 07 16:00:39 2020 +0100
@@ -20,6 +20,34 @@
 
 set(
 	SOURCES
+	${librpg_SOURCE_DIR}/rpg/battle.c
+	${librpg_SOURCE_DIR}/rpg/battle.h
+	${librpg_SOURCE_DIR}/rpg/battle-indicator.c
+	${librpg_SOURCE_DIR}/rpg/battle-indicator.h
+	${librpg_SOURCE_DIR}/rpg/battle-bar.c
+	${librpg_SOURCE_DIR}/rpg/battle-bar.h
+	${librpg_SOURCE_DIR}/rpg/battle-state.c
+	${librpg_SOURCE_DIR}/rpg/battle-state.h
+	${librpg_SOURCE_DIR}/rpg/battle-state-ai.c
+	${librpg_SOURCE_DIR}/rpg/battle-state-ai.h
+	${librpg_SOURCE_DIR}/rpg/battle-state-check.c
+	${librpg_SOURCE_DIR}/rpg/battle-state-check.h
+	${librpg_SOURCE_DIR}/rpg/battle-state-closing.c
+	${librpg_SOURCE_DIR}/rpg/battle-state-closing.h
+	${librpg_SOURCE_DIR}/rpg/battle-state-menu.c
+	${librpg_SOURCE_DIR}/rpg/battle-state-menu.h
+	${librpg_SOURCE_DIR}/rpg/battle-state-lost.c
+	${librpg_SOURCE_DIR}/rpg/battle-state-lost.h
+	${librpg_SOURCE_DIR}/rpg/battle-state-opening.c
+	${librpg_SOURCE_DIR}/rpg/battle-state-opening.h
+	${librpg_SOURCE_DIR}/rpg/battle-state-selection.c
+	${librpg_SOURCE_DIR}/rpg/battle-state-selection.h
+	${librpg_SOURCE_DIR}/rpg/battle-state-sub.c
+	${librpg_SOURCE_DIR}/rpg/battle-state-sub.h
+	${librpg_SOURCE_DIR}/rpg/battle-state-victory.c
+	${librpg_SOURCE_DIR}/rpg/battle-state-victory.h
+	${librpg_SOURCE_DIR}/rpg/character.c
+	${librpg_SOURCE_DIR}/rpg/character.h
 	${librpg_SOURCE_DIR}/rpg/inventory.c
 	${librpg_SOURCE_DIR}/rpg/inventory.h
 	${librpg_SOURCE_DIR}/rpg/item.c
@@ -32,6 +60,9 @@
 	${librpg_SOURCE_DIR}/rpg/message.h
 	${librpg_SOURCE_DIR}/rpg/rpg.c
 	${librpg_SOURCE_DIR}/rpg/rpg.h
+	${librpg_SOURCE_DIR}/rpg/selection.h
+	${librpg_SOURCE_DIR}/rpg/spell.c
+	${librpg_SOURCE_DIR}/rpg/spell.h
 	${librpg_SOURCE_DIR}/rpg/walksprite.c
 	${librpg_SOURCE_DIR}/rpg/walksprite.h
 )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-bar.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,356 @@
+/*
+ * battle-bar.h -- battle status bar and 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 <stdio.h>
+#include <string.h>
+
+#include <core/event.h>
+#include <core/font.h>
+#include <core/window.h>
+#include <core/util.h>
+
+#include <ui/align.h>
+#include <ui/theme.h>
+
+#include "battle.h"
+#include "battle-bar.h"
+#include "character.h"
+#include "spell.h"
+
+static void
+draw_status_character_stats(const struct battle *bt,
+                            const struct character *ch,
+                            int x,
+                            int y,
+                            unsigned int w,
+                            unsigned int h)
+{
+	struct theme *theme = BATTLE_THEME(bt);
+	struct label label;
+	unsigned int spacing, lw, lh;
+	char line[64];
+
+	/* Compute spacing between elements. */
+	spacing = h - (font_height(theme->fonts[THEME_FONT_INTERFACE]) * 3);
+	spacing /= 4;
+
+	/* Reuse the same label. */
+	label.theme = theme;
+	label.text = line;
+	label.flags = LABEL_FLAGS_SHADOW;
+
+	/* HP. */
+	snprintf(line, sizeof (line), "%d/%u", ch->hp, ch->hpmax);
+	label_query(&label, &lw, &lh);
+	label.x = x + w - lw - theme->padding;
+	label.y = y + spacing;
+	label_draw(&label);
+
+	/* MP. */
+	snprintf(line, sizeof (line), "%d/%u", ch->mp, ch->mpmax);
+	label_query(&label, &lw, &lh);
+	label.x = x + w - lw - theme->padding;
+	label.y = label.y + lh + spacing;
+	label_draw(&label);
+
+	/* Status. */
+	/* TODO: list all status. */
+}
+
+static void
+draw_status_character(const struct battle_bar *bar,
+                      const struct battle *bt,
+                      const struct character *ch,
+                      unsigned int index)
+{
+	int x, y;
+	unsigned int w, h;
+
+	/* Compute bounding box for rendering. */
+	w = bar->status_frame.w / BATTLE_TEAM_MAX;
+	h = bar->status_frame.h;
+	x = bar->status_frame.x + (index * w);
+	y = bar->status_frame.y;
+
+	draw_status_character_stats(bt, ch, x, y, w, h);
+}
+
+static void
+draw_status_characters(const struct battle_bar *bar, const struct battle *bt)
+{
+	const struct battle_entity *et;
+	unsigned int index = 0;
+
+	BATTLE_TEAM_FOREACH(bt, et) {
+		if (character_ok(et->ch))
+			draw_status_character(bar, bt, et->ch, index);
+
+		++index;
+	}
+}
+
+static void
+draw_status(const struct battle_bar *bar, const struct battle *bt)
+{
+	frame_draw(&bar->status_frame);
+	draw_status_characters(bar, bt);
+}
+
+static void
+draw_menu(const struct battle_bar *bar, const struct battle *bt)
+{
+	static struct {
+		unsigned int w, h;
+		enum align align;
+		struct label label;
+	} buttons[] = {
+		{
+			.align = ALIGN_TOP,
+			.label = {
+				.text = "Attack",
+				.flags = LABEL_FLAGS_SHADOW
+			}
+		},
+		{
+			.align = ALIGN_RIGHT,
+			.label = {
+				.text = "Magic",
+				.flags = LABEL_FLAGS_SHADOW
+			}
+		},
+		{
+			.align = ALIGN_BOTTOM,
+			.label = {
+				.text = "Objects",
+				.flags = LABEL_FLAGS_SHADOW
+			}
+		},
+		{
+			.align = ALIGN_LEFT,
+			.label = {
+				.text = "Special",
+				.flags = LABEL_FLAGS_SHADOW
+			}
+		}
+	};
+
+	struct theme theme;
+	int bx, by;
+	unsigned int bw, bh;
+
+	/* Copy theme according to menu selection. */
+	theme_shallow(&theme, bt->theme);
+
+	/* Compute bounding box with margins removed. */
+	bx = bar->menu_frame.x + theme.padding;
+	by = bar->menu_frame.y + theme.padding;
+	bw = bar->menu_frame.w - theme.padding * 2;
+	bh = bar->menu_frame.h - theme.padding * 2;
+
+	/* Draw menu frame. */
+	frame_draw(&bar->menu_frame);
+
+	for (size_t i = 0; i < NELEM(buttons); ++i) {
+		buttons[i].label.theme = &theme;
+
+		label_query(&buttons[i].label, &buttons[i].w, &buttons[i].h);
+
+		/* Change theme if it's selected. */
+		if ((size_t)bar->menu == i && bar->state != BATTLE_BAR_STATE_NONE)
+			theme.colors[THEME_COLOR_NORMAL] = BATTLE_THEME(bt)->colors[THEME_COLOR_SELECTED];
+		else
+			theme.colors[THEME_COLOR_NORMAL] = BATTLE_THEME(bt)->colors[THEME_COLOR_NORMAL];
+
+		align(buttons[i].align,
+		    &buttons[i].label.x, &buttons[i].label.y, buttons[i].w, buttons[i].h,
+		    bx, by, bw, bh);
+		label_draw(&buttons[i].label);
+	}
+}
+
+static void
+draw_sub(const struct battle_bar *bar)
+{
+	gridmenu_draw(&bar->sub_grid);
+}
+
+static bool
+handle_keydown(struct battle_bar *bar, const union event *ev)
+{
+	assert(ev->type == EVENT_KEYDOWN);
+
+	switch (bar->state) {
+	case BATTLE_BAR_STATE_MENU:
+		/* We are selecting a main menu entry. */
+		switch (ev->key.key) {
+		case KEY_UP:
+			bar->menu = BATTLE_BAR_MENU_ATTACK;
+			break;
+		case KEY_RIGHT:
+			bar->menu = BATTLE_BAR_MENU_MAGIC;
+			break;
+		case KEY_DOWN:
+			bar->menu = BATTLE_BAR_MENU_OBJECTS;
+			break;
+		case KEY_LEFT:
+			bar->menu = BATTLE_BAR_MENU_SPECIAL;
+			break;
+		case KEY_ENTER:
+			return true;
+		default:
+			break;
+		}
+		break;
+	case BATTLE_BAR_STATE_SUB:
+		/* We are in the sub menu (objects/spells). */
+		gridmenu_handle(&bar->sub_grid, ev);
+		return bar->sub_grid.state == GRIDMENU_STATE_ACTIVATED;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static bool
+handle_clickdown(struct battle_bar *bar, const union event *ev)
+{
+	assert(ev->type == EVENT_CLICKDOWN);
+
+	switch (bar->state) {
+	case BATTLE_BAR_STATE_MENU:
+		/* We are selecting a main menu entry. */
+		/* TODO: implement click here too. */
+		break;
+	case BATTLE_BAR_STATE_SUB:
+		/* We are in the sub menu (objects/spells). */
+		gridmenu_handle(&bar->sub_grid, ev);
+		return bar->sub_grid.state == GRIDMENU_STATE_ACTIVATED;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+void
+battle_bar_positionate(struct battle_bar *bar, const struct battle *bt)
+{
+	assert(bar);
+
+	/* Menu in the middle of the bar (take 20%). */
+	bar->menu_frame.w = bar->w * 0.2;
+	bar->menu_frame.h = bar->h;
+	bar->menu_frame.x = bar->x + (bar->w / 2) - (bar->menu_frame.w / 2);
+	bar->menu_frame.y = window.h - bar->h;
+	bar->menu_frame.theme = bt->theme;
+
+	/* Status bar on the right. */
+	bar->status_frame.x = bar->menu_frame.x + bar->menu_frame.w;
+	bar->status_frame.y = bar->menu_frame.y;
+	bar->status_frame.w = (bar->w - bar->menu_frame.w) / 2;
+	bar->status_frame.h = bar->h;
+	bar->status_frame.theme = bt->theme;
+}
+
+bool
+battle_bar_handle(struct battle_bar *bar, const struct battle *bt, const union event *ev)
+{
+	assert(bar);
+	assert(bt);
+	assert(ev);
+
+	if (bar->state == BATTLE_BAR_STATE_NONE)
+		return false;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		return handle_keydown(bar, ev);
+	case EVENT_CLICKDOWN:
+		return handle_clickdown(bar, ev);
+	default:
+		break;
+	}
+
+	return false;
+}
+
+void
+battle_bar_reset(struct battle_bar *bar)
+{
+	gridmenu_reset(&bar->sub_grid);
+
+	bar->menu = BATTLE_BAR_MENU_ATTACK;
+	bar->state = BATTLE_BAR_STATE_NONE;
+}
+
+void
+battle_bar_open_menu(struct battle_bar *bar)
+{
+	bar->state = BATTLE_BAR_STATE_MENU;
+	bar->menu = BATTLE_BAR_MENU_ATTACK;
+}
+
+void
+battle_bar_open_spells(struct battle_bar *bar, const struct battle *bt, struct character *ch)
+{
+	/* Sub menu bar on the left, take same space as status. */
+	bar->sub_grid.x = bar->x;
+	bar->sub_grid.y = bar->menu_frame.y;
+	bar->sub_grid.w = bar->status_frame.w;
+	bar->sub_grid.h = bar->h;
+	bar->sub_grid.theme = bt->theme;
+	bar->sub_grid.nrows = 3;
+	bar->sub_grid.ncols = 4;
+
+	memset(bar->sub_grid.menu, 0, sizeof (bar->sub_grid.menu));
+
+	for (size_t i = 0; i < CHARACTER_SPELL_MAX; ++i) {
+		if (ch->spells[i])
+			bar->sub_grid.menu[i] = ch->spells[i]->name;
+	}
+
+	gridmenu_repaint(&bar->sub_grid);
+
+	bar->state = BATTLE_BAR_STATE_SUB;
+}
+
+void
+battle_bar_draw(const struct battle_bar *bar, const struct battle *bt)
+{
+	assert(bar);
+	assert(bt);
+
+	draw_status(bar, bt);
+	draw_menu(bar, bt);
+
+	/* Sub menu is only shown if state is set to it. */
+	if (bar->state == BATTLE_BAR_STATE_SUB)
+		draw_sub(bar);
+}
+
+void
+battle_bar_finish(struct battle_bar *bar)
+{
+	assert(bar);
+
+	gridmenu_finish(&bar->sub_grid);
+
+	memset(bar, 0, sizeof (*bar));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-bar.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,186 @@
+/*
+ * battle-bar.h -- battle status bar and 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_BATTLE_BAR
+#define MOLKO_BATTLE_BAR
+
+/**
+ * \file battle-bar.h
+ * \brief Battle status bar and menu.
+ *
+ * This module is a view and user input for the game battle. It is used by the
+ * \ref battle.h object.
+ */
+
+#include <stdbool.h>
+
+#include <core/plat.h>
+
+#include <ui/frame.h>
+#include <ui/gridmenu.h>
+
+struct battle;
+struct character;
+
+union event;
+
+/**
+ * \brief Main menu selection.
+ */
+enum battle_bar_menu {
+	BATTLE_BAR_MENU_ATTACK  = 0,    /*!< Attack. */
+	BATTLE_BAR_MENU_MAGIC   = 1,    /*!< Cast a spell. */
+	BATTLE_BAR_MENU_OBJECTS = 2,    /*!< Use an object*/
+	BATTLE_BAR_MENU_SPECIAL = 3     /*!< Use a special character action. */
+};
+
+/**
+ * \brief Bar state.
+ */
+enum battle_bar_state {
+	BATTLE_BAR_STATE_NONE,          /*!< No state yet. */
+	BATTLE_BAR_STATE_MENU,          /*!< Main menu selection. */
+	BATTLE_BAR_STATE_SUB            /*!< Sub grid menu opened. */
+};
+
+/**
+ * \brief Battle bar.
+ *
+ * The bar is split into three individual pieces.
+ *
+ * ```
+ * +------------+--------+------------+
+ * | Grid menu  |  Menu  |   Status   |
+ * +------------+--------+------------+
+ * ```
+ *
+ * The left grid menu is only shown when member field state is set to \ref
+ * BATTLE_BAR_STATE_SUB, it is usually opened when user select a magic or an
+ * object.
+ *
+ * The menu in the middle is the main selection and contains the enumeration
+ * \ref battle_bar_menu using top, right, bottom left edges for short selection.
+ *
+ * The last part on the right is the status pane where the team characters stats
+ * are listed.
+ *
+ * By itself, the bar never modify the battle object, it is the responsability
+ * of the battle object or the battle state to change the bar object and to
+ * track change on events. As such, the battle object is always passed as const
+ * parameter.
+ */
+struct battle_bar {
+	int x;                          /*!< (+) Position in x. */
+	int y;                          /*!< (+) Position in y. */
+	unsigned int w;                 /*!< (+) Width. */
+	unsigned int h;                 /*!< (+) Height. */
+	enum battle_bar_state state;    /*!< (-) Current state. */
+
+	/* Right status frame. */
+	struct frame status_frame;      /*!< (-) Right status frame. */
+
+	/* Main menu selection. */
+	struct frame menu_frame;        /*!< (-) Main menu frame. */
+	enum battle_bar_menu menu;      /*!< (-) Main menu selection. */
+
+	/* Sub menu selection (spells/objects). */
+	struct gridmenu sub_grid;       /*!< (-) Sub menu object. */
+};
+
+/**
+ * Repositionate every elements in the bar.
+ *
+ * Call this function at least once, when you have set the x, y, w and h fields
+ * of the bar.
+ *
+ * \pre bar != NULL
+ * \pre bt != NULL
+ * \param bar the bar to set
+ * \param bt the current battle
+ */
+void
+battle_bar_positionate(struct battle_bar *bar, const struct battle *bt);
+
+/**
+ * Handle an event.
+ *
+ * Depending on the current bar state, it will either update the main menu or
+ * the sub menu if opened. The function returns true if an element is considered
+ * activated (usually with the return key).
+ *
+ * You must not ignore the return value because the battle state should change.
+ *
+ * \pre bar != NULL
+ * \pre bt != NULL
+ * \pre ev != NULL
+ * \param bar this bar
+ * \param bt the current battle
+ * \param ev the current event
+ * \return True if an element has been activated.
+ */
+bool
+battle_bar_handle(struct battle_bar *bar,
+                  const struct battle *bt,
+                  const union event *ev) PLAT_NODISCARD;
+
+/**
+ * Reset the battle bar selection, state and grid.
+ *
+ * Call this function each time a new player is selected.
+ *
+ * \pre bar != NULL
+ * \param bar the bar to reset
+ */
+void
+battle_bar_reset(struct battle_bar *bar);
+
+void
+battle_bar_open_menu(struct battle_bar *bar);
+
+/**
+ * Open the view to select a spell by opening the grid menu.
+ *
+ * \pre bar != NULL
+ * \pre character_ok(ch)
+ * \param bar this bar
+ * \param ch the owner of spells
+ */
+void
+battle_bar_open_spells(struct battle_bar *bar, const struct battle *bt, struct character *ch);
+
+/**
+ * Draw the bar.
+ *
+ * \pre bar != NULL
+ * \pre bt != NULL
+ * \param bar the bar to draw
+ * \param bt the current battle
+ */
+void
+battle_bar_draw(const struct battle_bar *bar, const struct battle *bt);
+
+/**
+ * Close internal resources if necessary.
+ *
+ * \pre bar != NULL
+ * \param bar the bar to clear
+ */
+void
+battle_bar_finish(struct battle_bar *bar);
+
+#endif /* !MOLKO_BATTLE_BAR */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-indicator.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,134 @@
+/*
+ * battle-indicator.c -- drawable for rendering a hp/mp count usage
+ *
+ * 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 <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <core/color.h>
+#include <core/font.h>
+#include <core/panic.h>
+
+#include <ui/theme.h>
+
+#include "battle-indicator.h"
+
+#define THEME(bti)      ((bti)->theme ? (bti)->theme : theme_default())
+#define STEP            (2)
+#define DELAY           (5)
+
+static inline unsigned int
+inc(int cmp, int tgt)
+{
+	return tgt > cmp ? fmin(cmp + STEP, tgt) : fmax(cmp - STEP, tgt);
+}
+
+static inline bool
+colored(const struct battle_indicator *bti)
+{
+	/* Only check r, g, b and ignore alpha. */
+	return COLOR_R(bti->cur) == COLOR_R(bti->color) &&
+	       COLOR_G(bti->cur) == COLOR_G(bti->color) &&
+	       COLOR_B(bti->cur) == COLOR_B(bti->color);
+}
+
+void
+battle_indicator_start(struct battle_indicator *bti)
+{
+	assert(bti);
+
+	char buf[128];
+	const struct theme *theme = THEME(bti);
+
+	snprintf(buf, sizeof (buf), "%u", bti->amount);
+
+	bti->cur = 0xffffffff;
+	bti->elapsed = 0;
+	bti->alpha = 250;
+	
+	if (!font_render(theme->fonts[THEME_FONT_INTERFACE], &bti->tex[0], buf, bti->cur) ||
+	    !font_render(theme->fonts[THEME_FONT_INTERFACE], &bti->tex[1], buf, 0x000000ff))
+		panic();
+}
+
+bool
+battle_indicator_completed(const struct battle_indicator *bti)
+{
+	assert(battle_indicator_ok(bti));
+
+	return colored(bti) && bti->alpha == 0;
+}
+
+bool
+battle_indicator_ok(const struct battle_indicator *bti)
+{
+	return bti && texture_ok(&bti->tex[0]) && texture_ok(&bti->tex[1]);
+}
+
+bool
+battle_indicator_update(struct battle_indicator *bti, unsigned int ticks)
+{
+	assert(battle_indicator_ok(bti));
+
+	bti->elapsed += ticks;
+
+	if (bti->elapsed > DELAY) {
+		bti->elapsed = 0;
+
+		if (!colored(bti)) {
+			/* Update colors first. */
+			bti->cur = COLOR_HEX(
+			    inc(COLOR_R(bti->cur), COLOR_R(bti->color)),
+			    inc(COLOR_G(bti->cur), COLOR_G(bti->color)),
+			    inc(COLOR_B(bti->cur), COLOR_B(bti->color)),
+			    255
+			);
+
+			texture_set_color_mod(&bti->tex[0], bti->cur);
+		} else {
+			/* Update alpha next. */
+			bti->alpha -= 10;
+
+			texture_set_alpha_mod(&bti->tex[0], bti->alpha);
+			texture_set_alpha_mod(&bti->tex[1], bti->alpha);
+		}
+	}
+
+	return battle_indicator_completed(bti);
+}
+
+void
+battle_indicator_draw(const struct battle_indicator *bti, int x, int y)
+{
+	assert(battle_indicator_ok(bti));
+
+	texture_draw(&bti->tex[1], x + 1, y + 1);
+	texture_draw(&bti->tex[0], x, y);
+}
+
+void
+battle_indicator_finish(struct battle_indicator *bti)
+{
+	assert(bti);
+
+	texture_finish(&bti->tex[0]);
+	texture_finish(&bti->tex[1]);
+
+	memset(bti, 0, sizeof (*bti));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-indicator.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,122 @@
+/*
+ * battle-indicator.h -- drawable for rendering a hp/mp count usage
+ *
+ * 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_BATTLE_INDICATOR_H
+#define MOLKO_BATTLE_INDICATOR_H
+
+/**
+ * \file battle-indicator.h
+ * \brief Drawable for rendering a hp/mp count usage.
+ *
+ * This small module creates an animation drawable that shows a label with a
+ * smooth color/fading transition, used to display hp/mp damage.
+ */
+
+#include <stdbool.h>
+
+#include <core/texture.h>
+
+/**
+ * \brief Default color for HP indicator.
+ */
+#define BATTLE_INDICATOR_HP_COLOR (0xa5303000)
+
+/**
+ * \brief Default color for MP indicator.
+ */
+#define BATTLE_INDICATOR_MP_COLOR (0xa23e8c00)
+
+struct theme;
+
+/**
+ * \brief Structure context for indicator animation.
+ *
+ * You must zero-initialize this structure and fill the color and amount field
+ * with the color target and the amount of damage to show respectively. Then use
+ * start, update and draw function in order until it completes.
+ */
+struct battle_indicator {
+	unsigned int color;             /*!< (+) Destination color. */
+	unsigned int amount;            /*!< (+) Amount text to draw. */
+	const struct theme *theme;      /*!< (+&?) Optional theme to use. */
+	unsigned int cur;               /*!< (-) Current color target. */
+	unsigned int elapsed;           /*!< (-) Elapsed time in transition. */
+	unsigned int alpha;             /*!< (-) Current alpha. */
+	struct texture tex[2];          /*!< (*) Rendered texture. */
+};
+
+/**
+ * Start the battle indicator. You must call this function only once.
+ *
+ * \pre bti != NULL
+ * \param bti the indicator
+ */
+void
+battle_indicator_start(struct battle_indicator *bti);
+
+/**
+ * Tells if the indicator has completed.
+ *
+ * \pre battle_indicator_ok(bti)
+ * \param bti the indicator to check
+ * \return True if completed.
+ */
+bool
+battle_indicator_completed(const struct battle_indicator *bti);
+
+/**
+ * Tells if the indicator is valid.
+ *
+ * \param bti the indicator to check (may be NULL)
+ * \return True if correctly initialized.
+ */
+bool
+battle_indicator_ok(const struct battle_indicator *bti);
+
+/**
+ * Update the indicator.
+ *
+ * \pre battle_indicator_ok(bti)
+ * \param bti the indicator
+ * \param ticks the elapsed milliseconds since last frame
+ * \return True if completed.
+ */
+bool
+battle_indicator_update(struct battle_indicator *bti, unsigned int ticks);
+
+/**
+ * Draw the indicator.
+ *
+ * \pre battle_indicator_ok(bti)
+ * \param bti the indicator
+ * \param x the x coordinate
+ * \param y the y coordinate
+ */
+void
+battle_indicator_draw(const struct battle_indicator *bti, int x, int y);
+
+/**
+ * Dispose resources.
+ *
+ * \pre battle_indicator_ok(bti)
+ * \param bti the indicator
+ */
+void
+battle_indicator_finish(struct battle_indicator *bti);
+
+#endif /* !MOLKO_BATTLE_INDICATOR_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-ai.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,55 @@
+/*
+ * battle-state-ai.c -- battle state (enemy is playing)
+ *
+ * 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 "battle.h"
+#include "battle-state.h"
+#include "battle-state-ai.h"
+#include "battle-state-check.h"
+#include "character.h"
+
+static bool
+update(struct battle_state *st, struct battle *bt, unsigned int ticks)
+{
+	(void)st;
+	(void)ticks;
+
+	struct character *ch = bt->order_cur->ch;
+
+	/*
+	 * Immediately invoke the enemy exec strategy and put the battle state
+	 * to check.
+	 */
+	character_exec(ch, bt);
+	battle_state_check(bt);
+
+	return false;
+}
+
+void
+battle_state_ai(struct battle *bt)
+{
+	assert(bt);
+
+	static struct battle_state self = {
+		.update = update
+	};
+
+	battle_switch(bt, &self);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-ai.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,34 @@
+/*
+ * battle-state-ai.h -- battle state (enemy is playing)
+ *
+ * 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_BATTLE_STATE_AI_H
+#define MOLKO_BATTLE_STATE_AI_H
+
+struct battle;
+
+/**
+ * Switch to battle state AI.
+ *
+ * \pre bt != NULL
+ * \param bt the battle to change state
+ * \post bt->state->update is set
+ */
+void
+battle_state_ai(struct battle *bt);
+
+#endif /* !MOLKO_BATTLE_STATE_AI_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-check.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,168 @@
+/*
+ * battle-state-check.c -- battle state (check status)
+ *
+ * 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/alloc.h>
+#include <core/panic.h>
+#include <core/sprite.h>
+#include <core/texture.h>
+
+#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 {
+	struct character *ch;
+	int x;
+	int y;
+	struct action action;
+	unsigned int alpha;
+	unsigned int elapsed;
+};
+
+static bool
+fadeout_update(struct action *act, unsigned int ticks)
+{
+	struct fadeout *fade = act->data;
+	struct character *ch = fade->ch;
+
+	fade->elapsed += ticks;
+
+	if (fade->elapsed >= 8) {
+		fade->elapsed = 0;
+
+		if (fade->alpha == 0) {
+			return true;
+		}
+
+		fade->alpha -= 10;
+		texture_set_alpha_mod(ch->sprite->texture, fade->alpha);
+	}
+
+	return false;
+}
+
+static void
+fadeout_draw(struct action *act)
+{
+	const struct fadeout *fade = act->data;
+
+	sprite_draw(fade->ch->sprite, 0, 0, fade->x, fade->y);
+}
+
+static void
+fadeout_finish(struct action *act)
+{
+	free(act->data);
+}
+
+static void
+fadeout(struct battle *bt, struct battle_entity *et)
+{
+	struct fadeout *fade;
+
+	if (!(fade = alloc_zero(1, sizeof (*fade))))
+		panic();
+
+	fade->ch = et->ch;
+	fade->x = et->x;
+	fade->y = et->y;
+	fade->alpha = 250;
+	fade->action.data = fade;
+	fade->action.draw = fadeout_draw;
+	fade->action.update = fadeout_update;
+	fade->action.finish = fadeout_finish;
+
+	if (!action_stack_add(&bt->actions[1], &fade->action))
+		free(fade);
+
+	memset(et, 0, sizeof (*et));
+}
+
+static bool
+is_dead(const struct battle *bt)
+{
+	const struct battle_entity *et;
+
+	BATTLE_TEAM_FOREACH(bt, et) {
+		if (!character_ok(et->ch))
+			continue;
+		if (et->ch->hp > 0)
+			return false;
+	}
+
+	return true;
+}
+
+static bool
+is_won(const struct battle *bt)
+{
+	const struct battle_entity *et;
+
+	BATTLE_ENEMY_FOREACH(bt, et)
+		if (character_ok(et->ch))
+			return false;
+
+	return true;
+}
+
+static void
+clean(struct battle *bt)
+{
+	struct battle_entity *et;
+
+	BATTLE_ENEMY_FOREACH(bt, et)
+		if (character_ok(et->ch) && et->ch->hp == 0)
+			fadeout(bt, et);
+}
+
+static bool
+update(struct battle_state *st, struct battle *bt, unsigned int ticks)
+{
+	(void)st;
+	(void)ticks;
+
+	clean(bt);
+
+	if (is_dead(bt))
+		battle_state_lost(bt);
+	else if (is_won(bt))
+		battle_state_victory(bt);
+	else
+		battle_next(bt);
+
+	return false;
+}
+
+void
+battle_state_check(struct battle *bt)
+{
+	assert(bt);
+
+	static struct battle_state self = {
+		.update = update
+	};
+
+	battle_switch(bt, &self);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-check.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,34 @@
+/*
+ * battle-state-check.h -- battle state (check status)
+ *
+ * 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_BATTLE_STATE_CHECK_H
+#define MOLKO_BATTLE_STATE_CHECK_H
+
+struct battle;
+
+/**
+ * Switch to battle state check.
+ *
+ * \pre bt != NULL
+ * \param bt the battle to change state
+ * \post bt->state->update is set
+ */
+void
+battle_state_check(struct battle *bt);
+
+#endif /* !MOLKO_BATTLE_STATE_CHECK_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-closing.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,108 @@
+/*
+ * battle-state-closing.c -- battle state (closing)
+ *
+ * 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 <core/alloc.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/texture.h>
+#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;
+};
+
+
+static bool
+update(struct battle_state *st, struct battle *bt, unsigned int ticks)
+{
+	(void)bt;
+
+	struct closing *closing = st->data;
+
+	closing->elapsed += ticks;
+
+	if (closing->elapsed > 8) {
+		closing->elapsed = 0;
+
+		if (closing->alpha == 255)
+			return true;
+
+		closing->alpha += 5;
+		texture_set_alpha_mod(&closing->texture, closing->alpha);
+	}
+
+	return false;
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	(void)bt;
+
+	const struct closing *closing = st->data;
+
+	texture_draw(&closing->texture, 0, 0);
+}
+
+static void
+finish(struct battle_state *st, struct battle *bt)
+{
+	(void)bt;
+
+	struct closing *closing = st->data;
+
+	texture_finish(&closing->texture);
+	free(closing);
+}
+
+void
+battle_state_closing(struct battle *bt)
+{
+	assert(bt);
+
+	struct closing *closing;
+
+	if (!(closing = alloc_zero(1, sizeof (*closing))) ||
+	    !texture_new(&closing->texture, window.w, window.h))
+		panic();
+
+	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();
+
+	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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-closing.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,33 @@
+/*
+ * battle-state-closing.h -- battle state (closing)
+ *
+ * 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_BATTLE_STATE_CLOSING_H
+#define MOLKO_BATTLE_STATE_CLOSING_H
+
+struct battle;
+
+/**
+ * Switch to battle state closing.
+ *
+ * \pre bt != NULL
+ * \param bt the battle to change state
+ */
+void
+battle_state_closing(struct battle *bt);
+
+#endif /* !MOLKO_BATTLE_STATE_CLOSING_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-lost.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,102 @@
+/*
+ * battle-state-lost.c -- battle state (lost)
+ *
+ * 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 <core/alloc.h>
+#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"
+
+struct lost {
+	struct battle_state self;
+	struct message msg;
+};
+
+static void
+handle(struct battle_state *st, struct battle *bt, const union event *ev)
+{
+	(void)bt;
+
+	struct lost *lost = st->data;
+
+	message_handle(&lost->msg, ev);
+}
+
+static bool
+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 false;
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	(void)bt;
+
+	const struct lost *lost = st->data;
+
+	message_draw(&lost->msg);
+}
+
+void
+battle_state_lost(struct battle *bt)
+{
+	assert(bt);
+
+	struct lost *lost;
+
+	if (!(lost = alloc_zero(1, 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;
+	lost->msg.timeout = MESSAGE_TIMEOUT_DEFAULT;
+	lost->msg.delay = MESSAGE_DELAY_DEFAULT;
+
+	message_start(&lost->msg);
+	message_query(&lost->msg, NULL, &lost->msg.h);
+
+	lost->msg.w = window.w * 0.6;
+	lost->msg.y = window.h * 0.1;
+	lost->msg.x = (window.w - lost->msg.w) / 2;
+
+	bt->status = BATTLE_STATUS_LOST;
+	battle_switch(bt, &lost->self);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-lost.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,33 @@
+/*
+ * battle-state-lost.h -- battle state (lost)
+ *
+ * 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_BATTLE_STATE_LOST_H
+#define MOLKO_BATTLE_STATE_LOST_H
+
+struct battle;
+
+/**
+ * Switch to battle state lost.
+ *
+ * \pre bt != NULL
+ * \param bt the battle to change state
+ */
+void
+battle_state_lost(struct battle *bt);
+
+#endif /* !MOLKO_BATTLE_STATE_LOST_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-menu.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,70 @@
+/*
+ * battle-state-menu.h -- battle state (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 <stdbool.h>
+
+#include "battle.h"
+#include "battle-bar.h"
+#include "battle-state-menu.h"
+#include "battle-state-sub.h"
+#include "battle-state.h"
+#include "character.h"
+#include "spell.h"
+
+static void
+open_spells(struct battle *bt)
+{
+	battle_bar_open_spells(&bt->bar, bt, bt->order_cur->ch);
+	battle_state_sub(bt);
+}
+
+static void
+handle(struct battle_state *st, struct battle *bt, const union event *ev)
+{
+	(void)st;
+
+	if (battle_bar_handle(&bt->bar, bt, ev)) {
+		switch (bt->bar.menu) {
+		case BATTLE_BAR_MENU_ATTACK:
+			/* TODO: do selection for attacking. */
+			break;
+		case BATTLE_BAR_MENU_MAGIC:
+			open_spells(bt);
+			break;
+		case BATTLE_BAR_MENU_OBJECTS:
+			break;
+		case BATTLE_BAR_MENU_SPECIAL:
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+void
+battle_state_menu(struct battle *bt)
+{
+	assert(bt);
+
+	static struct battle_state self = {
+		.handle = handle,
+	};
+
+	battle_switch(bt, &self);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-menu.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,34 @@
+/*
+ * battle-state-menu.h -- battle state (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_BATTLE_STATE_MENU_H
+#define MOLKO_BATTLE_STATE_MENU_H
+
+struct battle;
+
+/**
+ * Switch to battle state menu.
+ *
+ * \pre bt != NULL
+ * \param bt the battle to change state
+ * \post bt->state->handle is set
+ */
+void
+battle_state_menu(struct battle *bt);
+
+#endif /* !MOLKO_BATTLE_STATE_MENU_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-opening.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,101 @@
+/*
+ * battle-state-opening.c -- battle state (opening)
+ *
+ * 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 <stdbool.h>
+#include <stdlib.h>
+
+#include <core/alloc.h>
+#include <core/painter.h>
+#include <core/panic.h>
+#include <core/window.h>
+
+#include "battle.h"
+#include "battle-state.h"
+#include "battle-state-opening.h"
+#include "battle-state-menu.h"
+
+#define DELAY (1000U)
+
+struct opening {
+	struct battle_state self;
+	unsigned int elapsed;
+};
+
+static bool
+update(struct battle_state *st, struct battle *bt, unsigned int ticks)
+{
+	assert(bt);
+
+	struct opening *opening = st->data;
+
+	opening->elapsed += ticks;
+
+	/*
+	 * Those function will effectively change state accordingly to the
+	 * order of playing.
+	 */
+	if (opening->elapsed >= DELAY) {
+		battle_order(bt);
+		battle_next(bt);
+	}
+
+	return false;
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	assert(bt);
+
+	const struct opening *opening = st->data;
+	const unsigned int w = window.w;
+	const unsigned int h = window.h / 2;
+	const unsigned int ch = opening->elapsed * h / DELAY;
+
+	/* Draw some bezels opening. */
+	painter_set_color(0x000000ff);
+	painter_draw_rectangle(0, 0, w, h - ch);
+	painter_draw_rectangle(0, h + ch, w, h - ch);
+}
+
+static void
+finish(struct battle_state *st, struct battle *bt)
+{
+	(void)bt;
+
+	free(st);
+}
+
+void
+battle_state_opening(struct battle *bt)
+{
+	assert(bt);
+
+	struct opening *opening;
+
+	if (!(opening = alloc_zero(1, sizeof (*opening))))
+		panic();
+
+	opening->self.data = opening;
+	opening->self.update = update;
+	opening->self.draw = draw;
+	opening->self.finish = finish;
+
+	battle_switch(bt, &opening->self);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-opening.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,37 @@
+/*
+ * battle-state-opening.h -- battle state (opening)
+ *
+ * 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_BATTLE_STATE_OPENING_H
+#define MOLKO_BATTLE_STATE_OPENING_H
+
+struct battle;
+
+/**
+ * Switch to battle state opening.
+ *
+ * \pre bt != NULL
+ * \param bt the battle to change state
+ * \post bt->state->data is not NULL
+ * \post bt->state->update is set
+ * \post bt->state->draw is set
+ * \post bt->state->finish is set
+ */
+void
+battle_state_opening(struct battle *bt);
+
+#endif /* !MOLKO_BATTLE_STATE_OPENING_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-selection.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,221 @@
+/*
+ * battle-state-selection.h -- battle state (selection)
+ *
+ * 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 <core/alloc.h>
+#include <core/event.h>
+#include <core/panic.h>
+#include <core/sprite.h>
+#include <core/util.h>
+
+#include <ui/theme.h>
+
+#include "battle.h"
+#include "battle-bar.h"
+#include "battle-state.h"
+#include "battle-state-check.h"
+#include "battle-state-menu.h"
+#include "battle-state-selection.h"
+#include "battle-state-sub.h"
+#include "character.h"
+#include "spell.h"
+
+struct select {
+	struct battle_state state;
+	enum selection type;
+	unsigned int selection;
+};
+
+static void
+select_adj_in(struct select *select, const struct battle_entity entities[], size_t entitiesz, int step)
+{
+	assert(select->selection != (unsigned int)-1);
+
+	unsigned int newselection = select->selection;
+
+	if (step < 0) {
+		while (newselection > 0) {
+			if (character_ok(entities[--newselection].ch)) {
+				select->selection = newselection;
+				break;
+			}
+		}
+	} else {
+		while (newselection < entitiesz) {
+			if (character_ok(entities[++newselection].ch)) {
+				select->selection = newselection;
+				break;
+			}
+		}
+	}
+}
+
+static void
+select_adj(struct select *select, const struct battle *bt, int step)
+{
+	switch (select->type) {
+	case SELECTION_TEAM_ONE:
+	case SELECTION_TEAM_COMBINED:
+		select_adj_in(select, bt->team, NELEM(bt->team), step);
+		break;
+	case SELECTION_ENEMY_ONE:
+	case SELECTION_ENEMY_COMBINED:
+		select_adj_in(select, bt->enemies, NELEM(bt->enemies), step);
+	default:
+		break;
+	}
+}
+
+static void
+handle_keydown(struct battle_state *st, struct battle *bt, const union event *ev)
+{
+	assert(ev->type == EVENT_KEYDOWN);
+
+	struct select *select = st->data;
+	struct character *source = bt->order_cur->ch;
+	const struct spell *sp = source->spells[bt->bar.sub_grid.selected];
+
+	switch (ev->key.key) {
+	case KEY_ESCAPE:
+		battle_state_sub(bt);
+		break;
+	case KEY_ENTER:
+		battle_cast(bt, source, sp, select->selection);
+		battle_state_check(bt);
+		break;
+	case KEY_LEFT:
+	case KEY_UP:
+		select_adj(select, bt, -1);
+		break;
+	case KEY_RIGHT:
+	case KEY_DOWN:
+		select_adj(select, bt, +1);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+draw_cursor(const struct battle *bt, const struct battle_entity *et)
+{
+	const struct theme *theme = BATTLE_THEME(bt);
+	const struct sprite *cursor = theme->sprites[THEME_SPRITE_CURSOR];
+	int x, y;
+	unsigned int lh;
+
+	if (!cursor)
+		return;
+
+	label_query(&et->name, NULL, &lh);
+
+	x = et->name.x - cursor->cellw - theme->padding;
+	y = et->name.y + (((int)(lh) - (int)(cursor->cellh)) / 2);
+
+	sprite_draw(cursor, 1, 2, x, y);
+}
+
+static void
+draw_cursors(const struct battle_state *st,
+            const struct battle *bt,
+            const struct battle_entity entities[],
+            size_t entitiesz)
+{
+	const struct select *select = st->data;
+
+	if (select->selection == (unsigned int)-1) {
+		for (size_t i = 0; i < entitiesz; ++i) {
+			const struct battle_entity *et = &entities[i];
+
+			if (character_ok(et->ch))
+				draw_cursor(bt, et);
+		}
+	} else
+		draw_cursor(bt, &entities[select->selection]);
+}
+
+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;
+	}
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	const struct select *select = st->data;
+
+	switch (select->type) {
+	case SELECTION_SELF:
+		draw_cursor(bt, bt->order_cur);
+		break;
+	case SELECTION_ENEMY_ALL:
+	case SELECTION_ENEMY_ONE:
+	case SELECTION_ENEMY_COMBINED:
+		draw_cursors(st, bt, bt->enemies, NELEM(bt->enemies));
+		break;
+	case SELECTION_TEAM_ALL:
+	case SELECTION_TEAM_ONE:
+	case SELECTION_TEAM_COMBINED:
+		draw_cursors(st, bt, bt->team, NELEM(bt->team));
+		break;
+	default:
+		break;
+	}
+
+}
+
+static void
+finish(struct battle_state *st, struct battle *bt)
+{
+	(void)bt;
+
+	free(st->data);
+}
+
+void
+battle_state_selection(struct battle *bt,
+		       enum selection type,
+		       unsigned int selection)
+{
+	assert(bt);
+
+	struct select *select;
+
+	if (!(select = alloc_zero(1, sizeof (*select))))
+		panic();
+
+	select->type = type;
+	select->selection = selection;
+	select->state.data = select;
+	select->state.handle = handle;
+	select->state.draw = draw;
+	select->state.finish = finish;
+
+	battle_switch(bt, &select->state);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-selection.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,41 @@
+/*
+ * battle-state-selection.h -- battle state (selection)
+ *
+ * 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_BATTLE_STATE_SELECTION_H
+#define MOLKO_BATTLE_STATE_SELECTION_H
+
+#include "selection.h"
+
+struct battle;
+
+/**
+ * Switch to battle state selection.
+ *
+ * \pre bt != NULL
+ * \param bt the battle to change state
+ * \post bt->state->data is not NULL
+ * \post bt->state->handle is set
+ * \post bt->state->draw is set
+ * \post bt->state->finish is set
+ */
+void
+battle_state_selection(struct battle *bt,
+                       enum selection type,
+                       unsigned int selection);
+
+#endif /* !MOLKO_BATTLE_STATE_SELECTION_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-sub.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,167 @@
+/*
+ * battle-state-sub.c -- battle state (sub)
+ *
+ * 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 <stdbool.h>
+
+#include <core/event.h>
+#include <core/sprite.h>
+#include <core/trace.h>
+
+#include <ui/theme.h>
+
+#include "battle.h"
+#include "battle-bar.h"
+#include "battle-state.h"
+#include "battle-state-menu.h"
+#include "battle-state-sub.h"
+#include "battle-state-selection.h"
+#include "character.h"
+#include "spell.h"
+
+static void
+start_select_spell(struct battle *bt)
+{
+	const struct character *ch = bt->order_cur->ch;
+	const struct spell *sp = ch->spells[bt->bar.sub_grid.selected];
+	unsigned int selection = 0;
+
+	/* Don't forget to reset the gridmenu state. */
+	gridmenu_reset(&bt->bar.sub_grid);
+
+	if (!sp || sp->mp > (unsigned int)(ch->mp))
+		return;
+
+	/*
+	 * When starting the selection state we need to initialize the first
+	 * selection depending on the spell selection type. For example, if the
+	 * spell require to select exactly one enemy, we need to find the first
+	 * one in the battle that is not NULL.
+	 */
+	switch (sp->selection) {
+	case SELECTION_SELF:
+	case SELECTION_TEAM_COMBINED:
+	case SELECTION_TEAM_ONE:
+		selection = bt->order_cur - bt->team;
+		break;
+	case SELECTION_TEAM_ALL:
+	case SELECTION_ENEMY_ALL:
+		selection = -1;
+		break;
+	case SELECTION_ENEMY_COMBINED:
+	case SELECTION_ENEMY_ONE:
+		/* Find first available. */
+		for (size_t i = 0; i < BATTLE_ENEMY_MAX; ++i) {
+			if (character_ok(bt->enemies[i].ch)) {
+				selection = i;
+				break;
+			}
+		}
+		break;
+	default:
+		selection = 0;
+		break;
+	}
+
+	battle_state_selection(bt, sp->selection, selection);
+
+	/* A cursor should be present. */
+	if (!sprite_ok(BATTLE_THEME(bt)->sprites[THEME_SPRITE_CURSOR]))
+		tracef("battle: no cursor sprite in theme");
+}
+
+static void
+start_select_object(struct battle *bt)
+{
+	(void)bt;
+}
+
+static void
+draw_spell_help(const struct battle *bt)
+{
+	const struct character *ch = bt->order_cur->ch;
+	const struct spell *sp = ch->spells[bt->bar.sub_grid.selected];
+	struct label label = {0};
+	unsigned int lw, lh;
+
+	if (!sp)
+		return;
+
+	label.flags = LABEL_FLAGS_SHADOW;
+	label.text = sp->description;
+	label_query(&label, &lw, &lh);
+	label.x = bt->bar.sub_grid.x + (bt->bar.sub_grid.w / 2) - (lw / 2);
+	label.y = bt->bar.sub_grid.y - lh - BATTLE_THEME(bt)->padding;
+	label_draw(&label);
+}
+
+static void
+handle(struct battle_state *st, struct battle *bt, const union event *ev)
+{
+	(void)st;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		switch (ev->key.key) {
+		case KEY_ESCAPE:
+			/* Escape go to the previous state. */
+			bt->bar.state = BATTLE_BAR_STATE_MENU;
+			battle_state_menu(bt);
+			return;
+		default:
+			break;
+		}
+	default:
+		break;
+	}
+
+	if (battle_bar_handle(&bt->bar, bt, ev)) {
+		switch (bt->bar.menu) {
+		case BATTLE_BAR_MENU_MAGIC:
+			start_select_spell(bt);
+			break;
+		default:
+			start_select_object(bt);
+			break;
+		}
+	}
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	(void)st;
+
+	battle_bar_draw(&bt->bar, bt);
+
+	if (bt->bar.menu == BATTLE_BAR_MENU_MAGIC)
+		draw_spell_help(bt);
+}
+
+void
+battle_state_sub(struct battle *bt)
+{
+	assert(bt);
+
+	static struct battle_state self = {
+		.handle = handle,
+		.draw = draw
+	};
+
+	battle_switch(bt, &self);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-sub.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,35 @@
+/*
+ * battle-state-sub.h -- battle state (sub)
+ *
+ * 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_BATTLE_STATE_SUB_H
+#define MOLKO_BATTLE_STATE_SUB_H
+
+struct battle;
+
+/**
+ * Switch to battle state sub.
+ *
+ * \pre bt != NULL
+ * \param bt the battle to change state
+ * \post bt->state->handle is set
+ * \post bt->state->draw is set
+ */
+void
+battle_state_sub(struct battle *bt);
+
+#endif /* !MOLKO_BATTLE_STATE_SUB_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-victory.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,103 @@
+/*
+ * battle-state-victory.c -- battle state (victory)
+ *
+ * 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 <core/alloc.h>
+#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"
+
+struct victory {
+	struct battle_state self;
+	struct message msg;
+};
+
+static void
+handle(struct battle_state *st, struct battle *bt, const union event *ev)
+{
+	(void)bt;
+
+	struct victory *vic = st->data;
+
+	message_handle(&vic->msg, ev);
+}
+
+static bool
+update(struct battle_state *st, struct battle *bt, unsigned int ticks)
+{
+	(void)bt;
+
+	struct victory *vic = st->data;
+
+	if (message_update(&vic->msg, ticks))
+		battle_state_closing(bt);
+
+	return false;
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	(void)bt;
+
+	const struct victory *vic = st->data;
+
+	message_draw(&vic->msg);
+}
+
+void
+battle_state_victory(struct battle *bt)
+{
+	assert(bt);
+
+	struct victory *vic;
+
+	if (!(vic = alloc_zero(1, 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 |
+	                 MESSAGE_FLAGS_FADEIN |
+	                 MESSAGE_FLAGS_FADEOUT;
+	vic->msg.timeout = MESSAGE_TIMEOUT_DEFAULT;
+	vic->msg.delay = MESSAGE_DELAY_DEFAULT;
+
+	message_start(&vic->msg);
+	message_query(&vic->msg, NULL, &vic->msg.h);
+
+	vic->msg.w = window.w * 0.6;
+	vic->msg.y = window.h * 0.1;
+	vic->msg.x = (window.w - vic->msg.w) / 2;
+
+	bt->status = BATTLE_STATUS_WON;
+	battle_switch(bt, &vic->self);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state-victory.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,33 @@
+/*
+ * battle-state-victory.h -- battle state (victory)
+ *
+ * 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_BATTLE_STATE_VICTORY_H
+#define MOLKO_BATTLE_STATE_VICTORY_H
+
+struct battle;
+
+/**
+ * Switch to battle state victory.
+ *
+ * \pre bt != NULL
+ * \param bt the battle to change state
+ */
+void
+battle_state_victory(struct battle *bt);
+
+#endif /* !MOLKO_BATTLE_STATE_VICTORY_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,64 @@
+/*
+ * battle-state.c -- battle 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 "battle-state.h"
+
+void
+battle_state_handle(struct battle_state *st, struct battle *bt, const union event *ev)
+{
+	assert(st);
+	assert(bt);
+	assert(ev);
+
+	if (st->handle)
+		st->handle(st, bt, ev);
+}
+
+bool
+battle_state_update(struct battle_state *st, struct battle *bt, unsigned int ticks)
+{
+	assert(st);
+	assert(bt);
+
+	if (st->update)
+		return st->update(st, bt, ticks);
+
+	return false;
+}
+
+void
+battle_state_draw(const struct battle_state *st, const struct battle *bt)
+{
+	assert(st);
+	assert(bt);
+
+	if (st->draw)
+		st->draw(st, bt);
+}
+
+void
+battle_state_finish(struct battle_state *st, struct battle *bt)
+{
+	assert(st);
+	assert(bt);
+
+	if (st->finish)
+		st->finish(st, bt);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle-state.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,137 @@
+/*
+ * battle-state.h -- battle 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.
+ */
+
+#ifndef MOLKO_BATTLE_STATE_H
+#define MOLKO_BATTLE_STATE_H
+
+/**
+ * \file battle-state.h
+ * \brief Battle abstract state.
+ *
+ * As a game battle is split into different steps, they are implemented as
+ * multiple states to facilitate transitions and have a less complicated code.
+ *
+ * Each state can handle, update and draw the game logic. The battle itself
+ * does only few things by itself like playing music, handling custom actions
+ * and drawables and dispatch the rest to the current state.
+ */
+
+#include <stdbool.h>
+
+struct battle;
+
+union event;
+
+/**
+ * \brief Battle abstract state.
+ */
+struct battle_state {
+	/**
+	 * (+&?) Optional data for the state.
+	 */
+	void *data;
+
+	/**
+	 * (+?) Handle an event.
+	 *
+	 * \pre bt != NULL
+	 * \pre ev != NULL
+	 * \param bt the current battle
+	 * \param ev the event
+	 */
+	void (*handle)(struct battle_state *st, struct battle *bt, const union event *ev);
+
+	/**
+	 * (+?) Update the battle state.
+	 *
+	 * \pre bt != NULL
+	 * \param bt the current battle
+	 * \param ticks the number of milliseconds since last frame
+	 * \return True if the battle is considered complete.
+	 */
+	bool (*update)(struct battle_state *st, struct battle *bt, unsigned int ticks);
+
+	/**
+	 * (+?) Draw the battle state.
+	 *
+	 * Note, the battle itself already draw the background and game entities
+	 * see the \ref battle_draw function for more details.
+	 *
+	 * \pre bt != NULL
+	 * \param bt the current battle
+	 */
+	void (*draw)(const struct battle_state *st, const struct battle *bt);
+
+	/**
+	 * (+?) Close internal resources if necessary.
+	 *
+	 * \pre bt != NULL
+	 * \param bt the current battle
+	 */
+	void (*finish)(struct battle_state *st, struct battle *bt);
+};
+
+/**
+ * Shortcut for st->handle (if not NULL).
+ *
+ * \pre st != NULL
+ * \pre bt != NULL
+ * \pre ev != NULL
+ * \param st this state
+ * \param bt the current battle
+ * \param ev the input event
+ */
+void
+battle_state_handle(struct battle_state *st, struct battle *bt, const union event *ev);
+
+/**
+ * Shortcut for st->update (if not NULL).
+ *
+ * \pre st != NULL
+ * \pre bt != NULL
+ * \param st this state
+ * \param bt the current battle
+ * \param ticks the number of milliseconds since last frame
+ * \return The result of st->update if not NULL or false otherwise.
+ */
+bool
+battle_state_update(struct battle_state *st, struct battle *bt, unsigned int ticks);
+
+/**
+ * Shortcut for st->draw (if not NULL).
+ *
+ * \pre st != NULL
+ * \pre bt != NULL
+ * \param st this state
+ * \param bt the current battle
+ */
+void
+battle_state_draw(const struct battle_state *st, const struct battle *bt);
+
+/**
+ * Shortcut for st->finish (if not NULL).
+ *
+ * \pre st != NULL
+ * \pre bt != NULL
+ * \param st this state
+ * \param bt the current battle
+ */
+void
+battle_state_finish(struct battle_state *st, struct battle *bt);
+
+#endif /* !MOLKO_BATTLE_STATE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,442 @@
+/*
+ * battle.c -- battles
+ *
+ * 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 <string.h>
+
+#include <core/alloc.h>
+#include <core/event.h>
+#include <core/font.h>
+#include <core/painter.h>
+#include <core/sprite.h>
+#include <core/texture.h>
+#include <core/util.h>
+#include <core/window.h>
+
+#include <ui/align.h>
+#include <ui/frame.h>
+#include <ui/label.h>
+#include <ui/theme.h>
+
+#include "battle.h"
+#include "battle-indicator.h"
+#include "battle-state.h"
+#include "battle-state-opening.h"
+#include "battle-state-menu.h"
+#include "battle-state-ai.h"
+#include "character.h"
+#include "spell.h"
+
+struct indicator {
+	struct drawable dw;
+	struct battle_indicator bti;
+};
+
+static bool
+indicator_update(struct drawable *dw, unsigned int ticks)
+{
+	struct indicator *id = dw->data;
+
+	return battle_indicator_update(&id->bti, ticks);
+}
+
+static void
+indicator_draw(struct drawable *dw)
+{
+	const struct indicator *id = dw->data;
+
+	battle_indicator_draw(&id->bti, dw->x, dw->y);
+}
+
+static void
+indicator_free(struct drawable *dw)
+{
+	struct indicator *id = dw->data;
+
+	battle_indicator_finish(&id->bti);
+	free(id);
+}
+
+static struct battle_entity *
+find(struct battle *bt, const struct character *ch)
+{
+	struct battle_entity *et;
+
+	BATTLE_TEAM_FOREACH(bt, et)
+		if (et->ch == ch)
+			return et;
+	BATTLE_ENEMY_FOREACH(bt, et)
+		if (et->ch == ch)
+			return et;
+
+	return NULL;
+}
+
+static int
+cmp_order(const void *d1, const void *d2)
+{
+	const struct battle_entity *et1 = *(const struct battle_entity **)d1;
+	const struct battle_entity *et2 = *(const struct battle_entity **)d2;
+
+	return et1->ch->agt < et2->ch->agt;
+}
+
+static bool
+is_team(const struct battle *bt, const struct battle_entity *et)
+{
+	for (size_t i = 0; i < BATTLE_TEAM_MAX; ++i)
+		if (&bt->team[i] == et)
+			return true;
+
+	return false;
+}
+
+static void
+positionate_name(struct battle_entity *et, const struct battle *bt)
+{
+	unsigned int lw;
+
+	/* Show the character name below its sprite. */
+	et->name.text = et->ch->name;
+	et->name.flags = LABEL_FLAGS_SHADOW;
+	label_query(&et->name, &lw, NULL);
+	et->name.y = et->y + et->ch->sprite->cellh + BATTLE_THEME(bt)->padding;
+	et->name.x = et->x + (et->ch->sprite->cellw / 2) - (lw / 2);
+}
+
+static void
+positionate_names(struct battle *bt)
+{
+	struct battle_entity *et;
+
+	BATTLE_TEAM_FOREACH(bt, et)
+		if (character_ok(et->ch))
+			positionate_name(et, bt);
+	BATTLE_ENEMY_FOREACH(bt, et)
+		if (character_ok(et->ch))
+			positionate_name(et, bt);
+}
+
+static void
+positionate_team(struct battle *bt)
+{
+	struct battle_entity *et;
+	unsigned int requirement = 0, nmemb = 0, spacing;
+	int x, y;
+
+	BATTLE_TEAM_FOREACH(bt, et) {
+		/* Stop in case any member of the team has been positionated. */
+		if (et->x != 0 || et->y != 0)
+			return;
+
+		if (character_ok(et->ch) && sprite_ok(et->ch->sprite)) {
+			nmemb++;
+			requirement += et->ch->sprite->cellh;
+		}
+	}
+
+	/* TODO: compute a correct x placement. */
+	spacing = (window.h - requirement) / (nmemb + 1);
+	x = window.w - 200;
+	y = spacing;
+	
+	BATTLE_TEAM_FOREACH(bt, et) {
+		if (character_ok(et->ch) && sprite_ok(et->ch->sprite)) {
+			et->x = x;
+			et->y = y;
+			y += et->ch->sprite->cellh + spacing;
+		}
+	}
+}
+
+static void
+positionate_bar(struct battle *bt)
+{
+	/* Bar is located at bottom. */
+	bt->bar.w = window.w;
+	bt->bar.h = window.h * 0.12;
+	bt->bar.x = 0;
+	bt->bar.y = window.h - bt->bar.h;
+	
+	battle_bar_positionate(&bt->bar, bt);
+}
+
+static void
+draw_entity_sprite(const struct battle_entity *et)
+{
+	struct sprite *sprite = et->ch->sprite;
+	int row;
+
+	/*
+	 * Ennemies are usually defined with a single image as such the
+	 * sprite may contain only one cell/row. Otherwise if the user
+	 * have provided a structured sprite, use appropriate row.
+	 */
+	if (sprite->nrows >= 6)
+		row = 6;
+	else
+		row = 0;
+
+	sprite_draw(et->ch->sprite, row, 0, et->x, et->y);
+}
+
+static void
+draw_entity_name(const struct battle_entity *et, const struct battle *bt)
+{
+	struct theme theme;
+	struct label label = et->name;
+
+	if (et == bt->order_cur) {
+		theme_shallow(&theme, bt->theme);
+		label.theme = &theme;
+		theme.colors[THEME_COLOR_NORMAL] = theme.colors[THEME_COLOR_SELECTED];
+	}
+
+	label_draw(&label);
+}
+
+static void
+draw_entity(const struct battle_entity *et, const struct battle *bt)
+{
+	draw_entity_sprite(et);
+	draw_entity_name(et, bt);
+}
+
+static void
+draw_entities(const struct battle *bt, struct battle_entity *entities, size_t entitiesz)
+{
+	for (size_t i = 0; i < entitiesz; ++i) {
+		if (character_ok(entities[i].ch) && sprite_ok(entities[i].ch->sprite))
+			draw_entity(&entities[i], bt);
+	}
+}
+
+void
+battle_start(struct battle *bt)
+{
+	struct battle_entity *et;
+
+	BATTLE_TEAM_FOREACH(bt, et) {
+		if (character_ok(et->ch)) {
+			character_reset(et->ch);
+			texture_set_alpha_mod(et->ch->sprite->texture, 255);
+		}
+	}
+	BATTLE_ENEMY_FOREACH(bt, et) {
+		if (character_ok(et->ch)) {
+			character_reset(et->ch);
+			texture_set_alpha_mod(et->ch->sprite->texture, 255);
+		}
+	}
+
+	positionate_team(bt);
+	positionate_bar(bt);
+	positionate_names(bt);
+
+	/* Start the state "opening" animation. */
+	battle_state_opening(bt);
+}
+
+void
+battle_switch(struct battle *bt, struct battle_state *st)
+{
+	assert(bt);
+	assert(st);
+
+	if (bt->state)
+		battle_state_finish(bt->state, bt);
+
+	bt->state = st;
+}
+
+void
+battle_order(struct battle *bt)
+{
+	struct battle_entity *et, **porder;
+
+	/* First, put a pointer for every enemy/team member. */
+	porder = &bt->order[0];
+
+	BATTLE_TEAM_FOREACH(bt, et)
+		if (character_ok(et->ch))
+			*porder++ = et;
+	BATTLE_ENEMY_FOREACH(bt, et)
+		if (character_ok(et->ch))
+			*porder++ = et;
+
+	/* Now sort. */
+	qsort(bt->order, porder - bt->order, sizeof (*porder), cmp_order);
+}
+
+void
+battle_attack(struct battle *bt, struct character *source, struct character *target)
+{
+	assert(bt);
+	assert(character_ok(source));
+	assert(character_ok(target));
+
+	/* TODO: Extreme computation of damage. */
+	unsigned int count = 10;
+
+	if (count > (unsigned int)target->hp)
+		target->hp = 0;
+	else
+		target->hp -= count;
+
+	battle_indicator_hp(bt, target, count);
+}
+
+void
+battle_cast(struct battle *bt,
+            struct character *source,
+            const struct spell *spell,
+            unsigned int selection)
+{
+	assert(bt);
+	assert(source);
+	assert(spell);
+	assert((unsigned int)source->mp >= spell->mp);
+
+	source->mp -= spell->mp;
+	spell_action(spell, bt, source, selection);
+}
+
+void
+battle_next(struct battle *bt)
+{
+	assert(bt);
+
+	battle_bar_reset(&bt->bar);
+
+	if (!bt->order_cur)
+		bt->order_cur = bt->order[bt->order_curindex = 0];
+	else {
+		/* End of turn. */
+		if (++bt->order_curindex >= BATTLE_ENTITY_MAX || !bt->order[bt->order_curindex]) {
+			battle_order(bt);
+			bt->order_cur = bt->order[bt->order_curindex = 0];
+		} else
+			bt->order_cur = bt->order[bt->order_curindex];
+	}
+
+	/* Change state depending on the kind of entity. */
+	if (is_team(bt, bt->order_cur)) {
+		battle_bar_open_menu(&bt->bar);
+		battle_state_menu(bt);
+	} else
+		battle_state_ai(bt);
+}
+
+void
+battle_indicator_hp(struct battle *bt, const struct character *target, unsigned int amount)
+{
+	assert(bt);
+	assert(target);
+
+	const struct battle_entity *et = find(bt, target);
+	struct indicator *id = alloc_zero(1, sizeof (*id));
+
+	id->bti.color = BATTLE_INDICATOR_HP_COLOR;
+	id->bti.amount = amount;
+
+	id->dw.x = et->x + target->sprite->cellw;
+	id->dw.y = et->y + target->sprite->cellh;
+	id->dw.data = id;
+	id->dw.update = indicator_update;
+	id->dw.draw = indicator_draw;
+	id->dw.finish = indicator_free;
+
+	battle_indicator_start(&id->bti);
+
+	if (!drawable_stack_add(&bt->effects, &id->dw))
+		drawable_finish(&id->dw);
+}
+
+void
+battle_handle(struct battle *bt, const union event *ev)
+{
+	assert(bt && bt->state);
+	assert(ev);
+
+	/* Handle actions. */
+	action_stack_handle(&bt->actions[0], ev);
+	action_stack_handle(&bt->actions[1], ev);
+
+	/* Handling of action is disallowed if there are pending actions. */
+	if (action_stack_completed(&bt->actions[0]))
+		battle_state_handle(bt->state, bt, ev);
+}
+
+bool
+battle_update(struct battle *bt, unsigned int ticks)
+{
+	assert(bt && bt->state);
+
+	action_stack_update(&bt->actions[0], ticks);
+	action_stack_update(&bt->actions[1], ticks);
+	drawable_stack_update(&bt->effects, ticks);
+
+	/* Game cannot update if the actions[0] stack isn't completed. */
+	if (!action_stack_completed(&bt->actions[0]))
+		return false;
+
+	return battle_state_update(bt->state, bt, ticks);
+}
+
+void
+battle_draw(struct battle *bt)
+{
+	assert(bt && bt->state);
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+
+	if (bt->background && texture_ok(bt->background))
+		texture_draw(bt->background, 0, 0);
+
+	/* Draw entities. */
+	draw_entities(bt, bt->team, NELEM(bt->team));
+	draw_entities(bt, bt->enemies, NELEM(bt->enemies));
+
+	battle_bar_draw(&bt->bar, bt);
+
+	action_stack_draw(&bt->actions[0]);
+	action_stack_draw(&bt->actions[1]);
+
+	drawable_stack_draw(&bt->effects);
+
+	return battle_state_draw(bt->state, bt);
+}
+
+void
+battle_finish(struct battle *bt)
+{
+	assert(bt);
+
+	if (bt->state)
+		battle_state_finish(bt->state, bt);
+
+	drawable_stack_finish(&bt->effects);
+
+	action_stack_finish(&bt->actions[0]);
+	action_stack_finish(&bt->actions[1]);
+
+	memset(bt, 0, sizeof (*bt));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/battle.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,264 @@
+/*
+ * battle.h -- battles
+ *
+ * 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_BATTLE_H
+#define MOLKO_BATTLE_H
+
+#include <stdbool.h>
+
+#include <core/action.h>
+#include <core/drawable.h>
+
+#include <ui/frame.h>
+#include <ui/label.h>
+#include <ui/gridmenu.h>
+
+#include "battle-bar.h"
+#include "battle-state.h"
+#include "selection.h"
+#include "spell.h"
+
+union event;
+
+struct character;
+struct sound;
+struct spell;
+struct theme;
+
+#define BATTLE_TEAM_MAX         (4)     /*!< Maximum team members. */
+#define BATTLE_ENEMY_MAX        (8)     /*!< Maximum enemies. */
+#define BATTLE_ENTITY_MAX \
+	(BATTLE_TEAM_MAX + BATTLE_ENEMY_MAX)
+
+#define BATTLE_TEAM_FOREACH(bt, iter) \
+	for (size_t i = 0; i < BATTLE_TEAM_MAX && ((iter) = &(bt)->team[i]); ++i)
+#define BATTLE_ENEMY_FOREACH(bt, iter) \
+	for (size_t i = 0; i < BATTLE_ENEMY_MAX && ((iter) = &(bt)->enemies[i]); ++i)
+
+#define BATTLE_THEME(bt) ((bt)->theme ? (bt)->theme : theme_default())
+
+/**
+ * \brief In battle entity.
+ */
+struct battle_entity {
+	struct character *ch;   /*!< (+&?) Character to use. */
+	int x;                  /*!< (+) Position on screen. */
+	int y;                  /*!< (+) Position on screen. */
+	struct label name;      /*!< (*) Where its name label is located. */
+};
+
+/**
+ * \brief Generic battle status.
+ */
+enum battle_status {
+	BATTLE_STATUS_NONE,     /*!< Battle is not even started. */
+	BATTLE_STATUS_RUNNING,  /*!< Battle is running. */
+	BATTLE_STATUS_WON,      /*!< Battle has been won. */
+	BATTLE_STATUS_LOST,     /*!< Battle has been lost. */
+};
+
+/**
+ * \brief Battle structure.
+ */
+struct battle {
+	/**
+	 * (+&?) Battle state.
+	 */
+	struct battle_state *state;
+
+	/**
+	 * (-) Battle status.
+	 *
+	 * This information is provided as final result once the battle has
+	 * completed, modifying by the user won't change the battle routines.
+	 */
+	enum battle_status status;
+
+	/**
+	 * (+) Member of team.
+	 */
+	struct battle_entity team[BATTLE_TEAM_MAX];
+
+	/**
+	 * (+) Ennemies to fight.
+	 */
+	struct battle_entity enemies[BATTLE_ENEMY_MAX];
+
+	/**
+	 * (+&?) Order of playing.
+	 */
+	struct battle_entity *order[BATTLE_ENTITY_MAX];
+
+	/**
+	 * (-&?) Current entity playing.
+	 */
+	struct battle_entity *order_cur;
+
+	/**
+	 * (-) Current entity playing index.
+	 */
+	size_t order_curindex;
+
+	/**
+	 * (+&?) An optional background.
+	 */
+	struct texture *background;
+
+	/**
+	 * (+&?) A music to play.
+	 */
+	struct sound *music;
+
+	/**
+	 * (+&?) Theme to use.
+	 */
+	struct theme *theme;
+
+	/**
+	 * (+) Stack of animations.
+	 *
+	 * The drawing animations are ran in parallel.
+	 */
+	struct drawable_stack effects;
+
+	/**
+	 * (+) Stack of actions.
+	 *
+	 * The array [0] contains actions that must complete to continue the
+	 * battle, it can be used to write some messages while blocking the
+	 * battle.
+	 *
+	 * The array [1] is a set of actions that run in "parallel" and don't
+	 * prevent the battle from continuing.
+	 */
+	struct action_stack actions[2];
+
+	/**
+	 * (-) Bottom bar.
+	 */
+	struct battle_bar bar;
+};
+
+/**
+ * Call this function before using the battle but after calling \a battle_init.
+ *
+ * If the team entities are positioned to 0, 0 they will be placed
+ * automatically.
+ *
+ * \pre bt != NULL
+ * \param bt the battle object
+ */
+void
+battle_start(struct battle *bt);
+
+/**
+ * Select next member in team.
+ *
+ * \pre bt != NULL
+ * \param bt the battle object
+ */
+void
+battle_next(struct battle *bt);
+
+/**
+ * Change battle state.
+ *
+ * \pre bt != NULL
+ * \pre st != NULL
+ * \param bt the battle object
+ * \param st the state (referenced)
+ * \warning This will immediately close the current state and call finish field
+ *          function if defined.
+ */
+void
+battle_switch(struct battle *bt, struct battle_state *st);
+
+/**
+ * Compute battle ordering.
+ *
+ * \pre bt != NULL
+ * \param bt the battle object
+ */
+void
+battle_order(struct battle *bt);
+
+/**
+ * Default function to calculate damage calculation from the source to the
+ * given target and then add an amount indicator as a drawable.
+ *
+ * \pre bt != NULL
+ * \pre source != NULL
+ * \pre target != NULL
+ * \param bt the battle object
+ * \param source 
+ */
+void
+battle_attack(struct battle *bt, struct character *source, struct character *target);
+
+/**
+ * Default function to cast a spell.
+ */
+void
+battle_cast(struct battle *bt,
+	    struct character *source,
+	    const struct spell *spell,
+	    unsigned int selection);
+
+void
+battle_indicator_hp(struct battle *bt, const struct character *target, unsigned int amount);
+
+/**
+ * Handle an event.
+ *
+ * \pre bt != NULL
+ * \pre ev != NULL
+ * \param bt the battle object
+ * \param ev the event
+ */
+void
+battle_handle(struct battle *bt, const union event *ev);
+
+/**
+ * Update the battle.
+ *
+ * \pre bt != NULL
+ * \param bt the battle object
+ * \param ticks the number of milliseconds since last frame
+ */
+bool
+battle_update(struct battle *bt, unsigned int ticks);
+
+/**
+ * Draw the battle.
+ *
+ * \pre bt != NULL
+ * \param bt the battle object
+ */
+void
+battle_draw(struct battle *bt);
+
+/**
+ * Finish and dispose resources if necessary.
+ *
+ * \pre bt != NULL
+ * \param bt the battle object
+ */
+void
+battle_finish(struct battle *bt);
+
+#endif /* MOLKO_BATTLE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/character.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,56 @@
+/*
+ * character.c -- character definition
+ *
+ * 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 "character.h"
+
+bool
+character_ok(struct character *ch)
+{
+	return ch && ch->name && ch->type && ch->reset;
+}
+
+const char *
+character_status_string(enum character_status status)
+{
+	/* We have to use a switch-case as it is a bitmask. */
+	switch (status) {
+	case CHARACTER_STATUS_POISON:
+		return "poison";
+	default:
+		return "normal";
+	}
+}
+
+void
+character_reset(struct character *ch)
+{
+	assert(ch);
+
+	ch->reset(ch);
+}
+
+void
+character_exec(struct character *ch, struct battle *bt)
+{
+	assert(character_ok(ch));
+
+	if (ch->exec)
+		ch->exec(ch, bt);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/character.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,151 @@
+/*
+ * character.h -- character definition
+ *
+ * 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_CHARACTER_H
+#define MOLKO_CHARACTER_H
+
+/**
+ * \file character.h
+ * \brief Character definition.
+ */
+
+#include <stdbool.h>
+
+struct battle;
+struct sprite;
+struct spell;
+
+/**
+ * \brief Maximum number of spells in a character.
+ */
+#define CHARACTER_SPELL_MAX (64)
+
+/**
+ * \brief Character status
+ */
+enum character_status {
+	CHARACTER_STATUS_NORMAL,                        /*!< No status. */
+	CHARACTER_STATUS_POISON         = (1 << 0)      /*!< Character is poisoned. */
+};
+
+/**
+ * \brief Character object
+ *
+ * This structure owns the current character statistics used in battle.
+ */
+struct character {
+	const char *name;               /*!< (+) Character name. */
+	const char *type;               /*!< (+) Type or class name. */
+	unsigned int level;             /*!< (+) Character level. */
+	enum character_status status;   /*!< (+) Character status. */
+	int hp;                         /*!< (+) Heal points. */
+	unsigned int hpmax;             /*!< (+) Maximum heal points. */
+	unsigned int hpbonus;           /*!< (+) User heal points bonus. */
+	int mp;                         /*!< (+) Magic points. */
+	unsigned int mpmax;             /*!< (+) Maximum magic points. */
+	unsigned int mpbonus;           /*!< (+) User magic points bonus. */
+	int atk;                        /*!< (+) Current attack points (increase fire based spells too). */
+	unsigned int atkbonus;          /*!< (+) User attack bonus. */
+	int def;                        /*!< (+) Current defense points (increase earth based spells too). */
+	unsigned int defbonus;          /*!< (+) User defense bonus. */
+	int agt;                        /*!< (+) Current agility (increase wind based spells too). */
+	unsigned int agtbonus;          /*!< (+) User agility bonus. */
+	int luck;                       /*!< (+) Current luck points (increase */
+	unsigned int luckbonus;         /*!< (+) User luck bonus. */
+	struct sprite *sprite;          /*!< (+&) Sprite to use. */
+
+	/**
+	 * (+&?) List of spells for this character.
+	 */
+	const struct spell *spells[CHARACTER_SPELL_MAX];
+
+	/**
+	 * (+) Reset statistics from this character class.
+	 *
+	 * This function must reset the following member variables according
+	 * to the class characteristics:
+	 *
+	 * - hpmax
+	 * - mpmax
+	 * - atk
+	 * - def
+	 * - agt
+	 * - luck
+	 *
+	 * \param owner this owner
+	 */
+	void (*reset)(struct character *owner);
+
+	/**
+	 * (+?) Execute an action.
+	 *
+	 * This function should be present for AI enemies in a battle, it should
+	 * be kept NULL for team players unless they have automatic actions
+	 * which in that case would skip user input.
+	 *
+	 * \param owner this owner
+	 * \param bt the battle object
+	 */
+	void (*exec)(struct character *owner, struct battle *bt);
+};
+
+/**
+ * Check if this is a valid character object.
+ *
+ * \pre ch != NULL
+ * \param ch the character object
+ */
+bool
+character_ok(struct character *ch);
+
+/**
+ * Get a string name for the given status.
+ *
+ * Since status is a bitmask you have to select only one status.
+ *
+ * \pre status must be valid
+ * \param status the status
+ * \return A const string.
+ */
+const char *
+character_status_string(enum character_status status);
+
+/**
+ * Shortcut for ch->reset.
+ *
+ * This function is usually called after an equipment change, a level change
+ * or and of a battle.
+ *
+ * \pre ch != NULL
+ * \param ch the character object
+ */
+void
+character_reset(struct character *ch);
+
+/**
+ * Shortcut for ch->exec (if not NULL)
+ *
+ * \pre character_ok(ch)
+ * \pre bt != NULL
+ * \param ch the character
+ * \param bt the battle object
+ */
+void
+character_exec(struct character *ch, struct battle *bt);
+
+#endif /* !MOLKO_CHARACTER_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/selection.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,35 @@
+/*
+ * selection.h -- kind of selection
+ *
+ * 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_SELECTION_H
+#define MOLKO_SELECTION_H
+
+/**
+ * \brief Kind of selection.
+ */
+enum selection {
+	SELECTION_SELF,           /*!< Owner only. */
+	SELECTION_TEAM_ONE,       /*!< One member of the team. */
+	SELECTION_TEAM_ALL,       /*!< All members of the team. */
+	SELECTION_TEAM_COMBINED,  /*!< One or all members of the team. */
+	SELECTION_ENEMY_ONE,      /*!< One enemy. */
+	SELECTION_ENEMY_ALL,      /*!< All enemies. */
+	SELECTION_ENEMY_COMBINED  /*!< One or all enemies. */
+};
+
+#endif /* !MOLKO_SELECTION_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/spell.c	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,41 @@
+/*
+ * spell.c -- magic spells
+ *
+ * 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 "spell.h"
+
+void
+spell_action(const struct spell *s, struct battle *bt, struct character *owner, unsigned int selection)
+{
+	assert(s && s->action);
+	assert(bt);
+	assert(owner);
+
+	s->action(bt, owner, selection);
+}
+
+void
+spell_use(struct spell *s, struct character *owner, int selection)
+{
+	assert(s && s->use);
+	assert(owner);
+
+	if (s->use)
+		s->use(owner, selection);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/librpg/rpg/spell.h	Sat Nov 07 16:00:39 2020 +0100
@@ -0,0 +1,108 @@
+/*
+ * spell.h -- magic spells
+ *
+ * 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_SPELL_H
+#define MOLKO_SPELL_H
+
+/**
+ * \file spell.h
+ * \brief Magic spells.
+ */
+
+#include "selection.h"
+
+struct character;
+struct battle;
+
+/**
+ * \brief Kind of spell.
+ */
+enum spell_type {
+	SPELL_TYPE_NEUTRAL,     /*!< No type. */
+	SPELL_TYPE_FIRE,        /*!< Fire (affected by attack). */
+	SPELL_TYPE_WIND,        /*!< Wind (affected by agility). */
+	SPELL_TYPE_WATER,       /*!< Water (affected by luck). */
+	SPELL_TYPE_EARTH,       /*!< Earth (affected by defense). */
+	SPELL_TYPE_CHAOS,       /*!< Chaotic. */
+	SPELL_TYPE_HOLY,        /*!< Holy. */
+	SPELL_TYPE_TIME         /*!< Chrono. */
+};
+
+/**
+ * \brief Spell structure.
+ *
+ * A spell is a magical object owned by a character and can be used in a battle
+ * and/or outside of a battle. It costs a certain amount of magic points and is
+ * typed into a category (earth, fire, etc, …).
+ *
+ * A spell can select one character or all.
+ */
+struct spell {
+	const char *name;               /*!< (+&) Spell name. */
+	const char *description;        /*!< (+&) Long description. */
+	unsigned int mp;                /*!< (+) Number of MP required. */
+	enum spell_type type;           /*!< (+) Kind of spell. */
+	enum selection selection;       /*!< (+) Kind of selection. */
+
+	/**
+	 * (+) Execute the spell in a battle.
+	 *
+	 * \param bt the current battle
+	 * \param owner the spell owner
+	 * \param selection the selection (-1 == all)
+	 */
+	void (*action)(struct battle *bt, struct character *owner, unsigned int selection);
+
+	/**
+	 * (+) Use the spell outside of a battle.
+	 *
+	 * This function is optional.
+	 *
+	 * \param owner the spell owner
+	 * \param selection the selection flags
+	 */
+	void (*use)(struct character *owner, int selection);
+};
+
+/**
+ * Cast this spell within a battle.
+ *
+ * \pre s != NULL && s->action
+ * \pre bt != NULL
+ * \pre owner != NULL
+ * \param s the spell
+ * \param bt the battle
+ * \param owner the owner
+ * \param selection the selection (index or -1 for all)
+ */
+void
+spell_action(const struct spell *s, struct battle *bt, struct character *owner, unsigned int selection);
+
+/**
+ * Use this spell immediately.
+ *
+ * \pre s != NULL && s->use
+ * \pre owner != NULL
+ * \param s the spell
+ * \param owner the owner
+ * \param selection the selection flags
+ */
+void
+spell_use(struct spell *s, struct character *owner, int selection);
+
+#endif /* !MOLKO_SPELL_H */
--- a/libui/ui/gridmenu.c	Sat Nov 07 15:40:34 2020 +0100
+++ b/libui/ui/gridmenu.c	Sat Nov 07 16:00:39 2020 +0100
@@ -194,7 +194,7 @@
 			menu->selected -= 1;
 		break;
 	case KEY_ENTER:
-		menu->state = GRIDMENU_STATE_SELECTED;
+		menu->state = GRIDMENU_STATE_ACTIVATED;
 		break;
 	default:
 		break;
@@ -224,8 +224,11 @@
 		}
 	}
 
-	if (save != menu->selected)
+	/* A click immediately active the widget. */
+	if (save != menu->selected) {
 		gridmenu_repaint(menu);
+		menu->state = GRIDMENU_STATE_ACTIVATED;
+	}
 }
 
 void
--- a/libui/ui/gridmenu.h	Sat Nov 07 15:40:34 2020 +0100
+++ b/libui/ui/gridmenu.h	Sat Nov 07 16:00:39 2020 +0100
@@ -44,7 +44,7 @@
  */
 enum gridmenu_state {
 	GRIDMENU_STATE_NONE,            /*!< No state yet. */
-	GRIDMENU_STATE_SELECTED         /*!< An entry has been selected. */
+	GRIDMENU_STATE_ACTIVATED        /*!< An entry has been selected. */
 };
 
 /**
--- a/libui/ui/theme.c	Sat Nov 07 15:40:34 2020 +0100
+++ b/libui/ui/theme.c	Sat Nov 07 16:00:39 2020 +0100
@@ -66,7 +66,7 @@
 	struct font font;
 } default_fonts[] = {
 	FONT(fonts_opensans_light, 12, THEME_FONT_DEBUG),
-	FONT(fonts_opensans_regular, 12, THEME_FONT_INTERFACE)
+	FONT(fonts_opensans_regular, 14, THEME_FONT_INTERFACE)
 };
 
 bool
--- a/libui/ui/theme.h	Sat Nov 07 15:40:34 2020 +0100
+++ b/libui/ui/theme.h	Sat Nov 07 16:00:39 2020 +0100
@@ -27,11 +27,12 @@
 
 #include <stdbool.h>
 
+struct button;
 struct checkbox;
-struct button;
 struct font;
 struct frame;
 struct label;
+struct sprite;
 
 /**
  * \brief Font component.
@@ -54,6 +55,14 @@
 };
 
 /**
+ * \brief Special sprites.
+ */
+enum theme_sprite {
+	THEME_SPRITE_CURSOR,    /*!< Sprite cursor. */
+	THEME_SPRITE_LAST,
+};
+
+/**
  * \brief Abstract theme structure.
  */
 struct theme {
@@ -63,6 +72,11 @@
 	struct font *fonts[THEME_FONT_LAST];
 
 	/**
+	 * (+&?) Special sprites catalog.
+	 */
+	const struct sprite *sprites[THEME_SPRITE_LAST];
+
+	/**
 	 * (+) User interface colors.
 	 */
 	unsigned long colors[THEME_COLOR_LAST];