changeset 286:3991779aaba9

adventure: initial test of spawn
author David Demelier <markand@malikania.fr>
date Wed, 23 Dec 2020 15:37:48 +0100
parents c43e39745cd8
children 75d2fdf96064
files libmlk-adventure/CMakeLists.txt libmlk-adventure/adventure/action/spawner.c libmlk-adventure/adventure/adventure_p.h libmlk-adventure/adventure/assets.c libmlk-adventure/adventure/assets.h libmlk-adventure/adventure/character/black-cat.c libmlk-adventure/adventure/character/black-cat.h libmlk-adventure/adventure/character/neth.c libmlk-adventure/adventure/character/neth.h libmlk-adventure/adventure/dialog/save.c libmlk-adventure/adventure/item/potion.c libmlk-adventure/adventure/item/potion.h libmlk-adventure/adventure/molko.c libmlk-adventure/adventure/molko.h libmlk-adventure/adventure/state/battle.c libmlk-adventure/adventure/state/battle.h libmlk-adventure/adventure/state/mainmenu.c libmlk-adventure/nls/fr.po libmlk-adventure/nls/libmlk-adventure.pot libmlk-data/maps/map-world.json libmlk-data/sounds/potion.wav libmlk-rpg/CMakeLists.txt libmlk-rpg/nls/libmlk-rpg.pot libmlk-rpg/rpg/battle-bar.c libmlk-rpg/rpg/battle-bar.h libmlk-rpg/rpg/battle-state-menu.c libmlk-rpg/rpg/battle.c libmlk-rpg/rpg/battle.h libmlk-rpg/rpg/character.c libmlk-rpg/rpg/character.h libmlk-rpg/rpg/inventory.c libmlk-rpg/rpg/inventory.h libmlk-rpg/rpg/item.c libmlk-rpg/rpg/item.h
diffstat 34 files changed, 749 insertions(+), 400 deletions(-) [+]
line wrap: on
line diff
--- a/libmlk-adventure/CMakeLists.txt	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/CMakeLists.txt	Wed Dec 23 15:37:48 2020 +0100
@@ -29,12 +29,22 @@
 	${libadventure_SOURCE_DIR}/adventure/adventure_p.h
 	${libadventure_SOURCE_DIR}/adventure/assets.c
 	${libadventure_SOURCE_DIR}/adventure/assets.h
+	${libadventure_SOURCE_DIR}/adventure/character/black-cat.c
+	${libadventure_SOURCE_DIR}/adventure/character/black-cat.h
+	${libadventure_SOURCE_DIR}/adventure/character/neth.c
+	${libadventure_SOURCE_DIR}/adventure/character/neth.h
 	${libadventure_SOURCE_DIR}/adventure/dialog/save.c
 	${libadventure_SOURCE_DIR}/adventure/dialog/save.h
+	${libadventure_SOURCE_DIR}/adventure/item/potion.c
+	${libadventure_SOURCE_DIR}/adventure/item/potion.h
 	${libadventure_SOURCE_DIR}/adventure/mapscene/mapscene.c
 	${libadventure_SOURCE_DIR}/adventure/mapscene/mapscene.h
 	${libadventure_SOURCE_DIR}/adventure/molko.c
 	${libadventure_SOURCE_DIR}/adventure/molko.h
+	${libadventure_SOURCE_DIR}/adventure/state/battle.c
+	${libadventure_SOURCE_DIR}/adventure/state/battle.h
+	${libadventure_SOURCE_DIR}/adventure/state/continue.c
+	${libadventure_SOURCE_DIR}/adventure/state/continue.h
 	${libadventure_SOURCE_DIR}/adventure/state/mainmenu.c
 	${libadventure_SOURCE_DIR}/adventure/state/mainmenu.h
 	${libadventure_SOURCE_DIR}/adventure/state/map.c
@@ -43,8 +53,6 @@
 	${libadventure_SOURCE_DIR}/adventure/state/panic.h
 	${libadventure_SOURCE_DIR}/adventure/state/splashscreen.c
 	${libadventure_SOURCE_DIR}/adventure/state/splashscreen.h
-	${libadventure_SOURCE_DIR}/adventure/state/continue.c
-	${libadventure_SOURCE_DIR}/adventure/state/continue.h
 	${libadventure_SOURCE_DIR}/adventure/trace_hud.c
 	${libadventure_SOURCE_DIR}/adventure/trace_hud.h
 )
--- a/libmlk-adventure/adventure/action/spawner.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/adventure/action/spawner.c	Wed Dec 23 15:37:48 2020 +0100
@@ -24,8 +24,13 @@
 #include <core/game.h>
 #include <core/util.h>
 
+#include <rpg/battle.h>
 #include <rpg/map.h>
 
+#include <adventure/molko.h>
+
+#include <adventure/character/black-cat.h>
+
 #include "spawner.h"
 
 static inline unsigned int
@@ -39,6 +44,24 @@
 	return fmin(s->steps, gap_x + gap_y);
 }
 
+static void
+fight(void)
+{
+	/* TODO: */
+	struct battle *bt;
+
+	bt = alloc_new0(sizeof (*bt));
+	bt->enemies[0].ch = &character_black_cat;
+	bt->enemies[0].x = 400;
+	bt->enemies[0].y = 50;
+	bt->inventory = &molko.inventory;
+
+	for (size_t i = 0; i < TEAM_MEMBER_MAX; ++i)
+		bt->team[i].ch = molko.team.members[i];
+
+	molko_fight(bt);
+}
+
 static bool
 update(struct action *act, unsigned int ticks)
 {
@@ -53,9 +76,7 @@
 
 		if (s->steps == 0) {
 			s->steps = util_nrand(s->low, s->high);
-
-			/* TODO: start battle here. */
-			return false;
+			fight();
 		}
 	}
 
@@ -80,5 +101,7 @@
 	s->action.data = s;
 	s->action.update = update;
 
+	spawner_init(s);
+
 	return &s->action;
 }
--- a/libmlk-adventure/adventure/adventure_p.h	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/adventure/adventure_p.h	Wed Dec 23 15:37:48 2020 +0100
@@ -23,9 +23,11 @@
 
 #if defined(MOLKO_WITH_NLS)
 #       include <libintl.h>
-#       define _(s) dgettext("libmlk-adventure", s)
+#       define _(s)     dgettext("libmlk-adventure", s)
+#       define N_(s)    s
 #else
-#       define _(s) s
+#       define _(s)     s
+#       define N_(s)    s
 #endif
 
 #endif /* !MOLKO_ADVENTURE_ADVENTURE_P_H */
--- a/libmlk-adventure/adventure/assets.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/adventure/assets.c	Wed Dec 23 15:37:48 2020 +0100
@@ -18,15 +18,19 @@
 
 #include <core/image.h>
 #include <core/panic.h>
+#include <core/sound.h>
 #include <core/sprite.h>
 #include <core/texture.h>
 #include <core/util.h>
 
+#include <ui/theme.h>
+
 #include <adventure/molko.h>
 
 #include "assets.h"
 
-#define SPRITE(which, file, w, h) { which, file, w, h }
+#define SPRITE(which, file, w, h)       { which, file, w, h }
+#define SOUND(which, file)              { which, file }
 
 static struct {
 	enum assets_sprite index;
@@ -34,14 +38,24 @@
 	unsigned int cellw;
 	unsigned int cellh;
 	struct texture texture;
-	struct sprite sprite;
 } table_sprites[] = {
 	SPRITE(ASSETS_SPRITE_UI_CURSOR, "sprites/ui-cursor.png", 24, 24),
 	SPRITE(ASSETS_SPRITE_CHEST, "sprites/chest.png", 32, 32),
+	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)
 };
 
-struct sprite *assets_sprites[ASSETS_SPRITE_NUM] = {0};
+static struct {
+	enum assets_sound index;
+	const char *path;
+} table_sounds[] = {
+	SOUND(ASSETS_SOUND_ITEM_POTION, "sounds/potion.wav")
+};
+
+struct sprite assets_sprites[ASSETS_SPRITE_NUM];
+struct sound assets_sounds[ASSETS_SOUND_NUM];
 
 static void
 init_sprites(void)
@@ -50,10 +64,18 @@
 		if (!image_open(&table_sprites[i].texture, molko_path(table_sprites[i].path)))
 			panic();
 
-		sprite_init(&table_sprites[i].sprite, &table_sprites[i].texture,
+		sprite_init(&assets_sprites[table_sprites[i].index],
+		    &table_sprites[i].texture,
 		    table_sprites[i].cellw, table_sprites[i].cellh);
+	}
+}
 
-		assets_sprites[table_sprites[i].index] = &table_sprites[i].sprite;
+static void
+init_sounds(void)
+{
+	for (size_t i = 0; i < UTIL_SIZE(assets_sounds); ++i) {
+		if (!sound_open(&assets_sounds[table_sounds[i].index], molko_path(table_sounds[i].path)))
+			panic();
 	}
 }
 
@@ -61,6 +83,10 @@
 assets_init(void)
 {
 	init_sprites();
+	init_sounds();
+
+	/* Prepare the theme. */
+	theme_default()->sprites[THEME_SPRITE_CURSOR] = &assets_sprites[ASSETS_SPRITE_UI_CURSOR];
 }
 
 void
@@ -68,4 +94,6 @@
 {
 	for (size_t i = 0; i < UTIL_SIZE(table_sprites); ++i)
 		texture_finish(&table_sprites[i].texture);
+	for (size_t i = 0; i < UTIL_SIZE(table_sounds); ++i)
+		sound_finish(&assets_sounds[i]);
 }
--- a/libmlk-adventure/adventure/assets.h	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/adventure/assets.h	Wed Dec 23 15:37:48 2020 +0100
@@ -19,7 +19,8 @@
 #ifndef MOLKO_ADVENTURE_ASSETS_H
 #define MOLKO_ADVENTURE_ASSETS_H
 
-struct sprite;
+#include <core/sound.h>
+#include <core/sprite.h>
 
 enum assets_sprite {
 	/* UI elements. */
@@ -28,13 +29,26 @@
 	/* Actions. */
 	ASSETS_SPRITE_CHEST,
 
+	/* Characters enemies. */
+	ASSETS_SPRITE_CHARACTER_BLACK_CAT,
+
 	/* Team assets. */
+	ASSETS_SPRITE_CHARACTER_NETH,
+	ASSETS_SPRITE_CHARACTER_NETH_SWORD,
 	ASSETS_SPRITE_FACES,
 
 	ASSETS_SPRITE_NUM
 };
 
-extern struct sprite *assets_sprites[ASSETS_SPRITE_NUM];
+enum assets_sound {
+	/* Items. */
+	ASSETS_SOUND_ITEM_POTION,
+
+	ASSETS_SOUND_NUM
+};
+
+extern struct sprite assets_sprites[ASSETS_SPRITE_NUM];
+extern struct sound assets_sounds[ASSETS_SOUND_NUM];
 
 void
 assets_init(void);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/character/black-cat.c	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,51 @@
+/*
+ * black-cat.c -- Black Cat enemy
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <rpg/battle.h>
+#include <rpg/character.h>
+
+#include <adventure/adventure_p.h>
+#include <adventure/assets.h>
+
+#include "black-cat.h"
+
+static void
+reset(struct character *ch)
+{
+	ch->hpmax = ch->hp = 126;
+	ch->mpmax = ch->mp = 12;
+	ch->atk = 10;
+	ch->def = 8;
+	ch->agt = 13;
+}
+
+static void
+exec(struct character *ch, struct battle *bt)
+{
+	battle_attack(bt, ch, NULL);
+}
+
+struct character character_black_cat = {
+	.name = N_("Black Cat"),
+	.level = 4,
+	.sprites = {
+		[CHARACTER_SPRITE_NORMAL] = &assets_sprites[ASSETS_SPRITE_CHARACTER_BLACK_CAT]
+	},
+	.reset = reset,
+	.exec = exec
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/character/black-cat.h	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,24 @@
+/*
+ * black-cat.h -- Black Cat enemy
+ *
+ * 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_CHARACTER_BLACK_CAT_H
+#define MOLKO_ADVENTURE_CHARACTER_BLACK_CAT_H
+
+extern struct character character_black_cat;
+
+#endif /* !MOLKO_ADVENTURE_CHARACTER_BLACK_CAT_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/character/neth.c	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,46 @@
+/*
+ * neth.c -- Neth
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <rpg/character.h>
+
+#include <adventure/adventure_p.h>
+#include <adventure/assets.h>
+
+#include "neth.h"
+
+static void
+reset(struct character *ch)
+{
+	/* TODO: compute hpmax given the level. */
+	ch->hpmax = 570;
+	ch->mpmax = 50;
+	ch->atk = 22;
+	ch->def = 19;
+	ch->agt = 16;
+	ch->luck = 3;
+}
+
+struct character character_neth = {
+	.name = N_("Neth"),
+	.level = 1,
+	.sprites = {
+		[CHARACTER_SPRITE_NORMAL] = &assets_sprites[ASSETS_SPRITE_CHARACTER_NETH],
+		[CHARACTER_SPRITE_SWORD] = &assets_sprites[ASSETS_SPRITE_CHARACTER_NETH_SWORD]
+	},
+	.reset = reset
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/character/neth.h	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,24 @@
+/*
+ * neth.h -- Neth
+ *
+ * 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_CHARACTER_NETH_H
+#define MOLKO_ADVENTURE_CHARACTER_NETH_H
+
+extern struct character character_neth;
+
+#endif /* !MOLKO_ADVENTURE_CHARACTER_NETH_H */
--- a/libmlk-adventure/adventure/dialog/save.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/adventure/dialog/save.c	Wed Dec 23 15:37:48 2020 +0100
@@ -131,7 +131,7 @@
 {
 	/* TODO: determine face. */
 	for (size_t f = 0; f < TEAM_MAX; ++f) {
-		sprite_scale(assets_sprites[ASSETS_SPRITE_FACES], 0, f,
+		sprite_scale(&assets_sprites[ASSETS_SPRITE_FACES], 0, f,
 		    geo->saves[i].faces[f].x,
 		    geo->saves[i].faces[f].y,
 		    geo->saves[i].faces[f].w,
@@ -194,7 +194,7 @@
 static void
 draw_cursor(const struct dialog_save *dlg, const struct geo *geo)
 {
-	const struct sprite *sprite =assets_sprites[ASSETS_SPRITE_UI_CURSOR];
+	const struct sprite *sprite = &assets_sprites[ASSETS_SPRITE_UI_CURSOR];
 	const int x = geo->saves[dlg->selected].x - sprite->cellw;
 	const int y = geo->saves[dlg->selected].y;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/item/potion.c	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,42 @@
+/*
+ * potion.h -- give some heal points
+ *
+ * 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 <core/sound.h>
+
+#include <rpg/character.h>
+#include <rpg/item.h>
+
+#include <adventure/adventure_p.h>
+#include <adventure/assets.h>
+
+#include "potion.h"
+
+static void
+exec(const struct item *i, struct character *ch)
+{
+	sound_play(&assets_sounds[ASSETS_SOUND_ITEM_POTION], -1, 0);
+	ch->hp = fmin(ch->hp + 50, ch->hpmax);
+}
+
+const struct item item_potion = {
+	.name = N_("Potion"),
+	.description = N_("Recover 50 HP."),
+	.exec = exec
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/item/potion.h	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,24 @@
+/*
+ * potion.h -- give some heal points
+ *
+ * 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_ITEM_POTION_H
+#define MOLKO_ADVENTURE_ITEM_POTION_H
+
+extern const struct item item_potion;
+
+#endif /* !MOLKO_ADVENTURE_ITEM_POTION_H */
--- a/libmlk-adventure/adventure/molko.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/adventure/molko.c	Wed Dec 23 15:37:48 2020 +0100
@@ -37,6 +37,7 @@
 
 #include <rpg/rpg.h>
 
+#include <adventure/state/battle.h>
 #include <adventure/state/panic.h>
 #include <adventure/state/splashscreen.h>
 #include <adventure/state/mainmenu.h>
@@ -113,10 +114,20 @@
 void
 molko_teleport(const char *map, int origin_x, int origin_y)
 {
+	molko.state = MOLKO_STATE_MAP;
+
 	game_switch(state_map_new(map, origin_x, origin_y), false);
 	game.inhibit = INHIBIT_NONE;
 }
 
+void
+molko_fight(struct battle *bt)
+{
+	molko.state = MOLKO_STATE_BATTLE;
+
+	game_switch(state_battle_new(bt), false);
+}
+
 const char *
 molko_path(const char *file)
 {
--- a/libmlk-adventure/adventure/molko.h	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/adventure/molko.h	Wed Dec 23 15:37:48 2020 +0100
@@ -23,9 +23,25 @@
 #include <core/texture.h>
 #include <core/sprite.h>
 
+#include <rpg/inventory.h>
+#include <rpg/team.h>
+
+struct battle;
+
+enum molko_state {
+	MOLKO_STATE_MAP,
+	MOLKO_STATE_BATTLE,
+};
+
 struct molko {
 	struct state *panic;
 
+	/* Make sure to set this accordingly when changing states. */
+	enum molko_state state;
+
+	struct team team;
+	struct inventory inventory;
+
 	/* For map state. */
 	struct texture map_player_texture;
 	struct sprite map_player_sprite;
@@ -42,6 +58,9 @@
 void
 molko_teleport(const char *map, int origin_x, int origin_y);
 
+void
+molko_fight(struct battle *bt);
+
 const char *
 molko_path(const char *file);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/state/battle.c	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,101 @@
+/*
+ * battle.c -- manage a battle
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <core/alloc.h>
+#include <core/game.h>
+#include <core/painter.h>
+#include <core/state.h>
+
+#include <rpg/battle.h>
+
+#include "battle.h"
+
+struct self {
+	struct battle *battle;
+	struct state state;
+};
+
+static void
+start(struct state *state)
+{
+	struct self *self = state->data;
+
+	battle_start(self->battle);
+}
+
+static void
+handle(struct state *state, const union event *ev)
+{
+	struct self *self = state->data;
+
+	battle_handle(self->battle, ev);
+}
+
+static void
+update(struct state *state, unsigned int ticks)
+{
+	struct self *self = state->data;
+
+	/* TODO: once we have stacked states, pop it. */
+	if (battle_update(self->battle, ticks))
+		game_quit();
+}
+
+static void
+draw(struct state *state)
+{
+	struct self *self = state->data;
+
+	painter_set_color(0xffffffff);
+	painter_clear();
+	battle_draw(self->battle);
+	painter_present();
+}
+
+static void
+finish(struct state *state)
+{
+	struct self *self = state->data;
+
+	battle_finish(self->battle);
+
+	free(self->battle);
+	free(self);
+}
+
+struct state *
+state_battle_new(struct battle *bt)
+{
+	assert(bt);
+
+	struct self *self;
+
+	self = alloc_new0(sizeof (*self));
+	self->battle = bt;
+	self->state.data = self;
+	self->state.start = start;
+	self->state.handle = handle;
+	self->state.update = update;
+	self->state.draw = draw;
+	self->state.finish = finish;
+
+	return &self->state;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-adventure/adventure/state/battle.h	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,28 @@
+/*
+ * battle.h -- manage a battle
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MOLKO_ADVENTURE_STATE_BATTLE_H
+#define MOLKO_ADVENTURE_STATE_BATTLE_H
+
+struct battle;
+struct state;
+
+struct state *
+state_battle_new(struct battle *);
+
+#endif /* !MOLKO_ADVENTURE_STATE_BATTLE_H */
--- a/libmlk-adventure/adventure/state/mainmenu.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/adventure/state/mainmenu.c	Wed Dec 23 15:37:48 2020 +0100
@@ -36,10 +36,14 @@
 #include <ui/label.h>
 #include <ui/theme.h>
 
+#include <rpg/character.h>
+
 #include <adventure/assets.h>
 #include <adventure/molko.h>
 #include <adventure/adventure_p.h>
 
+#include <adventure/character/neth.h>
+
 #include "mainmenu.h"
 #include "continue.h"
 
@@ -58,6 +62,11 @@
 static void
 new(void)
 {
+	/* TODO: temporary. */
+	molko.team.members[0] = &character_neth;
+	character_reset(molko.team.members[0]);
+	molko.team.members[0]->hp = molko.team.members[0]->hpmax;
+	molko.team.members[0]->mp = molko.team.members[0]->mpmax;
 	molko_teleport("maps/map-world.map", -1, -1);
 }
 
@@ -170,7 +179,7 @@
 draw(struct state *state)
 {
 	struct self *self = state->data;
-	struct sprite *cursor = assets_sprites[ASSETS_SPRITE_UI_CURSOR];
+	struct sprite *cursor = &assets_sprites[ASSETS_SPRITE_UI_CURSOR];
 	int x, y;
 
 	painter_set_color(0xffffffff);
--- a/libmlk-adventure/nls/fr.po	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/nls/fr.po	Wed Dec 23 15:37:48 2020 +0100
@@ -15,13 +15,12 @@
 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
-
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-11-28 21:19+0100\n"
+"POT-Creation-Date: 2020-12-22 10:59+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -30,14 +29,32 @@
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:119
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:107
 msgid "Continue"
 msgstr "Continuer"
 
-#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:118
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/dialog/save.c:162
+#, c-format
+msgid "Last played: %s"
+msgstr ""
+
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:106
 msgid "New"
 msgstr "Nouvelle partie"
 
-#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:120
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/item/potion.c:36
+msgid "Potion"
+msgstr ""
+
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:108
 msgid "Quit"
 msgstr "Quitter"
+
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/item/potion.c:37
+msgid "Recover 50 HP."
+msgstr ""
+
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/dialog/save.c:168
+#, c-format
+msgid "Time played: %s"
+msgstr ""
--- a/libmlk-adventure/nls/libmlk-adventure.pot	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-adventure/nls/libmlk-adventure.pot	Wed Dec 23 15:37:48 2020 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-11-30 09:48+0100\n"
+"POT-Creation-Date: 2020-12-22 10:59+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,14 +17,32 @@
 "Content-Type: text/plain; charset=CHARSET\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:119
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:107
 msgid "Continue"
 msgstr ""
 
-#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:118
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/dialog/save.c:162
+#, c-format
+msgid "Last played: %s"
+msgstr ""
+
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:106
 msgid "New"
 msgstr ""
 
-#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:120
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/item/potion.c:36
+msgid "Potion"
+msgstr ""
+
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/state/mainmenu.c:108
 msgid "Quit"
 msgstr ""
+
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/item/potion.c:37
+msgid "Recover 50 HP."
+msgstr ""
+
+#: /Users/markand/Dev/molko/libmlk-adventure/adventure/dialog/save.c:168
+#, c-format
+msgid "Time played: %s"
+msgstr ""
--- a/libmlk-data/maps/map-world.json	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-data/maps/map-world.json	Wed Dec 23 15:37:48 2020 +0100
@@ -63,7 +63,7 @@
                         {
                          "name":"exec",
                          "type":"string",
-                         "value":"spawner"
+                         "value":"spawner|800|1000"
                         }],
                  "rotation":0,
                  "type":"",
Binary file libmlk-data/sounds/potion.wav has changed
--- a/libmlk-rpg/CMakeLists.txt	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/CMakeLists.txt	Wed Dec 23 15:37:48 2020 +0100
@@ -60,6 +60,8 @@
 	${libmlk-rpg_SOURCE_DIR}/rpg/character.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/equipment.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/equipment.h
+	${libmlk-rpg_SOURCE_DIR}/rpg/inventory.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/inventory.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/item.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/item.h
 	${libmlk-rpg_SOURCE_DIR}/rpg/map-file.c
--- a/libmlk-rpg/nls/libmlk-rpg.pot	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/nls/libmlk-rpg.pot	Wed Dec 23 15:37:48 2020 +0100
@@ -8,7 +8,7 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-20 10:24+0100\n"
+"POT-Creation-Date: 2020-12-22 10:58+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
--- a/libmlk-rpg/rpg/battle-bar.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/rpg/battle-bar.c	Wed Dec 23 15:37:48 2020 +0100
@@ -31,10 +31,26 @@
 #include "battle.h"
 #include "battle-bar.h"
 #include "character.h"
+#include "inventory.h"
+#include "item.h"
 #include "spell.h"
 #include "rpg_p.h"
 
 static void
+init_gridmenu(struct battle_bar *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
 draw_status_character_stats(const struct battle *bt,
                             const struct character *ch,
                             int x,
@@ -314,16 +330,7 @@
 void
 battle_bar_open_spells(struct battle_bar *bar, const struct battle *bt, struct character *ch)
 {
-	/* Sub menu bar on the left, take same space as status. */
-	bar->sub_grid.x = bar->x;
-	bar->sub_grid.y = bar->menu_frame.y;
-	bar->sub_grid.w = bar->status_frame.w;
-	bar->sub_grid.h = bar->h;
-	bar->sub_grid.theme = bt->theme;
-	bar->sub_grid.nrows = 3;
-	bar->sub_grid.ncols = 4;
-
-	memset(bar->sub_grid.menu, 0, sizeof (bar->sub_grid.menu));
+	init_gridmenu(bar, bt);
 
 	for (size_t i = 0; i < CHARACTER_SPELL_MAX; ++i) {
 		if (ch->spells[i])
@@ -336,6 +343,21 @@
 }
 
 void
+battle_bar_open_items(struct battle_bar *bar, const struct battle *bt)
+{
+	init_gridmenu(bar, bt);
+
+	for (size_t i = 0; i < INVENTORY_ITEM_MAX; ++i) {
+		if (bt->inventory->items[i].item)
+			bar->sub_grid.menu[i] = bt->inventory->items[i].item->name;
+	}
+
+	gridmenu_repaint(&bar->sub_grid);
+
+	bar->state = BATTLE_BAR_STATE_SUB;
+}
+
+void
 battle_bar_draw(const struct battle_bar *bar, const struct battle *bt)
 {
 	assert(bar);
--- a/libmlk-rpg/rpg/battle-bar.h	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/rpg/battle-bar.h	Wed Dec 23 15:37:48 2020 +0100
@@ -19,12 +19,28 @@
 #ifndef MOLKO_RPG_BATTLE_BAR_H
 #define MOLKO_RPG_BATTLE_BAR_H
 
-/**
- * \file battle-bar.h
- * \brief Battle status bar and menu.
+/*
+ * The bar is split into three individual pieces.
+ *
+ * +------------+--------+------------+
+ * | Grid menu  |  Menu  |   Status   |
+ * +------------+--------+------------+
+ *
  *
- * This module is a view and user input for the game battle. It is used by the
- * \ref battle.h object.
+ * The left grid menu is only shown when member field state is set to \ref
+ * BATTLE_BAR_STATE_SUB, it is usually opened when user select a magic or an
+ * object.
+ *
+ * The menu in the middle is the main selection and contains the enumeration
+ * battle_bar_menu using top, right, bottom left edges for short selection.
+ *
+ * The last part on the right is the status pane where the team characters stats
+ * are listed.
+ *
+ * By itself, the bar never modify the battle object, it is the responsability
+ * of the battle object or the battle state to change the bar object and to
+ * track change on events. As such, the battle object is always passed as const
+ * parameter.
  */
 
 #include <stdbool.h>
@@ -37,155 +53,60 @@
 
 union event;
 
-/**
- * \brief Main menu selection.
- */
 enum battle_bar_menu {
-	BATTLE_BAR_MENU_ATTACK  = 0,    /*!< Attack. */
-	BATTLE_BAR_MENU_MAGIC   = 1,    /*!< Cast a spell. */
-	BATTLE_BAR_MENU_OBJECTS = 2,    /*!< Use an object*/
-	BATTLE_BAR_MENU_SPECIAL = 3     /*!< Use a special character action. */
+	BATTLE_BAR_MENU_ATTACK  = 0,
+	BATTLE_BAR_MENU_MAGIC   = 1,
+	BATTLE_BAR_MENU_OBJECTS = 2,
+	BATTLE_BAR_MENU_SPECIAL = 3
 };
 
-/**
- * \brief Bar state.
- */
 enum battle_bar_state {
-	BATTLE_BAR_STATE_NONE,          /*!< No state yet. */
-	BATTLE_BAR_STATE_MENU,          /*!< Main menu selection. */
-	BATTLE_BAR_STATE_SUB            /*!< Sub grid menu opened. */
+	BATTLE_BAR_STATE_NONE,
+	BATTLE_BAR_STATE_MENU,
+	BATTLE_BAR_STATE_SUB
 };
 
-/**
- * \brief Battle bar.
- *
- * The bar is split into three individual pieces.
- *
- * ```
- * +------------+--------+------------+
- * | Grid menu  |  Menu  |   Status   |
- * +------------+--------+------------+
- * ```
- *
- * The left grid menu is only shown when member field state is set to \ref
- * BATTLE_BAR_STATE_SUB, it is usually opened when user select a magic or an
- * object.
- *
- * The menu in the middle is the main selection and contains the enumeration
- * \ref battle_bar_menu using top, right, bottom left edges for short selection.
- *
- * The last part on the right is the status pane where the team characters stats
- * are listed.
- *
- * By itself, the bar never modify the battle object, it is the responsability
- * of the battle object or the battle state to change the bar object and to
- * track change on events. As such, the battle object is always passed as const
- * parameter.
- */
 struct battle_bar {
-	int x;                          /*!< (+) Position in x. */
-	int y;                          /*!< (+) Position in y. */
-	unsigned int w;                 /*!< (+) Width. */
-	unsigned int h;                 /*!< (+) Height. */
-	enum battle_bar_state state;    /*!< (-) Current state. */
+	int x;
+	int y;
+	unsigned int w;
+	unsigned int h;
+	enum battle_bar_state state;
 
 	/* Right status frame. */
-	struct frame status_frame;      /*!< (-) Right status frame. */
+	struct frame status_frame;
 
 	/* Main menu selection. */
-	struct frame menu_frame;        /*!< (-) Main menu frame. */
-	enum battle_bar_menu menu;      /*!< (-) Main menu selection. */
+	struct frame menu_frame;
+	enum battle_bar_menu menu;
 
 	/* Sub menu selection (spells/objects). */
-	struct gridmenu sub_grid;       /*!< (-) Sub menu object. */
+	struct gridmenu sub_grid;
 };
 
-/**
- * Repositionate every elements in the bar.
- *
- * Call this function at least once, when you have set the x, y, w and h fields
- * of the bar.
- *
- * \pre bar != NULL
- * \pre bt != NULL
- * \param bar the bar to set
- * \param bt the current battle
- */
 void
 battle_bar_positionate(struct battle_bar *bar, const struct battle *bt);
 
-/**
- * Handle an event.
- *
- * Depending on the current bar state, it will either update the main menu or
- * the sub menu if opened. The function returns true if an element is considered
- * activated (usually with the return key).
- *
- * You must not ignore the return value because the battle state should change.
- *
- * \pre bar != NULL
- * \pre bt != NULL
- * \pre ev != NULL
- * \param bar this bar
- * \param bt the current battle
- * \param ev the current event
- * \return True if an element has been activated.
- */
 bool
 battle_bar_handle(struct battle_bar *bar,
                   const struct battle *bt,
                   const union event *ev);
 
-/**
- * Reset the battle bar selection, state and grid.
- *
- * Call this function each time a new player is selected.
- *
- * \pre bar != NULL
- * \param bar the bar to reset
- */
 void
 battle_bar_reset(struct battle_bar *bar);
 
-/**
- * Open global menu.
- *
- * \pre bar != NULL
- * \param bar the bar
- */
 void
 battle_bar_open_menu(struct battle_bar *bar);
 
-/**
- * Open the view to select a spell by opening the grid menu.
- *
- * \pre bar != NULL
- * \pre bt != NULL
- * \pre character_ok(ch)
- * \param bar this bar
- * \param bt the current battle
- * \param ch the owner of spells
- */
 void
 battle_bar_open_spells(struct battle_bar *bar, const struct battle *bt, struct character *ch);
 
-/**
- * Draw the bar.
- *
- * \pre bar != NULL
- * \pre bt != NULL
- * \param bar the bar to draw
- * \param bt the current battle
- */
+void
+battle_bar_open_items(struct battle_bar *bar, const struct battle *bt);
+
 void
 battle_bar_draw(const struct battle_bar *bar, const struct battle *bt);
 
-/**
- * Close internal resources if necessary.
- *
- * \pre bar != NULL
- * \param bar the bar to clear
- */
 void
 battle_bar_finish(struct battle_bar *bar);
 
--- a/libmlk-rpg/rpg/battle-state-menu.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/rpg/battle-state-menu.c	Wed Dec 23 15:37:48 2020 +0100
@@ -49,6 +49,13 @@
 }
 
 static void
+open_items(struct battle *bt)
+{
+	battle_bar_open_items(&bt->bar, bt);
+	battle_state_sub(bt);
+}
+
+static void
 handle(struct battle_state *st, struct battle *bt, const union event *ev)
 {
 	(void)st;
@@ -62,6 +69,7 @@
 			open_spells(bt);
 			break;
 		case BATTLE_BAR_MENU_OBJECTS:
+			open_items(bt);
 			break;
 		case BATTLE_BAR_MENU_SPECIAL:
 			break;
--- a/libmlk-rpg/rpg/battle.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/rpg/battle.c	Wed Dec 23 15:37:48 2020 +0100
@@ -87,6 +87,21 @@
 	return NULL;
 }
 
+static struct battle_entity *
+random_select(struct battle_entity *group, size_t groupsz)
+{
+	struct battle_entity *ret = NULL, *et = NULL;
+
+	do {
+		et = &group[util_nrand(0, groupsz - 1)];
+
+		if (et->ch)
+			ret = et;
+	} while (!ret);
+
+	return ret;
+}
+
 static int
 cmp_order(const void *d1, const void *d2)
 {
@@ -97,10 +112,10 @@
 }
 
 static bool
-is_team(const struct battle *bt, const struct battle_entity *et)
+is_team(const struct battle *bt, const struct character *ch)
 {
 	for (size_t i = 0; i < BATTLE_TEAM_MAX; ++i)
-		if (&bt->team[i] == et)
+		if (bt->team[i].ch == ch)
 			return true;
 
 	return false;
@@ -257,11 +272,16 @@
               struct character *source,
               struct character *target)
 {
-	(void)source;
-
 	assert(bt);
 	assert(character_ok(source));
-	assert(character_ok(target));
+
+	/* Target is empty? select randomly. */
+	if (!target) {
+		if (is_team(bt, source))
+			target = random_select(bt->enemies, BATTLE_ENEMY_MAX)->ch;
+		else
+			target = random_select(bt->team, BATTLE_TEAM_MAX)->ch;
+	}
 
 	battle_state_attacking(bt, source, target);
 }
@@ -303,7 +323,7 @@
 	}
 
 	/* Change state depending on the kind of entity. */
-	if (is_team(bt, bt->order_cur)) {
+	if (is_team(bt, bt->order_cur->ch)) {
 		battle_bar_open_menu(&bt->bar);
 		battle_state_menu(bt);
 	} else
--- a/libmlk-rpg/rpg/battle.h	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/rpg/battle.h	Wed Dec 23 15:37:48 2020 +0100
@@ -36,14 +36,14 @@
 union event;
 
 struct character;
+struct inventory;
 struct music;
 struct spell;
 struct theme;
 
-#define BATTLE_TEAM_MAX         (4)     /*!< Maximum team members. */
-#define BATTLE_ENEMY_MAX        (8)     /*!< Maximum enemies. */
-#define BATTLE_ENTITY_MAX \
-	(BATTLE_TEAM_MAX + BATTLE_ENEMY_MAX)
+#define BATTLE_TEAM_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)
@@ -52,248 +52,68 @@
 
 #define BATTLE_THEME(bt) ((bt)->theme ? (bt)->theme : theme_default())
 
-/**
- * \brief Generic battle status.
- */
 enum battle_status {
-	BATTLE_STATUS_NONE,     /*!< Battle is not even started. */
-	BATTLE_STATUS_RUNNING,  /*!< Battle is running. */
-	BATTLE_STATUS_WON,      /*!< Battle has been won. */
-	BATTLE_STATUS_LOST,     /*!< Battle has been lost. */
+	BATTLE_STATUS_NONE,
+	BATTLE_STATUS_RUNNING,
+	BATTLE_STATUS_WON,
+	BATTLE_STATUS_LOST,
 };
 
-/**
- * \brief Battle structure.
- */
 struct battle {
-	/**
-	 * (+&?) Battle state.
-	 */
 	struct battle_state *state;
-
-	/**
-	 * (-) Battle status.
-	 *
-	 * This information is provided as final result once the battle has
-	 * completed, modifying by the user won't change the battle routines.
-	 */
 	enum battle_status status;
-
-	/**
-	 * (+) Member of team.
-	 */
 	struct battle_entity team[BATTLE_TEAM_MAX];
-
-	/**
-	 * (+) Ennemies to fight.
-	 */
 	struct battle_entity enemies[BATTLE_ENEMY_MAX];
-
-	/**
-	 * (+&?) Order of playing.
-	 */
 	struct battle_entity *order[BATTLE_ENTITY_MAX];
-
-	/**
-	 * (-&?) Current entity playing.
-	 */
 	struct battle_entity *order_cur;
-
-	/**
-	 * (-) Current entity playing index.
-	 */
 	size_t order_curindex;
-
-	/**
-	 * (+&?) An optional background.
-	 */
 	struct texture *background;
-
-	/**
-	 * (+&?) A music to play.
-	 *
-	 * Music to play in the battle:
-	 *
-	 * - [0]: while the battle is running,
-	 * - [1]: in case of victory,
-	 * - [2]: in case of lost.
-	 */
 	struct music *music[3];
-
-	/**
-	 * (+&?) Theme to use.
-	 */
 	struct theme *theme;
-
-	/**
-	 * (+) Stack of animations.
-	 *
-	 * The drawing animations are ran in parallel.
-	 */
 	struct drawable_stack effects;
-
-	/**
-	 * (+) Stack of actions.
-	 *
-	 * The array [0] contains actions that must complete to continue the
-	 * battle, it can be used to write some messages while blocking the
-	 * battle.
-	 *
-	 * The array [1] is a set of actions that run in "parallel" and don't
-	 * prevent the battle from continuing.
-	 */
 	struct action_stack actions[2];
-
-	/**
-	 * (-) Bottom bar.
-	 */
+	struct inventory *inventory;
 	struct battle_bar bar;
 };
 
-/**
- * Call this function before using the battle but after calling \a battle_init.
- *
- * If the team entities are positioned to 0, 0 they will be placed
- * automatically.
- *
- * \pre bt != NULL
- * \param bt the battle object
- */
 void
 battle_start(struct battle *bt);
 
-/**
- * Select next member in battle.
- *
- * \pre bt != NULL
- * \param bt the battle object
- */
 void
 battle_next(struct battle *bt);
 
 struct battle_entity *
 battle_find(struct battle *bt, const struct character *ch);
 
-/**
- * Change battle state.
- *
- * \pre bt != NULL
- * \pre st != NULL
- * \param bt the battle object
- * \param st the state (referenced)
- * \warning This will immediately close the current state and call finish field
- *          function if defined.
- */
 void
 battle_switch(struct battle *bt, struct battle_state *st);
 
-/**
- * Compute battle ordering.
- *
- * \pre bt != NULL
- * \param bt the battle object
- */
 void
 battle_order(struct battle *bt);
 
-/**
- * All in one function to animate and compute damage.
- *
- * Use this function from the character AI or when a character from the team
- * should attack an enemy.
- *
- * This will do the following in order:
- *
- * 1. Change battle state to "attacking"
- * 2. Change battle entity state to "moving" or "blinking" if source is member of
- *    team or enemies respectively.
- * 3. Wait until animations complete.
- * 4. Compute and produce damage.
- * 5. Change battle state to "check"
- *
- * \pre bt != NULL
- * \pre source != NULL
- * \pre target != NULL
- * \param bt the battle object
- * \param source the entity attacking
- * \param target the target entity
- */
 void
 battle_attack(struct battle *bt,
               struct character *source,
               struct character *target);
 
-/**
- * Default function to cast a spell.
- *
- * Prefer to use this function to cast a spell because it performs some checks
- * and hooks before casting the spell.
- *
- * \pre bt != NULL
- * \pre source != NULL
- * \pre spell != NULL
- * \param bt the current battle
- * \param source the entity casting a spell
- * \param spell the spell used
- * \param selection the selection
- */
 void
 battle_cast(struct battle *bt,
             struct character *source,
             const struct spell *spell,
             unsigned int selection);
 
-/**
- * Spawn an indicator drawable to show damage.
- *
- * The drawable will draw the amount near the entity and fade away after
- * several seconds.
- *
- * \pre bt != NULL
- * \pre target != NULL
- * \param bt the battle object
- * \param target the target entity
- * \param amount the amount of damage
- */
 void
 battle_indicator_hp(struct battle *bt, const struct character *target, unsigned int amount);
 
-/**
- * Handle an event.
- *
- * \pre bt != NULL
- * \pre ev != NULL
- * \param bt the battle object
- * \param ev the event
- */
 void
 battle_handle(struct battle *bt, const union event *ev);
 
-/**
- * Update the battle.
- *
- * \pre bt != NULL
- * \param bt the battle object
- * \param ticks the number of milliseconds since last frame
- */
 bool
 battle_update(struct battle *bt, unsigned int ticks);
 
-/**
- * Draw the battle.
- *
- * \pre bt != NULL
- * \param bt the battle object
- */
 void
 battle_draw(struct battle *bt);
 
-/**
- * Finish and dispose resources if necessary.
- *
- * \pre bt != NULL
- * \param bt the battle object
- */
 void
 battle_finish(struct battle *bt);
 
--- a/libmlk-rpg/rpg/character.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/rpg/character.c	Wed Dec 23 15:37:48 2020 +0100
@@ -30,7 +30,7 @@
 bool
 character_ok(const struct character *ch)
 {
-	return ch && ch->name && ch->type && ch->reset && sprite_ok(ch->sprites[CHARACTER_SPRITE_NORMAL]);
+	return ch && ch->name && ch->reset && sprite_ok(ch->sprites[CHARACTER_SPRITE_NORMAL]);
 }
 
 const char *
--- a/libmlk-rpg/rpg/character.h	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/rpg/character.h	Wed Dec 23 15:37:48 2020 +0100
@@ -58,9 +58,8 @@
 
 struct character {
 	const char *name;
-	const char *type;
+	enum character_status status;
 	unsigned int level;
-	enum character_status status;
 	int hp;
 	unsigned int hpmax;
 	unsigned int hpbonus;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/rpg/inventory.c	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,64 @@
+/*
+ * inventory.c -- item inventory
+ *
+ * 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 <stddef.h>
+
+#include "inventory.h"
+
+static struct inventory_slot *
+find(struct inventory *iv, const struct item *item)
+{
+	for (size_t i = 0; i < INVENTORY_ITEM_MAX; ++i)
+		if (iv->items[i].item == item)
+			return &iv->items[i];
+
+	return NULL;
+}
+
+bool
+inventory_add(struct inventory *iv, const struct item *item, unsigned int amount)
+{
+	assert(iv);
+	assert(item);
+
+	struct inventory_slot *slot;
+
+	/* Find one existing, otherwise find one empty. */
+	if (!(slot = find(iv, item)))
+		slot = find(iv, NULL);
+
+	if (!slot)
+		return false;
+
+	slot->amount += amount;
+
+	return true;
+}
+
+void
+inventory_consume(struct inventory *iv, const struct item *item, unsigned int amount)
+{
+	assert(iv);
+	assert(item);
+
+	struct inventory_slot *slot;
+
+	if (!(slot = find(iv, item)))
+		slot->amount -= amount;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/rpg/inventory.h	Wed Dec 23 15:37:48 2020 +0100
@@ -0,0 +1,43 @@
+/*
+ * inventory.h -- item inventory
+ *
+ * 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_RPG_INVENTORY_H
+#define MOLKO_RPG_INVENTORY_H
+
+#include <stdbool.h>
+
+#define INVENTORY_ITEM_MAX (512)
+
+struct item;
+
+struct inventory_slot {
+	unsigned int amount;
+	const struct item *item;
+};
+
+struct inventory {
+	struct inventory_slot items[INVENTORY_ITEM_MAX];
+};
+
+bool
+inventory_add(struct inventory *iv, const struct item *item, unsigned int amount);
+
+void
+inventory_consume(struct inventory *iv, const struct item *item, unsigned int amount);
+
+#endif /* !MOLKO_RPG_INVENTORY_H */
--- a/libmlk-rpg/rpg/item.c	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/rpg/item.c	Wed Dec 23 15:37:48 2020 +0100
@@ -21,7 +21,7 @@
 #include "item.h"
 
 void
-item_exec(struct item *item, struct character *ch)
+item_exec(const struct item *item, struct character *ch)
 {
 	assert(item);
 	assert(ch);
--- a/libmlk-rpg/rpg/item.h	Mon Dec 21 09:50:16 2020 +0100
+++ b/libmlk-rpg/rpg/item.h	Wed Dec 23 15:37:48 2020 +0100
@@ -19,61 +19,22 @@
 #ifndef MOLKO_RPG_ITEM_H
 #define MOLKO_RPG_ITEM_H
 
-/**
- * \file item.h
- * \brief Inventory items.
- */
-
 #include <stdbool.h>
 
 struct character;
 struct texture;
 
-/**
- * \brief Inventory items.
- */
 struct item {
-	const char *name;               /*!< (+) Name of item. */
-	const char *summary;            /*!< (+) Summary description. */
-	struct texture *icon;           /*!< (+&) Icon to show. */
-
-	/**
-	 * (+) Execute the action for this character.
-	 *
-	 * \param item this item
-	 * \param ch the character owner
-	 */
-	void (*exec)(struct item *item, struct character *ch);
-
-	/**
-	 * (+?) Tells if the item can be used in this context.
-	 *
-	 * \param item this item
-	 * \param ch the character owner
-	 */
+	const char *name;
+	const char *description;
+	struct texture *icon;
+	void (*exec)(const struct item *item, struct character *ch);
 	bool (*allowed)(const struct item *item, const struct character *ch);
 };
 
-/**
- * Shortcut for item->exec (if not NULL).
- *
- * \pre item != NULL
- * \pre ch != NULL
- * \param item the item to use
- * \param ch the character owner
- */
 void
-item_exec(struct item *item, struct character *ch);
+item_exec(const struct item *item, struct character *ch);
 
-/**
- * Shortcut for item->allowed (if not NULL).
- *
- * \pre item != NULL
- * \pre ch != NULL
- * \param item the item to use
- * \param ch the character owner
- * \return The return value of item->allowed or true if NULL.
- */
 bool
 item_allowed(const struct item *item, struct character *ch);