changeset 398:14ce7c4871e3

rpg: overhaul of battle bar
author David Demelier <markand@malikania.fr>
date Sun, 27 Feb 2022 10:08:51 +0100
parents 73eabfd50410
children 77f843de956c
files examples/example-battle/main.c examples/example-battle/spell-fire.c src/libmlk-rpg/CMakeLists.txt src/libmlk-rpg/rpg/battle-bar-default.c src/libmlk-rpg/rpg/battle-bar-default.h src/libmlk-rpg/rpg/battle-entity.c src/libmlk-rpg/rpg/battle-state-ai.c src/libmlk-rpg/rpg/battle-state-ai.h src/libmlk-rpg/rpg/battle-state-attacking.c src/libmlk-rpg/rpg/battle-state-attacking.h src/libmlk-rpg/rpg/battle-state-check.c src/libmlk-rpg/rpg/battle-state-check.h src/libmlk-rpg/rpg/battle-state-closing.c src/libmlk-rpg/rpg/battle-state-closing.h src/libmlk-rpg/rpg/battle-state-lost.c src/libmlk-rpg/rpg/battle-state-lost.h src/libmlk-rpg/rpg/battle-state-menu.c src/libmlk-rpg/rpg/battle-state-menu.h src/libmlk-rpg/rpg/battle-state-opening.c src/libmlk-rpg/rpg/battle-state-opening.h src/libmlk-rpg/rpg/battle-state-rendering.c src/libmlk-rpg/rpg/battle-state-rendering.h src/libmlk-rpg/rpg/battle-state-selection.c src/libmlk-rpg/rpg/battle-state-victory.c src/libmlk-rpg/rpg/battle-state-victory.h src/libmlk-rpg/rpg/battle.c src/libmlk-rpg/rpg/battle.h src/libmlk-rpg/rpg/selection.c src/libmlk-ui/ui/gridmenu.c src/libmlk-ui/ui/gridmenu.h
diffstat 30 files changed, 823 insertions(+), 493 deletions(-) [+]
line wrap: on
line diff
--- a/examples/example-battle/main.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/examples/example-battle/main.c	Sun Feb 27 10:08:51 2022 +0100
@@ -40,9 +40,10 @@
 #include <ui/theme.h>
 #include <ui/ui.h>
 
+#include <rpg/battle-bar-default.h>
+#include <rpg/battle-bar.h>
+#include <rpg/battle.h>
 #include <rpg/character.h>
-#include <rpg/battle-bar-default.h>
-#include <rpg/battle.h>
 #include <rpg/rpg.h>
 #include <rpg/spell.h>
 
@@ -64,6 +65,8 @@
 	ch->luck = 50;
 }
 
+#if 0
+
 static void
 haunted_wood_reset(struct character *ch)
 {
@@ -75,10 +78,12 @@
 	ch->luck = 100;
 }
 
+#endif
+
 static void
 black_cat_reset(struct character *ch)
 {
-	ch->hpmax = ch->hp = 126;
+	ch->hpmax = ch->hp = 120;
 	ch->mpmax = ch->mp = 38;
 	ch->atk = 22;
 	ch->def = 19;
@@ -86,24 +91,7 @@
 	ch->luck = 14;
 }
 
-static struct character team[] = {
-	{
-		.name = "Molko",
-		.level = 1,
-		.hp = 120,
-		.mp = 50,
-		.reset = adventurer_reset,
-		.sprites = {
-			[CHARACTER_SPRITE_NORMAL] = &registry_sprites[REGISTRY_TEXTURE_JOHN_WALK],
-			[CHARACTER_SPRITE_SWORD] = &registry_sprites[REGISTRY_TEXTURE_JOHN_SWORD],
-		},
-		.spells = {
-			&spell_fire
-		}
-	},
-};
-
-static struct state *states[2];
+#if 0
 
 static void
 haunted_wood_strat(struct character *ch, struct battle *bt)
@@ -111,18 +99,22 @@
 	(void)ch;
 
 	/* TODO: Select randomly. */
-	battle_attack(bt, bt->order_cur->ch, bt->team[0].ch);
+	battle_attack(bt, battle_current(bt)->ch, bt->team[0]->ch);
 }
 
+#endif
+
 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);
+	battle_attack(bt, battle_current(bt)->ch, bt->team[0]->ch);
 }
 
+#if 0
+
 static struct character haunted_wood = {
 	.name = "Haunted Wood",
 	.level = 30,
@@ -133,15 +125,7 @@
 	.exec = haunted_wood_strat
 };
 
-static struct character black_cat = {
-	.name = "Black Cat",
-	.level = 6,
-	.reset = black_cat_reset,
-	.sprites = {
-		[CHARACTER_SPRITE_NORMAL] = &registry_sprites[REGISTRY_TEXTURE_BLACK_CAT],
-	},
-	.exec = black_cat_strat
-};
+#endif
 
 static void
 init(void)
@@ -157,39 +141,94 @@
 	theme_default()->sprites[THEME_SPRITE_CURSOR] = &registry_sprites[REGISTRY_TEXTURE_CURSOR];
 }
 
+static struct state *states[2];
 static struct state fight_state;
 
+static struct {
+	struct character ch;
+	struct battle_entity entity;
+} entities[] = {
+	/* == Enemies == */
+	{
+		.ch = {
+			.name = "Black Cat",
+			.level = 6,
+			.reset = black_cat_reset,
+			.sprites = {
+				[CHARACTER_SPRITE_NORMAL] = &registry_sprites[REGISTRY_TEXTURE_BLACK_CAT],
+			},
+			.exec = black_cat_strat
+		},
+		.entity = {
+			.ch = &entities[0].ch
+		}
+	},
+
+	/* == Team == */
+	{
+		.ch = {
+			.name = "Molko",
+			.level = 1,
+			.hp = 120,
+			.mp = 50,
+			.reset = adventurer_reset,
+			.sprites = {
+				[CHARACTER_SPRITE_NORMAL] = &registry_sprites[REGISTRY_TEXTURE_JOHN_WALK],
+				[CHARACTER_SPRITE_SWORD] = &registry_sprites[REGISTRY_TEXTURE_JOHN_SWORD],
+			},
+			.spells = {
+				&spell_fire
+			}
+		},
+		.entity = {
+			.ch = &entities[1].ch
+		}
+	},
+};
+
+static struct drawable *drawables[16];
+static struct drawable_stack drawable_stack;
+
+static struct action *actions[16];
+static struct action_stack action_stack;
+
+static struct battle_entity *entities_enemies[1];
+static struct battle_entity *entities_team[1];
+static struct battle bt;
+static struct battle_bar_default default_bar;
+static struct battle_bar bar;
+
 static void
 prepare_to_fight(void)
 {
-	struct battle *bt = alloc_new0(sizeof (*bt));
-
-//	bt->enemies[0].ch = &haunted_wood;
-	bt->team[0].ch = &team[0];
-	//bt->team[1].ch = &team[1];
+	action_stack_init(&action_stack, actions, UTIL_SIZE(actions));
+	drawable_stack_init(&drawable_stack, drawables, UTIL_SIZE(drawables));
 
-	/* Positionate the single ennemy to the left. */
-	align(ALIGN_LEFT,
-	    &bt->enemies[0].x, &bt->enemies[0].y,
-	    haunted_wood.sprites[CHARACTER_SPRITE_NORMAL]->cellw,
-	    haunted_wood.sprites[CHARACTER_SPRITE_NORMAL]->cellh,
-	    0, 0, window.w, window.h);
+	battle_init(&bt);
+	battle_bar_default_init(&default_bar);
+	battle_bar_default(&default_bar, &bar);
+
+	bt.team = entities_team;
+	bt.teamsz = UTIL_SIZE(entities_team);
+
+	bt.enemies = entities_enemies;
+	bt.enemiesz = UTIL_SIZE(entities_enemies);
 
 	/* Black cat is near the previous monster. */
-	bt->enemies[1].ch = &black_cat;
-	bt->enemies[1].x = 500;
-	bt->enemies[1].y = 100;
+	entities_team[0] = &entities[1].entity;
+	entities_enemies[0] = &entities[0].entity;
+	entities_enemies[0]->x = 500;
+	entities_enemies[0]->y = 100;
 
-	bt->background = &registry_images[REGISTRY_IMAGE_BATTLE_BACKGROUND];
+	bt.background = &registry_images[REGISTRY_IMAGE_BATTLE_BACKGROUND];
+	bt.bar = &bar;
+	bt.actions = &action_stack;
+	bt.effects = &drawable_stack;
 
-	battle_bar_default(bt);
-	battle_start(bt);
-
-	fight_state.data = bt;
+	battle_start(&bt);
 	game_push(&fight_state);
 }
 
-
 static void
 empty_handle(struct state *st, const union event *ev)
 {
@@ -234,32 +273,39 @@
 static void
 fight_handle(struct state *st, const union event *ev)
 {
-	battle_handle(st->data, ev);
+	(void)st;
+
+	battle_handle(&bt, ev);
 }
 
 static void
 fight_update(struct state *st, unsigned int ticks)
 {
-	struct battle *bt = st->data;
+	(void)st;
 
-	if (battle_update(bt, ticks))
+	if (battle_update(&bt, ticks))
 		game_pop();
 }
 
 static void
 fight_draw(struct state *st)
 {
+	(void)st;
+
 	painter_set_color(0x000000ff);
 	painter_clear();
-	battle_draw(st->data);
+	battle_draw(&bt);
 	painter_present();
 }
 
 static void
 fight_finish(struct state *st)
 {
-	battle_finish(st->data);
-	free(st->data);
+	(void)st;
+
+	battle_bar_finish(&bar, &bt);
+	battle_bar_default_finish(&default_bar);
+	battle_finish(&bt);
 }
 
 static struct state fight_state = {
--- a/examples/example-battle/spell-fire.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/examples/example-battle/spell-fire.c	Sun Feb 27 10:08:51 2022 +0100
@@ -20,10 +20,13 @@
 
 #include <core/action.h>
 #include <core/animation.h>
+#include <core/drawable.h>
 #include <core/alloc.h>
 
 #include <ui/align.h>
 
+#include <rpg/battle-state-check.h>
+#include <rpg/battle-state-rendering.h>
 #include <rpg/battle.h>
 #include <rpg/character.h>
 #include <rpg/spell.h>
@@ -31,42 +34,42 @@
 #include "registry.h"
 #include "spell-fire.h"
 
-struct data {
+struct self {
 	struct battle *battle;
 	struct character *target;
 	struct animation animation;
-	struct action action;
+	struct drawable drawable;
 	unsigned int selection;
 };
 
 static int
-update(struct action *act, unsigned int ticks)
+update(struct drawable *dw, unsigned int ticks)
 {
-	struct data *data = act->data;
+	struct self *self = dw->data;
 
-	return animation_update(&data->animation, ticks);
+	return animation_update(&self->animation, ticks);
 }
 
 static void
-draw(struct action *act)
+draw(struct drawable *dw)
 {
-	const struct data *data = act->data;
-	const struct battle_entity *et = &data->battle->enemies[data->selection];
+	const struct self *self = dw->data;
+	const struct battle_entity *et = self->battle->enemies[self->selection];
 	const struct sprite *sprite = et->ch->sprites[CHARACTER_SPRITE_NORMAL];
 	int x, y;
 
 	align(ALIGN_CENTER,
-	    &x, &y, data->animation.sprite->cellw, data->animation.sprite->cellh,
+	    &x, &y, self->animation.sprite->cellw, self->animation.sprite->cellh,
 	    et->x, et->y, sprite->cellw, sprite->cellh);
 
-	animation_draw(&data->animation, x, y);
+	animation_draw(&self->animation, x, y);
 }
 
 static void
-end(struct action *act)
+end(struct drawable *dw)
 {
-	struct data *data = act->data;
-	struct character *ch = data->battle->enemies[data->selection].ch;
+	struct self *self = dw->data;
+	struct character *ch = self->battle->enemies[self->selection]->ch;
 
 	/* TODO: compute damage. */
 	const unsigned int damage = 100;
@@ -76,13 +79,14 @@
 	else
 		ch->hp -= damage;
 
-	battle_indicator_hp(data->battle, data->battle->enemies[data->selection].ch, 100);
+	battle_indicator_hp(self->battle, self->battle->enemies[self->selection]->ch, 100);
+	battle_state_check(self->battle);
 }
 
 static void
-finish(struct action *act)
+finish(struct drawable *dw)
 {
-	free(act->data);
+	free(dw->data);
 }
 
 static void
@@ -96,25 +100,24 @@
 static void
 fire_action(struct battle *bt, struct character *owner, const struct selection *slt)
 {
-	struct data *data;
+	struct self *self;
 
 	(void)owner;
 
-	data = alloc_new0(sizeof (*data));
-	data->selection = slt->index_character;
-	data->battle = bt;
-	data->action.data = data;
-	data->action.update = update;
-	data->action.draw = draw;
-	data->action.finish = finish;
-	data->action.end = end;
+	self = alloc_new0(sizeof (*self));
+	self->selection = slt->index_character;
+	self->battle = bt;
+	self->drawable.data = self;
+	self->drawable.update = update;
+	self->drawable.draw = draw;
+	self->drawable.finish = finish;
+	self->drawable.end = end;
 
-	animation_init(&data->animation, &registry_sprites[REGISTRY_TEXTURE_EXPLOSION], 12);
-	animation_start(&data->animation);
+	animation_init(&self->animation, &registry_sprites[REGISTRY_TEXTURE_EXPLOSION], 12);
+	animation_start(&self->animation);
 
 	sound_play(&registry_sounds[REGISTRY_SOUND_FIRE]);
-
-	action_stack_add(&bt->actions[0], &data->action);
+	battle_state_rendering(bt, &self->drawable);
 }
 
 const struct spell spell_fire = {
--- a/src/libmlk-rpg/CMakeLists.txt	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/CMakeLists.txt	Sun Feb 27 10:08:51 2022 +0100
@@ -56,6 +56,8 @@
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-menu.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-opening.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-opening.h
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-rendering.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-rendering.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-selection.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-selection.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-victory.c
--- a/src/libmlk-rpg/rpg/battle-bar-default.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-bar-default.c	Sun Feb 27 10:08:51 2022 +0100
@@ -43,11 +43,29 @@
 #include "rpg_p.h"
 #include "spell.h"
 
-struct self {
-	struct battle_bar_default data;
-	struct battle_bar bar;
+#define THEME(bar) ((bar)->theme ? (bar)->theme : theme_default())
+
+struct geo {
+	int x, y;
+	unsigned int w, h;
 };
 
+static inline void
+dimensions(struct geo geo[2], const struct battle_bar_default *bar)
+{
+	/* 0 == main menu */
+	geo[0].w = bar->w * 0.2;
+	geo[0].h = bar->h;
+	geo[0].x = bar->x + (bar->w / 2) - (geo[0].w / 2);
+	geo[0].y = window.h - bar->h;
+
+	/* 1 == status frame */
+	geo[1].x = geo[0].x + geo[0].w;
+	geo[1].y = geo[0].y;
+	geo[1].w = (bar->w - geo[0].w) / 2;
+	geo[1].h = bar->h;
+}
+
 /*
  * The following validate_* functions are called when the user has validated a
  * selection depending on the current menu (e.g. Magic, Items).
@@ -63,18 +81,18 @@
 	struct character *target;
 
 	if (sel->index_side == 0)
-		target = bt->enemies[sel->index_character].ch;
+		target = bt->enemies[sel->index_character]->ch;
 	else
-		target = bt->team[sel->index_character].ch;
+		target = bt->team[sel->index_character]->ch;
 
-	battle_attack(bt, bt->order_cur->ch, target);
+	battle_attack(bt, battle_current(bt)->ch, target);
 }
 
 static void
 validate_magic(struct battle_bar_default *bar, struct battle *bt, const struct selection *sel)
 {
-	struct character *source = bt->order_cur->ch;
-	const struct spell *spell = source->spells[bar->sub_grid.selected];
+	struct character *source = battle_current(bt)->ch;
+	const struct spell *spell = source->spells[bar->grid.selected];
 
 	battle_cast(bt, source, spell, sel);
 }
@@ -85,15 +103,15 @@
 	struct inventory_slot *slot;
 	struct battle_entity *source, *target;
 
-	if (bar->sub_grid.selected >= INVENTORY_ITEM_MAX)
+	if (bar->grid.selected >= INVENTORY_ITEM_MAX)
 		return;
-	if (!(slot = &bt->inventory->items[bar->sub_grid.selected]))
+	if (!(slot = &bt->inventory->items[bar->grid.selected]))
 		return;
 
-	source = bt->order_cur;
+	source = battle_current(bt);
 	target = sel->index_side == 0
-		? &bt->enemies[sel->index_character]
-		: &bt->team[sel->index_character];
+		? bt->enemies[sel->index_character]
+		: bt->team[sel->index_character];
 
 	/* TODO: battle_use? */
 	battle_state_item(bt, source, target, slot);
@@ -106,7 +124,7 @@
  */
 
 static void
-switch_selection_attack(struct battle *bt)
+switch_selection_attack(struct battle_bar_default *bar, struct battle *bt)
 {
 	struct selection sel = {
 		.allowed_kinds = SELECTION_KIND_ONE,
@@ -114,7 +132,8 @@
 		.index_side = 0
 	};
 
-	/* Just make sure */
+	/* Disable handling anymore. */
+	bar->state = BATTLE_BAR_DEFAULT_STATE_NONE;
 
 	selection_first(&sel, bt);
 	battle_state_selection(bt, &sel);
@@ -123,13 +142,13 @@
 static void
 switch_selection_spell(struct battle_bar_default *bar, struct battle *bt)
 {
-	const struct character *ch = bt->order_cur->ch;
-	const struct spell *sp = ch->spells[bar->sub_grid.selected];
+	const struct character *ch = battle_current(bt)->ch;
+	const struct spell *sp = ch->spells[bar->grid.selected];
 	struct selection sel = {0};
 
-	if (bar->sub_grid.selected > CHARACTER_SPELL_MAX)
+	if (bar->grid.selected > CHARACTER_SPELL_MAX)
 		return;
-	if (!(sp = ch->spells[bar->sub_grid.selected]) || sp->mp > (unsigned int)(ch->mp))
+	if (!(sp = ch->spells[bar->grid.selected]) || sp->mp > (unsigned int)ch->mp)
 		return;
 
 	/* Use the spell selection algorithm to fill default values. */
@@ -148,7 +167,7 @@
 		.allowed_kinds = SELECTION_KIND_ONE,
 		.allowed_sides = SELECTION_SIDE_TEAM | SELECTION_SIDE_ENEMY,
 		.index_side = 1,
-		.index_character = bt->order_curindex
+		.index_character = battle_index(bt)
 	};
 
 	battle_state_selection(bt, &slt);
@@ -160,7 +179,7 @@
  */
 
 static void
-draw_help(const struct battle_bar_default *bar, const struct battle *bt, const char *what)
+draw_help(const struct battle_bar_default *bar, const char *what)
 {
 	struct label label = {0};
 	unsigned int lw = 0, lh = 0;
@@ -168,23 +187,23 @@
 	label.flags = LABEL_FLAGS_SHADOW;
 	label.text = what;
 	label_query(&label, &lw, &lh);
-	label.x = bar->sub_grid.x + (bar->sub_grid.w / 2) - (lw / 2);
-	label.y = bar->sub_grid.y - lh - BATTLE_THEME(bt)->padding;
+	label.x = bar->grid.x + (bar->grid.w / 2) - (lw / 2);
+	label.y = bar->grid.y - lh - THEME(bar)->padding;
 	label_draw(&label);
 }
 
 static void
 draw_spell_help(const struct battle_bar_default *bar, const struct battle *bt)
 {
-	const struct character *ch = bt->order_cur->ch;
+	const struct character *ch = battle_current(bt)->ch;
 	const struct spell *sp;
 
-	if (bar->sub_grid.selected >= CHARACTER_SPELL_MAX)
+	if (bar->grid.selected >= CHARACTER_SPELL_MAX)
 		return;
-	if (!(sp = ch->spells[bar->sub_grid.selected]))
+	if (!(sp = ch->spells[bar->grid.selected]))
 		return;
 
-	draw_help(bar, bt, sp->description);
+	draw_help(bar, sp->description);
 }
 
 static void
@@ -192,26 +211,25 @@
 {
 	const struct inventory_slot *slot;
 
-	if (bar->sub_grid.selected >= INVENTORY_ITEM_MAX)
+	if (bar->grid.selected >= INVENTORY_ITEM_MAX)
 		return;
 
-	slot = &bt->inventory->items[bar->sub_grid.selected];
+	slot = &bt->inventory->items[bar->grid.selected];
 
 	if (!slot->item)
 		return;
 
-	draw_help(bar, bt, slot->item->description);
+	draw_help(bar, slot->item->description);
 }
 
 static void
-draw_status_character_stats(const struct battle *bt,
+draw_status_character_stats(const struct battle_bar_default *bar,
                             const struct character *ch,
                             int x,
                             int y,
-                            unsigned int w,
                             unsigned int h)
 {
-	struct theme *theme = BATTLE_THEME(bt);
+	const struct theme *theme = THEME(bar);
 	struct label label;
 	unsigned int spacing, lw, lh;
 	char line[64];
@@ -225,17 +243,24 @@
 	label.text = line;
 	label.flags = LABEL_FLAGS_SHADOW;
 
+	/* Name. */
+	snprintf(line, sizeof (line), "%s", ch->name);
+	label_query(&label, &lw, &lh);
+	label.x = x + theme->padding;
+	label.y = y + spacing;
+	label_draw(&label);
+
 	/* 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.x = x + theme->padding;
+	label.y = label.y + lh + 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.x = x + theme->padding;
 	label.y = label.y + lh + spacing;
 	label_draw(&label);
 
@@ -247,43 +272,52 @@
 draw_status_character(const struct battle_bar_default *bar,
                       const struct battle *bt,
                       const struct character *ch,
+                      const struct geo *geo,
                       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;
+	w = geo->w / bt->teamsz;
+	h = geo->h;
+	x = geo->x + (index * w);
+	y = geo->y;
 
-	draw_status_character_stats(bt, ch, x, y, w, h);
+	draw_status_character_stats(bar, ch, x, y, h);
 }
 
 static void
-draw_status_characters(const struct battle_bar_default *bar, const struct battle *bt)
+draw_status_characters(const struct battle_bar_default *bar,
+                       const struct battle *bt,
+                       const struct geo *geo)
 {
 	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);
+			draw_status_character(bar, bt, et->ch, geo, index);
 
 		++index;
 	}
 }
 
 static void
-draw_status(const struct battle_bar_default *bar, const struct battle *bt)
+draw_status(const struct battle_bar_default *bar, const struct battle *bt, const struct geo *geo)
 {
-	frame_draw(&bar->status_frame);
-	draw_status_characters(bar, bt);
+	frame_draw(&(const struct frame) {
+		.x = geo->x,
+		.y = geo->y,
+		.w = geo->w,
+		.h = geo->h
+	});
+
+	draw_status_characters(bar, bt, geo);
 }
 
 static void
-draw_menu(const struct battle_bar_default *bar, const struct battle *bt)
+draw_menu(const struct battle_bar_default *bar, const struct geo *geo)
 {
 	struct {
 		unsigned int w, h;
@@ -320,32 +354,34 @@
 		}
 	};
 
-	struct theme theme;
+	const struct theme *theme = THEME(bar);
 	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;
+	bx = geo->x + theme->padding;
+	by = geo->y + theme->padding;
+	bw = geo->w - theme->padding * 2;
+	bh = geo->h - theme->padding * 2;
 
 	/* Draw menu frame. */
-	frame_draw(&bar->menu_frame);
+	frame_draw(&(const struct frame) {
+		.x = geo->x,
+		.y = geo->y,
+		.w = geo->w,
+		.h = geo->h
+	});
 
 	for (size_t i = 0; i < UTIL_SIZE(buttons); ++i) {
-		buttons[i].label.theme = &theme;
+		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_DEFAULT_STATE_NONE*/)
-			theme.colors[THEME_COLOR_NORMAL] = BATTLE_THEME(bt)->colors[THEME_COLOR_SELECTED];
+		if ((size_t)bar->menu == i)
+			buttons[i].label.flags |=  LABEL_FLAGS_SELECTED;
 		else
-			theme.colors[THEME_COLOR_NORMAL] = BATTLE_THEME(bt)->colors[THEME_COLOR_NORMAL];
+			buttons[i].label.flags &= ~LABEL_FLAGS_SELECTED;
 
 		align(buttons[i].align,
 		    &buttons[i].label.x, &buttons[i].label.y, buttons[i].w, buttons[i].h,
@@ -383,13 +419,13 @@
 		 */
 		switch (bar->menu) {
 		case BATTLE_BAR_DEFAULT_MENU_ATTACK:
-			switch_selection_attack(bt);
+			switch_selection_attack(bar, bt);
 			break;
 		case BATTLE_BAR_DEFAULT_MENU_ITEM:
 			battle_bar_default_open_item(bar, bt);
 			break;
 		case BATTLE_BAR_DEFAULT_MENU_MAGIC:
-			battle_bar_default_open_magic(bar, bt, bt->order_cur->ch);
+			battle_bar_default_open_magic(bar, bt, battle_current(bt)->ch);
 			break;
 		default:
 			break;
@@ -408,18 +444,9 @@
 handle_keydown_grid(struct battle_bar_default *bar, struct battle *bt, const union event *ev)
 {
 	/* Go back to main menu if I press escape. */
-	if (ev->key.key == KEY_ESCAPE) {
-		//gridmenu_reset(&bar->sub_grid);
+	if (ev->key.key == KEY_ESCAPE)
 		bar->state = BATTLE_BAR_DEFAULT_STATE_MENU;
-		return;
-	}
-
-	gridmenu_handle(&bar->sub_grid, ev);
-
-#if 0
-	if (bar->sub_grid.state == GRIDMENU_STATE_ACTIVATED) {
-		gridmenu_reset(&bar->sub_grid);
-
+	else if (gridmenu_handle(&bar->grid, ev)) {
 		switch (bar->menu) {
 		case BATTLE_BAR_DEFAULT_MENU_MAGIC:
 			switch_selection_spell(bar, bt);
@@ -431,7 +458,6 @@
 			break;
 		}
 	}
-#endif
 }
 
 static void
@@ -444,7 +470,8 @@
 		[BATTLE_BAR_DEFAULT_STATE_GRID] = handle_keydown_grid
 	};
 
-	handlers[bar->state](bar, bt, ev);
+	if (handlers[bar->state])
+		handlers[bar->state](bar, bt, ev);
 }
 
 #if 0
@@ -452,10 +479,10 @@
 static void
 handle_clickdown(struct battle_bar_default *bar, struct battle *bt, const union event *ev)
 {
+	assert(ev->type == EVENT_CLICKDOWN);
+
 	(void)bar;
 	(void)bt;
-	(void)ev;
-	assert(ev->type == EVENT_CLICKDOWN);
 
 	switch (bar->state) {
 	case BATTLE_BAR_DEFAULT_STATE_MENU:
@@ -464,8 +491,6 @@
 		break;
 	case BATTLE_BAR_DEFAULT_STATE_SUB:
 		/* We are in the sub menu (objects/spells). */
-		gridmenu_handle(&bar->sub_grid, ev);
-
 		if (bar->sub_grid.state == GRIDMENU_STATE_ACTIVATED)
 	default:
 		break;
@@ -477,21 +502,7 @@
 #endif
 
 static void
-init_gridmenu(struct battle_bar_default *bar, const struct battle *bt)
-{
-	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));
-}
-
-static void
-start(struct battle_bar *bar, struct battle *bt)
+self_start(struct battle_bar *bar, struct battle *bt)
 {
 	(void)bt;
 
@@ -505,50 +516,36 @@
 }
 
 static void
-handle(struct battle_bar *bar, struct battle *bt, const union event *ev)
+self_handle(struct battle_bar *bar, struct battle *bt, const union event *ev)
 {
 	battle_bar_default_handle(bar->data, bt, ev);
 }
 
 static void
-draw(const struct battle_bar *bar, const struct battle *bt)
+self_draw(const struct battle_bar *bar, const struct battle *bt)
 {
 	battle_bar_default_draw(bar->data, bt);
 }
 
-static void
-finish(struct battle_bar *bar, struct battle *bt)
-{
-	(void)bt;
-
-	battle_bar_default_finish(bar->data);
-	free(bar->data);
-}
-
 void
-battle_bar_default_positionate(struct battle_bar_default *bar, const struct battle *bt)
+battle_bar_default_init(struct battle_bar_default *bar)
 {
 	assert(bar);
-	assert(bt);
+
+	struct geo geo[2];
+
+	memset(bar, 0, sizeof (*bar));
 
 	bar->w = window.w;
 	bar->h = window.h * 0.12;
 	bar->x = 0;
 	bar->y = window.h - bar->h;
 
-	/* 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;
+	dimensions(geo, bar);
 
-	/* 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;
+	gridmenu_init(&bar->grid, 2, 2, NULL, 0);
+	gridmenu_resize(&bar->grid, bar->x, geo[0].y, geo[1].w, bar->h);
+	bar->grid.theme = bar->theme;
 }
 
 void
@@ -558,17 +555,14 @@
 	assert(bt);
 	assert(ch);
 
-#if 0
-	init_gridmenu(bar, bt);
+	bar->items = bar->grid.items = alloc_rearray0(bar->items, bar->itemsz,
+	    CHARACTER_SPELL_MAX, sizeof (*bar->items));
+	bar->itemsz = bar->grid.itemsz = CHARACTER_SPELL_MAX;
+	bar->state = BATTLE_BAR_DEFAULT_STATE_GRID;
 
 	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_DEFAULT_STATE_GRID;
-#endif
+			bar->grid.items[i] = ch->spells[i]->name;
 }
 
 void
@@ -577,10 +571,8 @@
 	assert(bar);
 	assert(bt);
 
+	/* TODO: not implemented yet. */
 #if 0
-
-	init_gridmenu(bar, bt);
-
 	for (size_t i = 0; i < INVENTORY_ITEM_MAX; ++i) {
 		if (bt->inventory->items[i].item) {
 			snprintf(bar->sub_items[i], sizeof (bar->sub_items[i]), "%-16s %u",
@@ -589,8 +581,6 @@
 		}
 	}
 
-	gridmenu_repaint(&bar->sub_grid);
-
 	bar->state = BATTLE_BAR_DEFAULT_STATE_GRID;
 #endif
 }
@@ -600,8 +590,6 @@
 {
 	assert(bar);
 
-	//gridmenu_reset(&bar->sub_grid);
-
 	bar->menu = BATTLE_BAR_DEFAULT_MENU_ATTACK;
 	bar->state = BATTLE_BAR_DEFAULT_STATE_MENU;
 }
@@ -618,10 +606,10 @@
 	assert(sel);
 
 	static void (*validate[])(struct battle_bar_default *, struct battle *, const struct selection *) = {
-		[BATTLE_BAR_DEFAULT_MENU_ATTACK] = validate_attack,
-		[BATTLE_BAR_DEFAULT_MENU_ITEM] = validate_item,
-		[BATTLE_BAR_DEFAULT_MENU_MAGIC] = validate_magic,
-		[BATTLE_BAR_DEFAULT_MENU_SPECIAL] = NULL
+		[BATTLE_BAR_DEFAULT_MENU_ATTACK]        = validate_attack,
+		[BATTLE_BAR_DEFAULT_MENU_ITEM]          = validate_item,
+		[BATTLE_BAR_DEFAULT_MENU_MAGIC]         = validate_magic,
+		[BATTLE_BAR_DEFAULT_MENU_SPECIAL]       = NULL
 	};
 
 	if (validate[bar->menu])
@@ -650,8 +638,11 @@
 	assert(bar);
 	assert(bt);
 
-	draw_status(bar, bt);
-	draw_menu(bar, bt);
+	struct geo geo[2];
+
+	dimensions(geo, bar);
+	draw_menu(bar, &geo[0]);
+	draw_status(bar, bt, &geo[1]);
 
 	if (bar->state == BATTLE_BAR_DEFAULT_STATE_GRID) {
 		switch (bar->menu) {
@@ -668,7 +659,7 @@
 
 	/* Sub menu is only shown if state is set to it. */
 	if (bar->state == BATTLE_BAR_DEFAULT_STATE_GRID)
-		gridmenu_draw(&bar->sub_grid);
+		gridmenu_draw(&bar->grid);
 }
 
 void
@@ -676,27 +667,21 @@
 {
 	assert(bar);
 
-	//gridmenu_finish(&bar->sub_grid);
-
+	free(bar->items);
 	memset(bar, 0, sizeof (*bar));
 }
 
 void
-battle_bar_default(struct battle *bt)
+battle_bar_default(struct battle_bar_default *self, struct battle_bar *bar)
 {
-	assert(bt);
+	assert(self);
+	assert(bar);
 
-	struct self *self;
+	memset(bar, 0, sizeof (*bar));
 
-	self = alloc_new0(sizeof (*self));
-	self->bar.data = self;
-	self->bar.start = start;
-	self->bar.select = self_select;
-	self->bar.handle = handle;
-	self->bar.draw = draw;
-	self->bar.finish = finish;
-
-	battle_bar_default_positionate(&self->data, bt);
-
-	bt->bar = &self->bar;
+	bar->data = self;
+	bar->start = self_start;
+	bar->select = self_select;
+	bar->handle = self_handle;
+	bar->draw = self_draw;
 }
--- a/src/libmlk-rpg/rpg/battle-bar-default.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-bar-default.h	Sun Feb 27 10:08:51 2022 +0100
@@ -21,12 +21,13 @@
 
 #include <core/core.h>
 
-#include <ui/frame.h>
 #include <ui/gridmenu.h>
 
 struct battle;
+struct battle_bar;
 struct character;
 struct selection;
+struct theme;
 
 union event;
 
@@ -38,6 +39,7 @@
 };
 
 enum battle_bar_default_state {
+	BATTLE_BAR_DEFAULT_STATE_NONE,
 	BATTLE_BAR_DEFAULT_STATE_MENU,
 	BATTLE_BAR_DEFAULT_STATE_GRID
 };
@@ -47,24 +49,20 @@
 	int y;
 	unsigned int w;
 	unsigned int h;
+	struct theme *theme;
 	enum battle_bar_default_state state;
-
-	/* Right status frame. */
-	struct frame status_frame;
-
-	/* Main menu selection. */
-	struct frame menu_frame;
 	enum battle_bar_default_menu menu;
 
-	/* Sub menu selection (spells/objects). */
-	char sub_items[16][128];
-	struct gridmenu sub_grid;
+	/* Private fields. */
+	const char **items;
+	size_t itemsz;
+	struct gridmenu grid;
 };
 
 CORE_BEGIN_DECLS
 
 void
-battle_bar_default_positionate(struct battle_bar_default *, const struct battle *);
+battle_bar_default_init(struct battle_bar_default *);
 
 void
 battle_bar_default_open_magic(struct battle_bar_default *, const struct battle *, struct character *);
@@ -79,7 +77,7 @@
 battle_bar_default_select(struct battle_bar_default *, struct battle *, const struct selection *);
 
 void
-battle_bar_default_handle(struct battle_bar_default *bar, struct battle *bt, const union event *);
+battle_bar_default_handle(struct battle_bar_default *, struct battle *, const union event *);
 
 void
 battle_bar_default_draw(const struct battle_bar_default *, const struct battle *);
@@ -88,7 +86,7 @@
 battle_bar_default_finish(struct battle_bar_default *);
 
 void
-battle_bar_default(struct battle *);
+battle_bar_default(struct battle_bar_default *, struct battle_bar *);
 
 CORE_END_DECLS
 
--- a/src/libmlk-rpg/rpg/battle-entity.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-entity.c	Sun Feb 27 10:08:51 2022 +0100
@@ -32,14 +32,14 @@
 static void
 draw_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.theme = BATTLE_THEME(bt);
+
+	if (et == battle_current(bt))
+		label.flags |=  LABEL_FLAGS_SELECTED;
+	else
+		label.flags &= ~LABEL_FLAGS_SELECTED;
 
 	label_draw(&label);
 }
--- a/src/libmlk-rpg/rpg/battle-state-ai.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-ai.c	Sun Feb 27 10:08:51 2022 +0100
@@ -32,7 +32,17 @@
 	(void)st;
 	(void)ticks;
 
-	return battle_state_ai_update(bt);
+	battle_state_ai_update(bt);
+
+	return 0;
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	(void)st;
+
+	battle_state_ai_draw(bt);
 }
 
 static void
@@ -43,20 +53,26 @@
 	free(st);
 }
 
-int
+void
 battle_state_ai_update(struct battle *bt)
 {
-	assert(bt);
+	assert(battle_ok(bt));
 
-	struct character *ch = bt->order_cur->ch;
+	struct character *ch = battle_current(bt)->ch;
 
 	/*
 	 * Immediately invoke the enemy exec strategy and put the battle state
 	 * to check.
 	 */
 	character_exec(ch, bt);
+}
 
-	return 0;
+void
+battle_state_ai_draw(const struct battle *bt)
+{
+	assert(battle_ok(bt));
+
+	battle_draw_component(bt, BATTLE_COMPONENT_ALL);
 }
 
 void
@@ -69,6 +85,7 @@
 	self = alloc_new0(sizeof (*self));
 	self->data = bt;
 	self->update = update;
+	self->draw = draw;
 	self->finish = finish;
 
 	battle_switch(bt, self);
--- a/src/libmlk-rpg/rpg/battle-state-ai.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-ai.h	Sun Feb 27 10:08:51 2022 +0100
@@ -21,10 +21,13 @@
 
 struct battle;
 
-int
+void
 battle_state_ai_update(struct battle *);
 
 void
+battle_state_ai_draw(const struct battle *);
+
+void
 battle_state_ai(struct battle *);
 
 #endif /* !MLK_RPG_BATTLE_STATE_AI_H */
--- a/src/libmlk-rpg/rpg/battle-state-attacking.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-attacking.c	Sun Feb 27 10:08:51 2022 +0100
@@ -56,9 +56,15 @@
 static int
 update(struct battle_state *st, struct battle *bt, unsigned int ticks)
 {
-	(void)ticks;
+	battle_state_attacking_update(st->data, bt, ticks);
+
+	return 0;
+}
 
-	return battle_state_attacking_update(st->data, bt);
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	battle_state_attacking_draw(st->data, bt);
 }
 
 static void
@@ -98,14 +104,16 @@
 		battle_entity_state_moving(source, x, y);
 }
 
-int
-battle_state_attacking_update(struct battle_state_attacking *atk, struct battle *bt)
+void
+battle_state_attacking_update(struct battle_state_attacking *atk, struct battle *bt, unsigned int ticks)
 {
 	assert(atk);
 	assert(bt);
 
+	battle_update_component(bt, ticks, BATTLE_COMPONENT_ALL);
+
 	if (!battle_entity_update(atk->source, 0))
-		return 0;
+		return;
 
 	switch (atk->status) {
 	case BATTLE_STATE_ATTACKING_ADVANCING:
@@ -135,8 +143,15 @@
 	default:
 		break;
 	}
+}
 
-	return 0;
+void
+battle_state_attacking_draw(const struct battle_state_attacking *atk, const struct battle *bt)
+{
+	assert(atk);
+	assert(battle_ok(bt));
+
+	battle_draw_component(bt, BATTLE_COMPONENT_ALL);
 }
 
 void
@@ -151,6 +166,7 @@
 	self = alloc_new0(sizeof (*self));
 	self->state.data = self;
 	self->state.update = update;
+	self->state.draw = draw;
 	self->state.finish = finish;
 
 	battle_state_attacking_init(&self->data, source, target);
--- a/src/libmlk-rpg/rpg/battle-state-attacking.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-attacking.h	Sun Feb 27 10:08:51 2022 +0100
@@ -45,8 +45,11 @@
                             struct battle_entity *,
                             struct battle_entity *);
 
-int
-battle_state_attacking_update(struct battle_state_attacking *, struct battle *);
+void
+battle_state_attacking_update(struct battle_state_attacking *, struct battle *, unsigned int);
+
+void
+battle_state_attacking_draw(const struct battle_state_attacking *, const struct battle *);
 
 void
 battle_state_attacking(struct battle_entity *, struct battle_entity *, struct battle *);
--- a/src/libmlk-rpg/rpg/battle-state-check.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-check.c	Sun Feb 27 10:08:51 2022 +0100
@@ -24,6 +24,7 @@
 #include <core/panic.h>
 #include <core/sprite.h>
 #include <core/texture.h>
+#include <core/trace.h>
 
 #include "battle-state-check.h"
 #include "battle-state-lost.h"
@@ -31,20 +32,21 @@
 #include "battle-state.h"
 #include "battle.h"
 #include "character.h"
+#include "rpg_p.h"
 
 struct fadeout {
 	struct character *ch;
 	int x;
 	int y;
-	struct action action;
+	struct drawable dw;
 	unsigned int alpha;
 	unsigned int elapsed;
 };
 
 static int
-fadeout_update(struct action *act, unsigned int ticks)
+fadeout_update(struct drawable *dw, unsigned int ticks)
 {
-	struct fadeout *fade = act->data;
+	struct fadeout *fade = dw->data;
 
 	fade->elapsed += ticks;
 
@@ -61,9 +63,9 @@
 }
 
 static void
-fadeout_draw(struct action *act)
+fadeout_draw(struct drawable *dw)
 {
-	const struct fadeout *fade = act->data;
+	const struct fadeout *fade = dw->data;
 	struct sprite *sprite = fade->ch->sprites[CHARACTER_SPRITE_NORMAL];
 
 	texture_set_alpha_mod(sprite->texture, fade->alpha);
@@ -72,9 +74,9 @@
 }
 
 static void
-fadeout_finish(struct action *act)
+fadeout_finish(struct drawable *dw)
 {
-	free(act->data);
+	free(dw->data);
 }
 
 static void
@@ -82,22 +84,23 @@
 {
 	struct fadeout *fade;
 
-	if (!(fade = alloc_new0(sizeof (*fade))))
-		panic();
+	if (!bt->effects) {
+		tracef(_("can't create a fadeout effect without a drawable_stack"));
+		return;
+	}
 
+	fade = alloc_new0(sizeof (*fade));
 	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;
+	fade->dw.data = fade;
+	fade->dw.draw = fadeout_draw;
+	fade->dw.update = fadeout_update;
+	fade->dw.finish = fadeout_finish;
 
-	if (action_stack_add(&bt->actions[1], &fade->action) < 0)
+	if (drawable_stack_add(bt->effects, &fade->dw) < 0)
 		free(fade);
-
-	memset(et, 0, sizeof (*et));
 }
 
 static int
@@ -130,11 +133,12 @@
 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);
+	for (size_t i = 0; i < bt->enemiesz; ++i) {
+		if (bt->enemies[i] && character_ok(bt->enemies[i]->ch) && bt->enemies[i]->ch->hp == 0) {
+			fadeout(bt, bt->enemies[i]);
+			bt->enemies[i] = NULL;
+		}
+	}
 }
 
 static int
@@ -143,7 +147,17 @@
 	(void)st;
 	(void)ticks;
 
-	return battle_state_check_update(bt);
+	battle_state_check_update(bt);
+
+	return 0;
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	(void)st;
+
+	battle_state_check_draw(bt);
 }
 
 static void
@@ -154,10 +168,10 @@
 	free(st);
 }
 
-int
+void
 battle_state_check_update(struct battle *bt)
 {
-	assert(bt);
+	assert(battle_ok(bt));
 
 	clean(bt);
 
@@ -167,20 +181,27 @@
 		battle_state_victory(bt);
 	else
 		battle_next(bt);
+}
 
-	return 0;
+void
+battle_state_check_draw(const struct battle *bt)
+{
+	assert(battle_ok(bt));
+
+	battle_draw_component(bt, BATTLE_COMPONENT_ALL);
 }
 
 void
 battle_state_check(struct battle *bt)
 {
-	assert(bt);
+	assert(battle_ok(bt));
 
 	struct battle_state *self;
 
 	self = alloc_new0(sizeof (*self));
 	self->data = bt;
 	self->update = update;
+	self->draw = draw;
 	self->finish = finish;
 
 	battle_switch(bt, self);
--- a/src/libmlk-rpg/rpg/battle-state-check.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-check.h	Sun Feb 27 10:08:51 2022 +0100
@@ -21,10 +21,13 @@
 
 struct battle;
 
-int
+void
 battle_state_check_update(struct battle *);
 
 void
+battle_state_check_draw(const struct battle *);
+
+void
 battle_state_check(struct battle *);
 
 #endif /* !MLK_RPG_BATTLE_STATE_CHECK_H */
--- a/src/libmlk-rpg/rpg/battle-state-closing.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-closing.c	Sun Feb 27 10:08:51 2022 +0100
@@ -48,7 +48,7 @@
 {
 	(void)bt;
 
-	battle_state_closing_draw(st->data);
+	battle_state_closing_draw(st->data, bt);
 }
 
 static void
@@ -105,10 +105,11 @@
 }
 
 void
-battle_state_closing_draw(const struct battle_state_closing *cls)
+battle_state_closing_draw(const struct battle_state_closing *cls, const struct battle *bt)
 {
 	assert(cls);
 
+	battle_draw_component(bt, BATTLE_COMPONENT_ALL);
 	texture_draw(&cls->texture, 0, 0);
 }
 
--- a/src/libmlk-rpg/rpg/battle-state-closing.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-closing.h	Sun Feb 27 10:08:51 2022 +0100
@@ -36,7 +36,7 @@
 battle_state_closing_update(struct battle_state_closing *, unsigned int);
 
 void
-battle_state_closing_draw(const struct battle_state_closing *);
+battle_state_closing_draw(const struct battle_state_closing *, const struct battle *);
 
 void
 battle_state_closing_finish(struct battle_state_closing *);
--- a/src/libmlk-rpg/rpg/battle-state-lost.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-lost.c	Sun Feb 27 10:08:51 2022 +0100
@@ -53,7 +53,7 @@
 {
 	(void)bt;
 
-	battle_state_lost_draw(st->data);
+	battle_state_lost_draw(st->data, bt);
 }
 
 void
@@ -108,10 +108,12 @@
 }
 
 void
-battle_state_lost_draw(struct battle_state_lost *lost)
+battle_state_lost_draw(struct battle_state_lost *lost, const struct battle *bt)
 {
 	assert(lost);
+	assert(battle_ok(bt));
 
+	battle_draw_component(bt, BATTLE_COMPONENT_ALL);
 	message_draw(&lost->msg);
 }
 
--- a/src/libmlk-rpg/rpg/battle-state-lost.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-lost.h	Sun Feb 27 10:08:51 2022 +0100
@@ -40,7 +40,7 @@
 battle_state_lost_update(struct battle_state_lost *, struct battle *, unsigned int);
 
 void
-battle_state_lost_draw(struct battle_state_lost *);
+battle_state_lost_draw(struct battle_state_lost *, const struct battle *);
 
 void
 battle_state_lost(struct battle *);
--- a/src/libmlk-rpg/rpg/battle-state-menu.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-menu.c	Sun Feb 27 10:08:51 2022 +0100
@@ -26,35 +26,6 @@
 #include "battle-state.h"
 #include "battle.h"
 
-#if 0
-
-static void
-open_spells(struct battle *bt)
-{
-	battle_bar_open_spells(&bt->bar, bt, bt->order_cur->ch);
-	battle_state_sub(bt);
-}
-
-static void
-open_attack(struct battle *bt)
-{
-	struct selection slt = {
-		.allowed_sides = SELECTION_SIDE_ENEMY
-	};
-
-	selection_first(&slt, bt);
-	battle_state_selection(bt, &slt);
-}
-
-static void
-open_items(struct battle *bt)
-{
-	battle_bar_open_items(&bt->bar, bt);
-	battle_state_sub(bt);
-}
-
-#endif
-
 static void
 handle(struct battle_state *st, struct battle *bt, const union event *ev)
 {
@@ -63,6 +34,24 @@
 	battle_state_menu_handle(bt, ev);
 }
 
+static int
+update(struct battle_state *st, struct battle *bt, unsigned int ticks)
+{
+	(void)st;
+
+	battle_state_menu_update(bt, ticks);
+
+	return 0;
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	(void)st;
+
+	battle_state_menu_draw(bt);
+}
+
 static void
 finish(struct battle_state *st, struct battle *bt)
 {
@@ -81,6 +70,22 @@
 }
 
 void
+battle_state_menu_update(struct battle *bt, unsigned int ticks)
+{
+	assert(battle_ok(bt));
+
+	battle_update_component(bt, ticks, BATTLE_COMPONENT_ALL);
+}
+
+void
+battle_state_menu_draw(const struct battle *bt)
+{
+	assert(battle_ok(bt));
+
+	battle_draw_component(bt, BATTLE_COMPONENT_ALL);
+}
+
+void
 battle_state_menu(struct battle *bt)
 {
 	assert(bt);
@@ -90,6 +95,8 @@
 	state = alloc_new0(sizeof (*state));
 	state->data = bt;
 	state->handle = handle;
+	state->update = update;
+	state->draw = draw;
 	state->finish = finish;
 
 	battle_bar_start(bt->bar, bt);
--- a/src/libmlk-rpg/rpg/battle-state-menu.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-menu.h	Sun Feb 27 10:08:51 2022 +0100
@@ -27,6 +27,12 @@
 battle_state_menu_handle(struct battle *, const union event *);
 
 void
+battle_state_menu_update(struct battle *, unsigned int);
+
+void
+battle_state_menu_draw(const struct battle *);
+
+void
 battle_state_menu(struct battle *);
 
 #endif /* !MLK_RPG_BATTLE_STATE_MENU_H */
--- a/src/libmlk-rpg/rpg/battle-state-opening.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-opening.c	Sun Feb 27 10:08:51 2022 +0100
@@ -50,7 +50,7 @@
 {
 	(void)bt;
 
-	battle_state_opening_draw(st->data);
+	battle_state_opening_draw(st->data, bt);
 }
 
 static void
@@ -77,12 +77,17 @@
 }
 
 void
-battle_state_opening_draw(const struct battle_state_opening *op)
+battle_state_opening_draw(const struct battle_state_opening *op, const struct battle *bt)
 {
+	assert(op);
+	assert(bt);
+
 	const unsigned int w = window.w;
 	const unsigned int h = window.h / 2;
 	const unsigned int ch = op->elapsed * h / DELAY;
 
+	battle_draw_component(bt, BATTLE_COMPONENT_BACKGROUND | BATTLE_COMPONENT_ENTITIES);
+
 	/* Draw some bezels opening. */
 	painter_set_color(0x000000ff);
 	painter_draw_rectangle(0, 0, w, h - ch);
--- a/src/libmlk-rpg/rpg/battle-state-opening.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-opening.h	Sun Feb 27 10:08:51 2022 +0100
@@ -29,7 +29,7 @@
 battle_state_opening_update(struct battle_state_opening *, struct battle *, unsigned int);
 
 void
-battle_state_opening_draw(const struct battle_state_opening *);
+battle_state_opening_draw(const struct battle_state_opening *, const struct battle *);
 
 void
 battle_state_opening(struct battle *);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-rendering.c	Sun Feb 27 10:08:51 2022 +0100
@@ -0,0 +1,114 @@
+/*
+ * battle-state-rendering.c -- battle state (rendering an action)
+ *
+ * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <core/alloc.h>
+#include <core/drawable.h>
+
+#include "battle-state-rendering.h"
+#include "battle.h"
+
+struct self {
+	struct battle_state_rendering data;
+	struct battle_state state;
+};
+
+static int
+update(struct battle_state *st, struct battle *bt, unsigned int ticks)
+{
+	battle_state_rendering_update(st->data, bt, ticks);
+
+	return 0;
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	battle_state_rendering_draw(st->data, bt);
+}
+
+static void
+finish(struct battle_state *st, struct battle *bt)
+{
+	(void)bt;
+
+	battle_state_rendering_finish(st->data);
+	free(st->data);
+}
+
+void
+battle_state_rendering_init(struct battle_state_rendering *rdr, struct drawable *dw)
+{
+	assert(rdr);
+	assert(dw);
+
+	rdr->drawable = dw;
+}
+
+int
+battle_state_rendering_update(struct battle_state_rendering *rdr, struct battle *bt, unsigned int ticks)
+{
+	assert(rdr);
+	assert(battle_ok(bt));
+
+	battle_update_component(bt, BATTLE_COMPONENT_ALL, ticks);
+
+	if (drawable_update(rdr->drawable, ticks)) {
+		drawable_end(rdr->drawable);
+		return 1;
+	}
+
+	return 0;
+}
+
+void
+battle_state_rendering_draw(const struct battle_state_rendering *rdr, const struct battle *bt)
+{
+	assert(rdr);
+
+	battle_draw_component(bt, BATTLE_COMPONENT_ALL);
+	drawable_draw(rdr->drawable);
+}
+
+void
+battle_state_rendering_finish(struct battle_state_rendering *rdr)
+{
+	assert(rdr);
+
+	drawable_finish(rdr->drawable);
+}
+
+void
+battle_state_rendering(struct battle *bt, struct drawable *dw)
+{
+	assert(bt);
+	assert(dw);
+
+	struct self *self;
+
+	self = alloc_new0(sizeof (*self));
+	self->state.data = self;
+	self->state.update = update;
+	self->state.draw = draw;
+	self->state.finish = finish;
+
+	battle_state_rendering_init(&self->data, dw);
+	battle_switch(bt, &self->state);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libmlk-rpg/rpg/battle-state-rendering.h	Sun Feb 27 10:08:51 2022 +0100
@@ -0,0 +1,44 @@
+/*
+ * battle-state-rendering.h -- battle state (rendering an action)
+ *
+ * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MLK_RPG_BATTLE_STATE_RENDERING_H
+#define MLK_RPG_BATTLE_STATE_RENDERING_H
+
+struct battle;
+struct drawable;
+
+struct battle_state_rendering {
+	struct drawable *drawable;
+};
+
+void
+battle_state_rendering_init(struct battle_state_rendering *, struct drawable *);
+
+int
+battle_state_rendering_update(struct battle_state_rendering *, struct battle *, unsigned int);
+
+void
+battle_state_rendering_draw(const struct battle_state_rendering *, const struct battle *);
+
+void
+battle_state_rendering_finish(struct battle_state_rendering *);
+
+void
+battle_state_rendering(struct battle *, struct drawable *);
+
+#endif /* !MLK_RPG_BATTLE_STATE_RENDERING_H */
--- a/src/libmlk-rpg/rpg/battle-state-selection.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-selection.c	Sun Feb 27 10:08:51 2022 +0100
@@ -45,7 +45,7 @@
 };
 
 static void
-select_adj_in(struct battle_state_selection *slt, const struct battle_entity *entities, size_t entitiesz, int step)
+select_adj_in(struct battle_state_selection *slt, struct battle_entity **entities, size_t entitiesz, int step)
 {
 	assert(slt->select.index_character != (unsigned int)-1);
 
@@ -53,14 +53,14 @@
 
 	if (step < 0) {
 		while (newselection > 0) {
-			if (character_ok(entities[--newselection].ch)) {
+			if (character_ok(entities[--newselection]->ch)) {
 				slt->select.index_character = newselection;
 				break;
 			}
 		}
 	} else {
 		while (newselection < entitiesz) {
-			if (character_ok(entities[++newselection].ch)) {
+			if (character_ok(entities[++newselection]->ch)) {
 				slt->select.index_character = newselection;
 				break;
 			}
@@ -72,9 +72,9 @@
 select_adj(struct battle_state_selection *slt, const struct battle *bt, int step)
 {
 	if (slt->select.index_side == 0)
-		select_adj_in(slt, bt->enemies, UTIL_SIZE(bt->enemies), step);
+		select_adj_in(slt, bt->enemies, bt->enemiesz, step);
 	else
-		select_adj_in(slt, bt->team, UTIL_SIZE(bt->team), step);
+		select_adj_in(slt, bt->team, bt->teamsz, step);
 }
 
 static void
@@ -133,13 +133,13 @@
 
 static void
 draw_cursors(const struct battle *bt,
-             const struct battle_entity *entities,
+             struct battle_entity * const *entities,
              size_t entitiesz)
 {
 	for (size_t i = 0; i < entitiesz; ++i) {
-		const struct battle_entity *et = &entities[i];
+		const struct battle_entity *et = entities[i];
 
-		if (character_ok(et->ch))
+		if (et && character_ok(et->ch))
 			draw_cursor(bt, et);
 	}
 }
@@ -195,18 +195,20 @@
 	assert(stl);
 	assert(bt);
 
+	battle_draw_component(bt, BATTLE_COMPONENT_ALL);
+
 	if (stl->select.index_character == -1U) {
 		/* All selected. */
 		if (stl->select.index_side == 0)
-			draw_cursors(bt, bt->enemies, UTIL_SIZE(bt->enemies));
+			draw_cursors(bt, bt->enemies, bt->enemiesz);
 		else
-			draw_cursors(bt, bt->team, UTIL_SIZE(bt->team));
+			draw_cursors(bt, bt->team, bt->teamsz);
 	} else {
 		/* Select one. */
 		if (stl->select.index_side == 0)
-			draw_cursor(bt, &bt->enemies[stl->select.index_character]);
+			draw_cursor(bt, bt->enemies[stl->select.index_character]);
 		else
-			draw_cursor(bt, &bt->team[stl->select.index_character]);
+			draw_cursor(bt, bt->team[stl->select.index_character]);
 	}
 }
 
--- a/src/libmlk-rpg/rpg/battle-state-victory.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-victory.c	Sun Feb 27 10:08:51 2022 +0100
@@ -55,7 +55,7 @@
 {
 	(void)bt;
 
-	battle_state_victory_draw(st->data);
+	battle_state_victory_draw(st->data, bt);
 }
 
 void
@@ -101,6 +101,8 @@
 	assert(vic);
 	assert(bt);
 
+	battle_update_component(bt, BATTLE_COMPONENT_ALL, ticks);
+
 	if (message_update(&vic->msg, ticks))
 		battle_state_closing(bt);
 
@@ -108,10 +110,11 @@
 }
 
 void
-battle_state_victory_draw(const struct battle_state_victory *vic)
+battle_state_victory_draw(const struct battle_state_victory *vic, const struct battle *bt)
 {
 	assert(vic);
 
+	battle_draw_component(bt, BATTLE_COMPONENT_ALL);
 	message_draw(&vic->msg);
 }
 
--- a/src/libmlk-rpg/rpg/battle-state-victory.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle-state-victory.h	Sun Feb 27 10:08:51 2022 +0100
@@ -40,7 +40,7 @@
 battle_state_victory_update(struct battle_state_victory *, struct battle *, unsigned int);
 
 void
-battle_state_victory_draw(const struct battle_state_victory *);
+battle_state_victory_draw(const struct battle_state_victory *, const struct battle *);
 
 void
 battle_state_victory(struct battle *);
--- a/src/libmlk-rpg/rpg/battle.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle.c	Sun Feb 27 10:08:51 2022 +0100
@@ -25,9 +25,9 @@
 #include <core/event.h>
 #include <core/font.h>
 #include <core/music.h>
-#include <core/painter.h>
 #include <core/sprite.h>
 #include <core/texture.h>
+#include <core/trace.h>
 #include <core/util.h>
 #include <core/window.h>
 
@@ -48,6 +48,7 @@
 #include "character.h"
 #include "inventory.h"
 #include "item.h"
+#include "rpg_p.h"
 #include "spell.h"
 
 struct indicator {
@@ -96,14 +97,14 @@
 }
 
 static struct battle_entity *
-random_select(struct battle_entity *group, size_t groupsz)
+random_select(struct battle_entity **group, size_t groupsz)
 {
 	struct battle_entity *ret = NULL, *et = NULL;
 
 	do {
-		et = &group[util_nrand(0, groupsz - 1)];
+		et = group[util_nrand(0, groupsz - 1)];
 
-		if (et->ch)
+		if (et && et->ch)
 			ret = et;
 	} while (!ret);
 
@@ -116,14 +117,14 @@
 	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;
+	return et2->ch->agt < et1->ch->agt;
 }
 
 static int
 is_team(const struct battle *bt, const struct character *ch)
 {
-	for (size_t i = 0; i < BATTLE_TEAM_MAX; ++i)
-		if (bt->team[i].ch == ch)
+	for (size_t i = 0; i < bt->teamsz; ++i)
+		if (bt->team[i] && bt->team[i]->ch == ch)
 			return 1;
 
 	return 0;
@@ -170,7 +171,7 @@
 		if (et->x != 0 || et->y != 0)
 			return;
 
-		if (battle_entity_ok(&bt->team[i])) {
+		if (battle_entity_ok(bt->team[i])) {
 			nmemb++;
 			requirement += et->ch->sprites[CHARACTER_SPRITE_NORMAL]->cellh;
 		}
@@ -180,7 +181,7 @@
 	spacing = (window.h - requirement) / (nmemb + 1);
 	x = window.w - 200;
 	y = spacing;
-	
+
 	BATTLE_TEAM_FOREACH(bt, et) {
 		if (battle_entity_ok(et)) {
 			et->x = x;
@@ -191,26 +192,42 @@
 }
 
 static void
-draw_entities(const struct battle *bt, struct battle_entity *entities, size_t entitiesz)
+draw_entities(const struct battle *bt, struct battle_entity **entities, size_t entitiesz)
 {
 	for (size_t i = 0; i < entitiesz; ++i) {
-		if (battle_entity_ok(&entities[i]))
-			battle_entity_draw(&entities[i], bt);
+		if (battle_entity_ok(entities[i]))
+			battle_entity_draw(entities[i], bt);
 	}
 }
 
 static void
-update_entities(struct battle_entity *entities, size_t entitiesz, unsigned int ticks)
+update_entities(struct battle_entity **entities, size_t entitiesz, unsigned int ticks)
 {
 	for (size_t i = 0; i < entitiesz; ++i) {
-		if (battle_entity_ok(&entities[i]))
-			battle_entity_update(&entities[i], ticks);
+		if (battle_entity_ok(entities[i]))
+			battle_entity_update(entities[i], ticks);
 	}
 }
 
 void
+battle_init(struct battle *bt)
+{
+	assert(bt);
+
+	memset(bt, 0, sizeof (*bt));
+}
+
+int
+battle_ok(const struct battle *bt)
+{
+	return bt && bt->state && bt->bar && bt->enemiesz && bt->team;
+}
+
+void
 battle_start(struct battle *bt)
 {
+	assert(bt);
+
 	struct battle_entity *et;
 
 	BATTLE_TEAM_FOREACH(bt, et)
@@ -229,6 +246,8 @@
 	/* Play music if present. */
 	if (bt->music[0])
 		music_play(bt->music[0], MUSIC_LOOP);
+
+	battle_order(bt);
 }
 
 void
@@ -246,20 +265,39 @@
 void
 battle_order(struct battle *bt)
 {
-	struct battle_entity *et, **porder;
-
-	/* First, put a pointer for every enemy/team member. */
-	porder = &bt->order[0];
+	struct battle_entity **porder;
 
-	BATTLE_TEAM_FOREACH(bt, et)
-		if (character_ok(et->ch))
-			*porder++ = et;
-	BATTLE_ENEMY_FOREACH(bt, et)
-		if (character_ok(et->ch))
-			*porder++ = et;
+	/* Create a pointer list to every entity. */
+	bt->order = alloc_rearray0(bt->order, bt->ordersz,
+	    bt->teamsz + bt->enemiesz, sizeof (*bt->order));
+	bt->ordersz = bt->teamsz + bt->enemiesz;
+	bt->ordercur = porder = bt->order;
+
+	for (size_t i = 0; i < bt->teamsz; ++i)
+		if (bt->team[i] && character_ok(bt->team[i]->ch))
+			*porder++ = bt->team[i];
+	for (size_t i = 0; i < bt->enemiesz; ++i)
+		if (bt->enemies[i] && character_ok(bt->enemies[i]->ch))
+			*porder++ = bt->enemies[i];
 
 	/* Now sort. */
-	qsort(bt->order, porder - bt->order, sizeof (*porder), cmp_order);
+	qsort(bt->order, bt->ordersz, sizeof (*bt->order), cmp_order);
+}
+
+struct battle_entity *
+battle_current(const struct battle *bt)
+{
+	assert(bt);
+
+	return *bt->ordercur;
+}
+
+size_t
+battle_index(const struct battle *bt)
+{
+	assert(bt);
+
+	return bt->ordercur - bt->order;
 }
 
 void
@@ -273,9 +311,9 @@
 	/* Target is empty? select randomly. */
 	if (!target) {
 		if (is_team(bt, source))
-			target = random_select(bt->enemies, BATTLE_ENEMY_MAX)->ch;
+			target = random_select(bt->enemies, bt->enemiesz)->ch;
 		else
-			target = random_select(bt->team, BATTLE_TEAM_MAX)->ch;
+			target = random_select(bt->team, bt->teamsz)->ch;
 	}
 
 	battle_state_attacking(battle_find(bt, source), battle_find(bt, target), bt);
@@ -295,7 +333,6 @@
 	/* TODO: animate. */
 	source->mp -= spell->mp;
 	spell_action(spell, bt, source, selection);
-	battle_state_check(bt);
 }
 
 void
@@ -321,26 +358,16 @@
 {
 	assert(bt);
 
-	if (!bt->order_cur) {
+	if (!bt->ordercur)
 		battle_order(bt);
-		bt->order_cur = bt->order[bt->order_curindex = 0];
-	} else {
-		for (++bt->order_curindex; bt->order_curindex < BATTLE_ENTITY_MAX; ++bt->order_curindex) {
-			if (battle_entity_ok(bt->order[bt->order_curindex])) {
-				bt->order_cur = bt->order[bt->order_curindex];
-				break;
-			}
-		}
-
-		/* End of "turn". */
-		if (bt->order_curindex >= BATTLE_ENTITY_MAX) {
+	else {
+		if (bt->ordercur - bt->order + (size_t)1U >= bt->ordersz)
 			battle_order(bt);
-			bt->order_cur = bt->order[bt->order_curindex = 0];
-		}
+		else
+			bt->ordercur++;
 	}
 
-	/* Change state depending on the kind of entity. */
-	if (is_team(bt, bt->order_cur->ch)) {
+	if (is_team(bt, battle_current(bt)->ch)) {
 		battle_bar_start(bt->bar, bt);
 		battle_state_menu(bt);
 	} else
@@ -362,8 +389,14 @@
 	assert(target);
 
 	const struct battle_entity *et = find(bt, target);
-	struct indicator *id = alloc_new0(sizeof (*id));
+	struct indicator *id;
 
+	if (!(bt->effects)) {
+		tracef(_("unable to add id without a drawable_stack"));
+		return;
+	}
+
+	id = alloc_new0(sizeof (*id));
 	id->bti.color = BATTLE_INDICATOR_HP_COLOR;
 	id->bti.amount = labs(amount);
 
@@ -377,23 +410,46 @@
 
 	battle_indicator_start(&id->bti);
 
-	if (drawable_stack_add(&bt->effects, &id->dw) < 0)
+	if (drawable_stack_add(bt->effects, &id->dw) < 0)
 		drawable_finish(&id->dw);
 }
 
 void
+battle_handle_component(struct battle *bt, const union event *ev, enum battle_component comp)
+{
+	assert(bt);
+	assert(ev);
+
+	if (comp & BATTLE_COMPONENT_BAR)
+		battle_bar_handle(bt->bar, bt, ev);
+	if ((comp & BATTLE_COMPONENT_ACTIONS) && bt->actions)
+		action_stack_handle(bt->actions, ev);
+}
+
+void
 battle_handle(struct battle *bt, const union event *ev)
 {
-	assert(bt && bt->state);
+	assert(bt);
 	assert(ev);
 
-	/* Handle actions. */
-	action_stack_handle(&bt->actions[0], ev);
-	action_stack_handle(&bt->actions[1], ev);
+	battle_state_handle(bt->state, bt, ev);
+}
+
+void
+battle_update_component(struct battle *bt, unsigned int ticks, enum battle_component comp)
+{
+	assert(bt);
 
-	/* Handling of action is disallowed if there are pending actions. */
-	if (action_stack_completed(&bt->actions[0]))
-		battle_state_handle(bt->state, bt, ev);
+	if (comp & BATTLE_COMPONENT_ENTITIES) {
+		update_entities(bt->team, bt->teamsz, ticks);
+		update_entities(bt->enemies, bt->enemiesz, ticks);
+	}
+	if (comp & BATTLE_COMPONENT_BAR)
+		battle_bar_update(bt->bar, bt, ticks);
+	if ((comp & BATTLE_COMPONENT_ACTIONS) && bt->actions)
+		action_stack_update(bt->actions, ticks);
+	if ((comp & BATTLE_COMPONENT_DRAWABLES) && bt->effects)
+		drawable_stack_update(bt->effects, ticks);
 }
 
 int
@@ -401,44 +457,35 @@
 {
 	assert(bt && bt->state);
 
-	action_stack_update(&bt->actions[0], ticks);
-	action_stack_update(&bt->actions[1], ticks);
-	drawable_stack_update(&bt->effects, ticks);
-
-	update_entities(bt->team, UTIL_SIZE(bt->team), ticks);
-	update_entities(bt->enemies, UTIL_SIZE(bt->enemies), ticks);
-
-	/* Game cannot update if the actions[0] stack isn't completed. */
-	if (!action_stack_completed(&bt->actions[0]))
-		return 0;
-
 	return battle_state_update(bt->state, bt, ticks);
 }
 
 void
-battle_draw(struct battle *bt)
+battle_draw_component(const struct battle *bt, enum battle_component comp)
 {
-	assert(bt && bt->state);
+	assert(bt);
 
-	painter_set_color(0xffffffff);
-	painter_clear();
-
-	if (bt->background && texture_ok(bt->background))
+	if ((comp & BATTLE_COMPONENT_BACKGROUND) && texture_ok(bt->background))
 		texture_scale(bt->background,
 		    0, 0, bt->background->w, bt->background->h,
 		    0, 0, window.w, window.h,
 		    0.f);
-
-	/* Draw entities. */
-	draw_entities(bt, bt->team, UTIL_SIZE(bt->team));
-	draw_entities(bt, bt->enemies, UTIL_SIZE(bt->enemies));
+	if (comp & BATTLE_COMPONENT_ENTITIES) {
+		draw_entities(bt, bt->team, bt->teamsz);
+		draw_entities(bt, bt->enemies, bt->enemiesz);
+	}
+	if (comp & BATTLE_COMPONENT_BAR)
+		battle_bar_draw(bt->bar, bt);
+	if ((comp & BATTLE_COMPONENT_ACTIONS) && bt->actions)
+		action_stack_draw(bt->actions);
+	if ((comp & BATTLE_COMPONENT_DRAWABLES) && bt->effects)
+		drawable_stack_draw(bt->effects);
+}
 
-	battle_bar_draw(bt->bar, bt);
-
-	action_stack_draw(&bt->actions[0]);
-	action_stack_draw(&bt->actions[1]);
-
-	drawable_stack_draw(&bt->effects);
+void
+battle_draw(const struct battle *bt)
+{
+	assert(battle_ok(bt));
 
 	battle_state_draw(bt->state, bt);
 }
@@ -451,12 +498,6 @@
 	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]);
-
-	battle_bar_finish(bt->bar, bt);
-
+	free(bt->order);
 	memset(bt, 0, sizeof (*bt));
 }
--- a/src/libmlk-rpg/rpg/battle.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/battle.h	Sun Feb 27 10:08:51 2022 +0100
@@ -44,14 +44,10 @@
 struct spell;
 struct theme;
 
-#define BATTLE_TEAM_MAX         (4)
-#define BATTLE_ENEMY_MAX        (8)
-#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)
+	for (size_t i = 0; i < (bt)->teamsz && ((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)
+	for (size_t i = 0; i < (bt)->enemiesz && ((iter) = (bt)->enemies[i]); ++i)
 
 #define BATTLE_THEME(bt) ((bt)->theme ? (bt)->theme : theme_default())
 
@@ -62,19 +58,30 @@
 	BATTLE_STATUS_LOST,
 };
 
+enum battle_component {
+	BATTLE_COMPONENT_BACKGROUND     = (1 << 0),
+	BATTLE_COMPONENT_ENTITIES       = (1 << 1),
+	BATTLE_COMPONENT_BAR            = (1 << 2),
+	BATTLE_COMPONENT_ACTIONS        = (1 << 3),
+	BATTLE_COMPONENT_DRAWABLES      = (1 << 4),
+	BATTLE_COMPONENT_ALL            = 0xff
+};
+
 struct battle {
 	struct battle_state *state;
 	enum battle_status status;
-	struct battle_entity team[BATTLE_TEAM_MAX];
-	struct battle_entity enemies[BATTLE_ENEMY_MAX];
-	struct battle_entity *order[BATTLE_ENTITY_MAX];
-	struct battle_entity *order_cur;
-	size_t order_curindex;
+	struct battle_entity **team;
+	size_t teamsz;
+	struct battle_entity **enemies;
+	size_t enemiesz;
+	struct battle_entity **order;
+	struct battle_entity **ordercur;
+	size_t ordersz;
 	struct texture *background;
 	struct music *music[3];
 	struct theme *theme;
-	struct drawable_stack effects;
-	struct action_stack actions[2];
+	struct drawable_stack *effects;
+	struct action_stack *actions;
 	struct inventory *inventory;
 	struct battle_bar *bar;
 };
@@ -82,6 +89,12 @@
 CORE_BEGIN_DECLS
 
 void
+battle_init(struct battle *);
+
+int
+battle_ok(const struct battle *);
+
+void
 battle_start(struct battle *);
 
 void
@@ -96,6 +109,12 @@
 void
 battle_order(struct battle *);
 
+struct battle_entity *
+battle_current(const struct battle *);
+
+size_t
+battle_index(const struct battle *);
+
 void
 battle_attack(struct battle *, struct character *, struct character *);
 
@@ -109,13 +128,22 @@
 battle_indicator_hp(struct battle *, const struct character *, long);
 
 void
+battle_handle_component(struct battle *, const union event *, enum battle_component);
+
+void
 battle_handle(struct battle *, const union event *);
 
+void
+battle_update_component(struct battle *, unsigned int, enum battle_component);
+
 int
 battle_update(struct battle *, unsigned int);
 
 void
-battle_draw(struct battle *);
+battle_draw_component(const struct battle *, enum battle_component);
+
+void
+battle_draw(const struct battle *);
 
 void
 battle_finish(struct battle *);
--- a/src/libmlk-rpg/rpg/selection.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-rpg/rpg/selection.c	Sun Feb 27 10:08:51 2022 +0100
@@ -25,38 +25,18 @@
 #include "selection.h"
 
 static void
-random(struct selection *slt, const struct battle *bt, const struct battle_entity *entities, size_t entitiesz)
+random(struct selection *slt, struct battle_entity **entities, size_t entitiesz)
 {
-	(void)bt;
-
-	struct {
-		const struct battle_entity *entity;
-		size_t position;
-	} table[BATTLE_ENTITY_MAX] = {0};
-
-	size_t tablesz = 0;
-
-	/*
-	 * Merge the list of valid entities into the table to select a random
-	 * one.
-	 */
-	for (size_t i = 0; i < entitiesz; ++i) {
-		if (battle_entity_ok(&entities[i])) {
-			table[tablesz].entity = &entities[i];
-			table[tablesz++].position = i;
-		}
-	}
-
-	slt->index_character = table[util_nrand(0, tablesz)].position;
+	do {
+		slt->index_character = util_nrand(0, entitiesz);
+	} while (!battle_entity_ok(entities[slt->index_character]));
 }
 
 static void
-first(struct selection *slt, const struct battle *bt, const struct battle_entity *entities, size_t entitiesz)
+first(struct selection *slt, struct battle_entity **entities, size_t entitiesz)
 {
-	(void)bt;
-
 	for (size_t i = 0; i < entitiesz; ++i) {
-		if (battle_entity_ok(&entities[i])) {
+		if (battle_entity_ok(entities[i])) {
 			slt->index_character = i;
 			break;
 		}
@@ -70,9 +50,9 @@
 	assert(bt);
 
 	if (slt->index_side == 0)
-		first(slt, bt, bt->enemies, BATTLE_ENEMY_MAX);
+		first(slt, bt->enemies, bt->enemiesz);
 	else
-		first(slt, bt, bt->team, BATTLE_TEAM_MAX);
+		first(slt, bt->team, bt->teamsz);
 }
 
 void
@@ -82,7 +62,7 @@
 	assert(bt);
 
 	if (slt->index_side == 0)
-		random(slt, bt, bt->enemies, BATTLE_ENEMY_MAX);
+		random(slt, bt->enemies, bt->enemiesz);
 	else
-		random(slt, bt, bt->team, BATTLE_TEAM_MAX);
+		random(slt, bt->team, bt->teamsz);
 }
--- a/src/libmlk-ui/ui/gridmenu.c	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-ui/ui/gridmenu.c	Sun Feb 27 10:08:51 2022 +0100
@@ -133,7 +133,7 @@
 	for (size_t i = 0; i < pagesz; ++i) {
 		item = i + pagenr * pagesz;
 
-		if (item >= menu->itemsz)
+		if (item >= menu->itemsz || !menu->items[item])
 			continue;
 
 		label.text = menu->items[item];
@@ -230,7 +230,7 @@
 gridmenu_init(struct gridmenu *menu,
               unsigned int nr,
               unsigned int nc,
-              const char * const *items,
+              const char **items,
               size_t itemsz)
 {
 	assert(menu);
--- a/src/libmlk-ui/ui/gridmenu.h	Fri Feb 18 16:16:38 2022 +0100
+++ b/src/libmlk-ui/ui/gridmenu.h	Sun Feb 27 10:08:51 2022 +0100
@@ -36,7 +36,7 @@
 	unsigned int h;
 	size_t selected;
 	const struct theme *theme;
-	const char * const *items;
+	const char **items;
 	size_t itemsz;
 	unsigned int nrows;
 	unsigned int ncols;
@@ -51,7 +51,7 @@
 CORE_BEGIN_DECLS
 
 void
-gridmenu_init(struct gridmenu *, unsigned int, unsigned int, const char * const *, size_t);
+gridmenu_init(struct gridmenu *, unsigned int, unsigned int, const char **, size_t);
 
 void
 gridmenu_resize(struct gridmenu *, int, int, unsigned int, unsigned int);