changeset 289:63d9fb56c609

rpg: rework selection
author David Demelier <markand@malikania.fr>
date Thu, 07 Jan 2021 15:52:56 +0100
parents cc0f02ae9005
children 9948e288925b
files doc/docs/dev/api/core/util.md libmlk-adventure/CMakeLists.txt libmlk-adventure/adventure/action/spawner.c libmlk-adventure/adventure/assets.c libmlk-adventure/adventure/assets.h libmlk-adventure/adventure/character/neth.c libmlk-adventure/adventure/molko.c libmlk-adventure/adventure/spell/fire-minor.c libmlk-adventure/adventure/spell/fire-minor.h libmlk-data/maps/map-world.json libmlk-rpg/CMakeLists.txt libmlk-rpg/rpg/battle-state-check.c libmlk-rpg/rpg/battle-state-menu.c libmlk-rpg/rpg/battle-state-selection.c libmlk-rpg/rpg/battle-state-sub.c libmlk-rpg/rpg/battle-state.h libmlk-rpg/rpg/battle.c libmlk-rpg/rpg/battle.h libmlk-rpg/rpg/selection.c libmlk-rpg/rpg/selection.h libmlk-rpg/rpg/spell.c libmlk-rpg/rpg/spell.h
diffstat 22 files changed, 448 insertions(+), 200 deletions(-) [+]
line wrap: on
line diff
--- a/doc/docs/dev/api/core/util.md	Thu Jan 07 15:50:01 2021 +0100
+++ b/doc/docs/dev/api/core/util.md	Thu Jan 07 15:52:56 2021 +0100
@@ -45,7 +45,7 @@
 
 ### util_nrand
 
-Returns a random number between `lower` and `upper` (included).
+Returns a random number between `lower` and `upper` (excluded).
 
 ```c
 unsigned int
--- a/libmlk-adventure/CMakeLists.txt	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-adventure/CMakeLists.txt	Thu Jan 07 15:52:56 2021 +0100
@@ -41,6 +41,8 @@
 	${libadventure_SOURCE_DIR}/adventure/mapscene/mapscene.h
 	${libadventure_SOURCE_DIR}/adventure/molko.c
 	${libadventure_SOURCE_DIR}/adventure/molko.h
+	${libadventure_SOURCE_DIR}/adventure/spell/fire-minor.c
+	${libadventure_SOURCE_DIR}/adventure/spell/fire-minor.h
 	${libadventure_SOURCE_DIR}/adventure/state/battle.c
 	${libadventure_SOURCE_DIR}/adventure/state/battle.h
 	${libadventure_SOURCE_DIR}/adventure/state/continue.c
--- a/libmlk-adventure/adventure/action/spawner.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-adventure/adventure/action/spawner.c	Thu Jan 07 15:52:56 2021 +0100
@@ -25,6 +25,7 @@
 #include <core/util.h>
 
 #include <rpg/battle.h>
+#include <rpg/character.h>
 #include <rpg/map.h>
 
 #include <adventure/molko.h>
@@ -51,13 +52,24 @@
 	struct battle *bt;
 
 	bt = alloc_new0(sizeof (*bt));
-	bt->enemies[0].ch = &character_black_cat;
+
+	bt->enemies[0].ch = alloc_dup(&character_black_cat, sizeof (character_black_cat));
 	bt->enemies[0].x = 400;
 	bt->enemies[0].y = 50;
+	bt->enemies[1].ch = alloc_dup(&character_black_cat, sizeof (character_black_cat));
+	bt->enemies[1].x = 200;
+	bt->enemies[1].y = 100;
+
 	bt->inventory = &molko.inventory;
 
-	for (size_t i = 0; i < TEAM_MEMBER_MAX; ++i)
-		bt->team[i].ch = molko.team.members[i];
+	for (size_t i = 0; i < TEAM_MEMBER_MAX; ++i) {
+		if (molko.team.members[i]) {
+			bt->team[i].ch = alloc_dup(molko.team.members[i], sizeof (*molko.team.members[i]));
+			character_reset(bt->team[i].ch);
+			bt->team[i].ch->hp = bt->team[i].ch->hpmax;
+			bt->team[i].ch->mp = bt->team[i].ch->mpmax;
+		}
+	}
 
 	molko_fight(bt);
 }
--- a/libmlk-adventure/adventure/assets.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-adventure/adventure/assets.c	Thu Jan 07 15:52:56 2021 +0100
@@ -44,7 +44,8 @@
 	SPRITE(ASSETS_SPRITE_CHARACTER_BLACK_CAT, "images/black-cat.png", 123, 161),
 	SPRITE(ASSETS_SPRITE_CHARACTER_NETH, "sprites/john-walk.png", 256, 256),
 	SPRITE(ASSETS_SPRITE_CHARACTER_NETH_SWORD, "sprites/john-sword.png", 256, 256),
-	SPRITE(ASSETS_SPRITE_FACES, "sprites/faces.png", 144, 144)
+	SPRITE(ASSETS_SPRITE_FACES, "sprites/faces.png", 144, 144),
+	SPRITE(ASSETS_SPRITE_EXPLOSION, "sprites/explosion.png", 256, 256)
 };
 
 static struct {
--- a/libmlk-adventure/adventure/assets.h	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-adventure/adventure/assets.h	Thu Jan 07 15:52:56 2021 +0100
@@ -37,6 +37,9 @@
 	ASSETS_SPRITE_CHARACTER_NETH_SWORD,
 	ASSETS_SPRITE_FACES,
 
+	/* Animations. */
+	ASSETS_SPRITE_EXPLOSION,
+
 	ASSETS_SPRITE_NUM
 };
 
--- a/libmlk-adventure/adventure/character/neth.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-adventure/adventure/character/neth.c	Thu Jan 07 15:52:56 2021 +0100
@@ -21,6 +21,8 @@
 #include <adventure/adventure_p.h>
 #include <adventure/assets.h>
 
+#include <adventure/spell/fire-minor.h>
+
 #include "neth.h"
 
 static void
@@ -42,5 +44,8 @@
 		[CHARACTER_SPRITE_NORMAL] = &assets_sprites[ASSETS_SPRITE_CHARACTER_NETH],
 		[CHARACTER_SPRITE_SWORD] = &assets_sprites[ASSETS_SPRITE_CHARACTER_NETH_SWORD]
 	},
+	.spells = {
+		&spell_fire_minor
+	},
 	.reset = reset
 };
--- a/libmlk-adventure/adventure/molko.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-adventure/adventure/molko.c	Thu Jan 07 15:52:56 2021 +0100
@@ -50,6 +50,9 @@
 #define WINDOW_WIDTH    1280
 #define WINDOW_HEIGHT   720
 
+
+#include "character/neth.h"
+
 static jmp_buf panic_buf;
 
 struct molko molko;
@@ -92,6 +95,9 @@
 	game_switch(state_splashscreen_ne(), true);
 #else
 	game_switch(state_mainmenu_new(), true);
+	molko.team.members[0] = &character_neth;
+	molko.team.members[1] = &character_neth;
+	molko_teleport("maps/map-world.map", -1, -1);
 #endif
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/spell/fire-minor.c	Thu Jan 07 15:52:56 2021 +0100
@@ -0,0 +1,130 @@
+/*
+ * fire-minor.c -- minor 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 <math.h>
+#include <stdlib.h>
+
+#include <core/action.h>
+#include <core/alloc.h>
+#include <core/animation.h>
+#include <core/maths.h>
+
+#include <rpg/battle.h>
+#include <rpg/character.h>
+#include <rpg/selection.h>
+
+#include <adventure/adventure_p.h>
+#include <adventure/assets.h>
+
+#include "fire-minor.h"
+
+struct rendering {
+	struct battle *battle;
+	struct battle_entity *source;
+	struct battle_entity *target;
+	struct action action;
+	struct animation animation;
+};
+
+static bool
+update(struct action *act, unsigned int ticks)
+{
+	struct rendering *rdr = act->data;
+
+	return animation_update(&rdr->animation, ticks);
+}
+
+static void
+draw(struct action *act)
+{
+	const struct rendering *rdr = act->data;
+
+	animation_draw(&rdr->animation, rdr->target->x, rdr->target->y);
+}
+
+static void
+end(struct action *act)
+{
+	struct rendering *rdr = act->data;
+	float base;
+
+	/* Compute damage. */
+	/* TODO: move this into a general maths computation. */
+	/* TODO: move min/max limits outside. */
+	base  = util_nrand(50, 70);
+	base += base * (maths_scale(rdr->source->ch->atk + rdr->source->ch->atkbonus, 0, 1000, 0, 100) / 100);
+
+	/* Reduce damage taken. */
+	base -= base * (maths_scale(rdr->target->ch->atk + rdr->target->ch->atkbonus, 0, 1000, 0, 100) / 200);
+	base  = base < 0 ? 0 : base;
+
+	/* TODO: add battle_damage function*/
+	rdr->target->ch->hp = fmax(rdr->target->ch->hp - base, rdr->target->ch->hp);
+	battle_indicator_hp(rdr->battle, rdr->target->ch, base);
+}
+
+static void
+finish(struct action *act)
+{
+	free(act->data);
+}
+
+static void
+select(const struct battle *bt, struct selection *slt)
+{
+	slt->index_side = 0;
+
+	selection_first(slt, bt);
+}
+
+static void
+action(struct battle *bt, struct character *owner, const struct selection *slt)
+{
+	struct rendering *rdr;
+
+	/* Action. */
+	rdr = alloc_new0(sizeof (*rdr));
+	rdr->action.data = rdr;
+	rdr->action.update = update;
+	rdr->action.draw = draw;
+	rdr->action.end = end;
+	rdr->action.finish = finish;
+
+	/* Battle and target. */
+	rdr->battle = bt;
+	rdr->source = battle_find(bt, owner);
+	rdr->target = &bt->enemies[slt->index_character];
+
+	/* Animation. */
+	rdr->animation.delay = 10;
+	rdr->animation.sprite = &assets_sprites[ASSETS_SPRITE_EXPLOSION];
+	animation_start(&rdr->animation);
+
+	action_stack_add(&bt->actions[0], &rdr->action);
+}
+
+const struct spell spell_fire_minor = {
+	.name = N_("Fire Minor"),
+	.description = N_("A small amount of fire balls"),
+	.mp = 10,
+	.type = SPELL_TYPE_FIRE,
+	.select_kind = SELECTION_KIND_ONE,
+	.select_side = SELECTION_SIDE_ENEMY,
+	.select = select,
+	.action = action
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/spell/fire-minor.h	Thu Jan 07 15:52:56 2021 +0100
@@ -0,0 +1,24 @@
+/*
+ * fire-minor.h -- minor 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 MOLKO_ADVENTURE_FIRE_MINOR_H
+#define MOLKO_ADVENTURE_FIRE_MINOR_H
+
+extern const struct spell spell_fire_minor;
+
+#endif /* !MOLKO_ADVENTURE_FIRE_MINOR_H */
--- a/libmlk-data/maps/map-world.json	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-data/maps/map-world.json	Thu Jan 07 15:52:56 2021 +0100
@@ -63,7 +63,7 @@
                         {
                          "name":"exec",
                          "type":"string",
-                         "value":"spawner|800|1000"
+                         "value":"spawner|1|2"
                         }],
                  "rotation":0,
                  "type":"",
--- a/libmlk-rpg/CMakeLists.txt	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/CMakeLists.txt	Thu Jan 07 15:52:56 2021 +0100
@@ -75,6 +75,7 @@
 	${libmlk-rpg_SOURCE_DIR}/rpg/rpg_p.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/save.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/save.h
+	${libmlk-rpg_SOURCE_DIR}/rpg/selection.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/selection.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/spell.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/spell.h
--- a/libmlk-rpg/rpg/battle-state-check.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/battle-state-check.c	Thu Jan 07 15:52:56 2021 +0100
@@ -53,7 +53,6 @@
 			return true;
 
 		fade->alpha -= 10;
-		texture_set_alpha_mod(ch->sprites[CHARACTER_SPRITE_NORMAL]->texture, fade->alpha);
 	}
 
 	return false;
@@ -63,8 +62,11 @@
 fadeout_draw(struct action *act)
 {
 	const struct fadeout *fade = act->data;
+	struct sprite *sprite = fade->ch->sprites[CHARACTER_SPRITE_NORMAL];
 
-	sprite_draw(fade->ch->sprites[CHARACTER_SPRITE_NORMAL], 0, 0, fade->x, fade->y);
+	texture_set_alpha_mod(sprite->texture, fade->alpha);
+	sprite_draw(sprite, 0, 0, fade->x, fade->y);
+	texture_set_alpha_mod(sprite->texture, 255);
 }
 
 static void
--- a/libmlk-rpg/rpg/battle-state-menu.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/battle-state-menu.c	Thu Jan 07 15:52:56 2021 +0100
@@ -35,17 +35,12 @@
 static void
 open_attack(struct battle *bt)
 {
-	unsigned int selection = 0;
+	struct selection slt = {
+		.allowed_sides = SELECTION_SIDE_ENEMY
+	};
 
-	/* Find first enemy to attack. */
-	for (size_t i = 0; i < BATTLE_ENEMY_MAX; ++i) {
-		if (character_ok(bt->enemies[i].ch)) {
-			selection = i;
-			break;
-		}
-	}
-
-	battle_state_selection(bt, SELECTION_ENEMY_ONE, selection);
+	selection_first(&slt, bt);
+	battle_state_selection(bt, &slt);
 }
 
 static void
--- a/libmlk-rpg/rpg/battle-state-selection.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/battle-state-selection.c	Thu Jan 07 15:52:56 2021 +0100
@@ -18,6 +18,7 @@
 
 #include <assert.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include <core/alloc.h>
 #include <core/event.h>
@@ -35,28 +36,49 @@
 
 struct select {
 	struct battle_state state;
-	enum selection type;
-	unsigned int selection;
+	struct selection slt;
 };
 
 static void
-select_adj_in(struct select *select, const struct battle_entity entities[], size_t entitiesz, int step)
+attack(struct select *select, struct battle *bt)
 {
-	assert(select->selection != (unsigned int)-1);
+	struct character *target;
+
+	if (select->slt.index_side == 0)
+		target = bt->enemies[select->slt.index_character].ch;
+	else
+		target = bt->team[select->slt.index_character].ch;
+
+	battle_attack(bt, bt->order_cur->ch, target);
+}
 
-	unsigned int newselection = select->selection;
+static void
+cast(struct select *select, struct battle *bt)
+{
+	struct character *source = bt->order_cur->ch;
+	const struct spell *spell = source->spells[bt->bar.sub_grid.selected];
+
+	battle_cast(bt, source, spell, &select->slt);
+}
+
+static void
+select_adj_in(struct select *select, const struct battle_entity *entities, size_t entitiesz, int step)
+{
+	assert(select->slt.index_character != (unsigned int)-1);
+
+	unsigned int newselection = select->slt.index_character;
 
 	if (step < 0) {
 		while (newselection > 0) {
 			if (character_ok(entities[--newselection].ch)) {
-				select->selection = newselection;
+				select->slt.index_character = newselection;
 				break;
 			}
 		}
 	} else {
 		while (newselection < entitiesz) {
 			if (character_ok(entities[++newselection].ch)) {
-				select->selection = newselection;
+				select->slt.index_character = newselection;
 				break;
 			}
 		}
@@ -66,17 +88,10 @@
 static void
 select_adj(struct select *select, const struct battle *bt, int step)
 {
-	switch (select->type) {
-	case SELECTION_TEAM_ONE:
-	case SELECTION_TEAM_COMBINED:
+	if (select->slt.index_side == 0)
+		select_adj_in(select, bt->enemies, UTIL_SIZE(bt->enemies), step);
+	else
 		select_adj_in(select, bt->team, UTIL_SIZE(bt->team), step);
-		break;
-	case SELECTION_ENEMY_ONE:
-	case SELECTION_ENEMY_COMBINED:
-		select_adj_in(select, bt->enemies, UTIL_SIZE(bt->enemies), step);
-	default:
-		break;
-	}
 }
 
 static void
@@ -85,33 +100,49 @@
 	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);
+		switch (bt->bar.menu) {
+		case BATTLE_BAR_MENU_MAGIC:
+		case BATTLE_BAR_MENU_OBJECTS:
+			battle_state_sub(bt);
+			break;
+		default:
+			battle_state_menu(bt);
+			break;
+		}
 		break;
 	case KEY_ENTER:
 		switch (bt->bar.menu) {
 		case BATTLE_BAR_MENU_ATTACK:
-			battle_attack(bt, source, bt->enemies[select->selection].ch);
+			attack(select, bt);
 			break;
 		case BATTLE_BAR_MENU_MAGIC:
-			battle_cast(bt, source, sp, select->selection);
+			cast(select, bt);
 			break;
 		default:
 			break;
 		}
 		break;
 	case KEY_LEFT:
+		if (select->slt.allowed_sides & SELECTION_SIDE_ENEMY)
+			select->slt.index_side = 0;
+		break;
+	case KEY_RIGHT:
+		if (select->slt.allowed_sides & SELECTION_SIDE_TEAM)
+			select->slt.index_side = 1;
+		break;
 	case KEY_UP:
 		select_adj(select, bt, -1);
 		break;
-	case KEY_RIGHT:
 	case KEY_DOWN:
 		select_adj(select, bt, +1);
 		break;
+	case KEY_TAB:
+		if (select->slt.allowed_kinds == SELECTION_KIND_BOTH)
+			select->slt.index_character = -1;
+		break;
 	default:
 		break;
 	}
@@ -138,21 +169,16 @@
 
 static void
 draw_cursors(const struct battle_state *st,
-            const struct battle *bt,
-            const struct battle_entity entities[],
-            size_t entitiesz)
+             const struct battle *bt,
+             const struct battle_entity *entities,
+             size_t entitiesz)
 {
-	const struct select *select = st->data;
+	for (size_t i = 0; i < entitiesz; ++i) {
+		const struct battle_entity *et = &entities[i];
 
-	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]);
+		if (character_ok(et->ch))
+			draw_cursor(bt, et);
+	}
 }
 
 static void
@@ -174,24 +200,19 @@
 {
 	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, UTIL_SIZE(bt->enemies));
-		break;
-	case SELECTION_TEAM_ALL:
-	case SELECTION_TEAM_ONE:
-	case SELECTION_TEAM_COMBINED:
-		draw_cursors(st, bt, bt->team, UTIL_SIZE(bt->team));
-		break;
-	default:
-		break;
+	if (select->slt.index_character == -1) {
+		/* All selected. */
+		if (select->slt.index_side == 0)
+			draw_cursors(st, bt, bt->enemies, UTIL_SIZE(bt->enemies));
+		else
+			draw_cursors(st, bt, bt->team, UTIL_SIZE(bt->team));
+	} else {
+		/* Select one. */
+		if (select->slt.index_side == 0)
+			draw_cursor(bt, &bt->enemies[select->slt.index_character]);
+		else
+			draw_cursor(bt, &bt->team[select->slt.index_character]);
 	}
-
 }
 
 static void
@@ -203,9 +224,7 @@
 }
 
 void
-battle_state_selection(struct battle *bt,
-		       enum selection type,
-		       unsigned int selection)
+battle_state_selection(struct battle *bt, const struct selection *slt)
 {
 	assert(bt);
 
@@ -214,12 +233,11 @@
 	if (!(select = alloc_new0(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;
+	memcpy(&select->slt, slt, sizeof (*slt));
 
 	battle_switch(bt, &select->state);
 }
--- a/libmlk-rpg/rpg/battle-state-sub.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/battle-state-sub.c	Thu Jan 07 15:52:56 2021 +0100
@@ -36,46 +36,18 @@
 {
 	const struct character *ch = bt->order_cur->ch;
 	const struct spell *sp = ch->spells[bt->bar.sub_grid.selected];
-	unsigned int selection = 0;
+	struct selection slt = {0};
 
 	/* Don't forget to reset the gridmenu state. */
 	gridmenu_reset(&bt->bar.sub_grid);
 
-	if (!sp || sp->mp > (unsigned int)(ch->mp))
+	if (bt->bar.sub_grid.selected > CHARACTER_SPELL_MAX)
+		return;
+	if (!(sp = ch->spells[bt->bar.sub_grid.selected]) || 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);
+	spell_select(sp, bt, &slt);
+	battle_state_selection(bt, &slt);
 
 	/* A cursor should be present. */
 	if (!sprite_ok(BATTLE_THEME(bt)->sprites[THEME_SPRITE_CURSOR]))
--- a/libmlk-rpg/rpg/battle-state.h	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/battle-state.h	Thu Jan 07 15:52:56 2021 +0100
@@ -86,9 +86,7 @@
 battle_state_opening(struct battle *bt);
 
 void
-battle_state_selection(struct battle *bt,
-		       enum selection type,
-		       unsigned int selection);
+battle_state_selection(struct battle *bt, const struct selection *slt);
 
 void
 battle_state_sub(struct battle *bt);
--- a/libmlk-rpg/rpg/battle.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/battle.c	Thu Jan 07 15:52:56 2021 +0100
@@ -290,7 +290,7 @@
 battle_cast(struct battle *bt,
             struct character *source,
             const struct spell *spell,
-            unsigned int selection)
+            const struct selection *selection)
 {
 	assert(bt);
 	assert(source);
@@ -314,12 +314,18 @@
 		battle_order(bt);
 		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]) {
+		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) {
 			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. */
--- a/libmlk-rpg/rpg/battle.h	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/battle.h	Thu Jan 07 15:52:56 2021 +0100
@@ -38,6 +38,7 @@
 struct character;
 struct inventory;
 struct music;
+struct selection;
 struct spell;
 struct theme;
 
@@ -100,7 +101,7 @@
 battle_cast(struct battle *bt,
             struct character *source,
             const struct spell *spell,
-            unsigned int selection);
+            const struct selection *slt);
 
 void
 battle_indicator_hp(struct battle *bt, const struct character *target, unsigned int amount);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/rpg/selection.c	Thu Jan 07 15:52:56 2021 +0100
@@ -0,0 +1,84 @@
+/*
+ * selection.c -- 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.
+ */
+
+#include <assert.h>
+
+#include <core/util.h>
+
+#include "battle.h"
+#include "character.h"
+#include "selection.h"
+
+static void
+random(struct selection *slt, const struct battle *bt, const struct battle_entity *entities, size_t entitiesz)
+{
+	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;
+}
+
+static void
+first(struct selection *slt, const struct battle *bt, const struct battle_entity *entities, size_t entitiesz)
+{
+	for (size_t i = 0; i < entitiesz; ++i) {
+		if (battle_entity_ok(&entities[i])) {
+			slt->index_character = i;
+			break;
+		}
+	}
+}
+
+void
+selection_first(struct selection *slt, const struct battle *bt)
+{
+	assert(slt);
+	assert(bt);
+
+	if (slt->index_side == 0)
+		first(slt, bt, bt->enemies, BATTLE_ENEMY_MAX);
+	else
+		first(slt, bt, bt->team, BATTLE_TEAM_MAX);
+}
+
+void
+selection_random(struct selection *slt, const struct battle *bt)
+{
+	assert(slt);
+	assert(bt);
+
+	if (slt->index_side == 0)
+		random(slt, bt, bt->enemies, BATTLE_ENEMY_MAX);
+	else
+		random(slt, bt, bt->team, BATTLE_TEAM_MAX);
+}
--- a/libmlk-rpg/rpg/selection.h	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/selection.h	Thu Jan 07 15:52:56 2021 +0100
@@ -19,17 +19,36 @@
 #ifndef MOLKO_RPG_SELECTION_H
 #define MOLKO_RPG_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. */
+struct battle;
+
+enum selection_kind {
+	SELECTION_KIND_SELF,
+	SELECTION_KIND_ONE,
+	SELECTION_KIND_ALL,
+	SELECTION_KIND_BOTH
+};
+
+enum selection_side {
+	/* Which side allowed (can be both). */
+	SELECTION_SIDE_TEAM     = (1 << 0),
+	SELECTION_SIDE_ENEMY    = (1 << 1)
 };
 
+struct selection {
+	enum selection_kind allowed_kinds;
+	enum selection_side allowed_sides;
+	
+	/* Character index in battle entity array. */
+	unsigned int index_character;
+
+	/* Side index (0 = enemy, 1 = team). */
+	unsigned int index_side;
+};
+
+void
+selection_first(struct selection *, const struct battle *);
+
+void
+selection_random(struct selection *, const struct battle *);
+
 #endif /* !MOLKO_RPG_SELECTION_H */
--- a/libmlk-rpg/rpg/spell.c	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/spell.c	Thu Jan 07 15:52:56 2021 +0100
@@ -21,21 +21,32 @@
 #include "spell.h"
 
 void
-spell_action(const struct spell *s, struct battle *bt, struct character *owner, unsigned int selection)
+spell_select(const struct spell *s, const struct battle *bt, struct selection *slt)
+{
+	assert(s && s->select);
+	assert(bt);
+	assert(slt);
+
+	s->select(bt, slt);
+}
+
+void
+spell_action(const struct spell *s, struct battle *bt, struct character *owner, const struct selection *slt)
 {
 	assert(s && s->action);
 	assert(bt);
 	assert(owner);
+	assert(slt);
 
-	s->action(bt, owner, selection);
+	s->action(bt, owner, slt);
 }
 
 void
-spell_use(struct spell *s, struct character *owner, int selection)
+spell_use(struct spell *s, struct character *owner, const struct selection *slt)
 {
 	assert(s && s->use);
 	assert(owner);
+	assert(slt);
 
-	if (s->use)
-		s->use(owner, selection);
+	s->use(owner, slt);
 }
--- a/libmlk-rpg/rpg/spell.h	Thu Jan 07 15:50:01 2021 +0100
+++ b/libmlk-rpg/rpg/spell.h	Thu Jan 07 15:52:56 2021 +0100
@@ -28,81 +28,39 @@
 
 struct character;
 struct battle;
+struct selection;
 
-/**
- * \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. */
+	SPELL_TYPE_NEUTRAL,
+	SPELL_TYPE_FIRE,
+	SPELL_TYPE_WIND,
+	SPELL_TYPE_WATER,
+	SPELL_TYPE_EARTH,
+	SPELL_TYPE_CHAOS,
+	SPELL_TYPE_HOLY,
+	SPELL_TYPE_TIME
 };
 
-/**
- * \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. */
+	const char *name;
+	const char *description;
+	unsigned int mp;
+	enum spell_type type;
+	enum selection_kind select_kind;
+	enum selection_side select_side;
 
-	/**
-	 * (+) 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);
+	void (*select)(const struct battle *, struct selection *);
+	void (*action)(struct battle *, struct character *, const struct selection *);
+	void (*use)(struct character *, const struct 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_select(const struct spell *s, const struct battle *bt, struct selection *slt);
+
 void
-spell_action(const struct spell *s, struct battle *bt, struct character *owner, unsigned int selection);
+spell_action(const struct spell *s, struct battle *bt, struct character *owner, const struct selection *slt);
 
-/**
- * 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);
+spell_use(struct spell *s, struct character *owner, const struct selection *slt);
 
 #endif /* !MOLKO_RPG_SPELL_H */