changeset 290:9948e288925b

rpg: add support for items in battle
author David Demelier <markand@malikania.fr>
date Fri, 08 Jan 2021 12:56:10 +0100
parents 63d9fb56c609
children 5d8700074dd7
files libmlk-adventure/adventure/item/potion.c libmlk-adventure/adventure/molko.c libmlk-adventure/adventure/state/mainmenu.c libmlk-rpg/CMakeLists.txt libmlk-rpg/rpg/battle-bar.c libmlk-rpg/rpg/battle-bar.h libmlk-rpg/rpg/battle-state-item.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/inventory.c libmlk-rpg/rpg/item.c libmlk-rpg/rpg/item.h
diffstat 16 files changed, 347 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- a/libmlk-adventure/adventure/item/potion.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-adventure/adventure/item/potion.c	Fri Jan 08 12:56:10 2021 +0100
@@ -20,6 +20,7 @@
 
 #include <core/sound.h>
 
+#include <rpg/battle.h>
 #include <rpg/character.h>
 #include <rpg/item.h>
 
@@ -29,14 +30,32 @@
 #include "potion.h"
 
 static void
-exec(const struct item *i, struct character *ch)
+heal(struct character *ch)
+{
+	ch->hp = fmin(ch->hp + 50, ch->hpmax);
+	sound_play(&assets_sounds[ASSETS_SOUND_ITEM_POTION], -1, 0);
+}
+
+static void
+exec_menu(const struct item *item, struct character *ch)
 {
-	sound_play(&assets_sounds[ASSETS_SOUND_ITEM_POTION], -1, 0);
-	ch->hp = fmin(ch->hp + 50, ch->hpmax);
+	(void)item;
+
+	heal(ch);
+}
+
+static void
+exec_battle(const struct item *item, struct battle *bt, struct character *src, struct character *tgt)
+{
+	(void)item;
+
+	heal(tgt);
+	battle_indicator_hp(bt, tgt, 50);
 }
 
 const struct item item_potion = {
 	.name = N_("Potion"),
 	.description = N_("Recover 50 HP."),
-	.exec = exec
+	.exec_menu = exec_menu,
+	.exec_battle = exec_battle
 };
--- a/libmlk-adventure/adventure/molko.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-adventure/adventure/molko.c	Fri Jan 08 12:56:10 2021 +0100
@@ -52,6 +52,7 @@
 
 
 #include "character/neth.h"
+#include "item/potion.h"
 
 static jmp_buf panic_buf;
 
@@ -97,6 +98,7 @@
 	game_switch(state_mainmenu_new(), true);
 	molko.team.members[0] = &character_neth;
 	molko.team.members[1] = &character_neth;
+	inventory_add(&molko.inventory, &item_potion, 100);
 	molko_teleport("maps/map-world.map", -1, -1);
 #endif
 }
--- a/libmlk-adventure/adventure/state/mainmenu.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-adventure/adventure/state/mainmenu.c	Fri Jan 08 12:56:10 2021 +0100
@@ -42,6 +42,7 @@
 #include <adventure/molko.h>
 #include <adventure/adventure_p.h>
 
+#include <adventure/item/potion.h>
 #include <adventure/character/neth.h>
 
 #include "mainmenu.h"
@@ -67,6 +68,8 @@
 	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;
+	inventory_add(&molko.inventory, &item_potion, 10);
+
 	molko_teleport("maps/map-world.map", -1, -1);
 }
 
--- a/libmlk-rpg/CMakeLists.txt	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/CMakeLists.txt	Fri Jan 08 12:56:10 2021 +0100
@@ -46,6 +46,7 @@
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-attacking.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-check.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-closing.c
+	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-item.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-lost.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-menu.c
 	${libmlk-rpg_SOURCE_DIR}/rpg/battle-state-opening.c
--- a/libmlk-rpg/rpg/battle-bar.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/battle-bar.c	Fri Jan 08 12:56:10 2021 +0100
@@ -323,6 +323,8 @@
 void
 battle_bar_open_menu(struct battle_bar *bar)
 {
+	gridmenu_reset(&bar->sub_grid);
+
 	bar->state = BATTLE_BAR_STATE_MENU;
 	bar->menu = BATTLE_BAR_MENU_ATTACK;
 }
@@ -348,8 +350,11 @@
 	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;
+		if (bt->inventory->items[i].item) {
+			snprintf(bar->sub_items[i], sizeof (bar->sub_items[i]), "%-16s %u",
+			    bt->inventory->items[i].item->name, bt->inventory->items[i].amount);
+			bar->sub_grid.menu[i] = bar->sub_items[i];
+		}
 	}
 
 	gridmenu_repaint(&bar->sub_grid);
--- a/libmlk-rpg/rpg/battle-bar.h	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/battle-bar.h	Fri Jan 08 12:56:10 2021 +0100
@@ -81,6 +81,7 @@
 	enum battle_bar_menu menu;
 
 	/* Sub menu selection (spells/objects). */
+	char sub_items[GRIDMENU_ENTRY_MAX][128];
 	struct gridmenu sub_grid;
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/rpg/battle-state-item.c	Fri Jan 08 12:56:10 2021 +0100
@@ -0,0 +1,158 @@
+/*
+ * battle-state-item.c -- battle state (using item)
+ *
+ * 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/panic.h>
+#include <core/window.h>
+
+#include <rpg/inventory.h>
+#include <rpg/item.h>
+
+#include <ui/align.h>
+#include <ui/frame.h>
+#include <ui/label.h>
+
+#include "battle-entity-state.h"
+#include "battle-state.h"
+#include "battle.h"
+
+enum substate {
+	SUBSTATE_ADVANCING,
+	SUBSTATE_MESSAGE,
+	SUBSTATE_RETURNING
+};
+
+struct msg {
+	struct frame frame;
+	struct label label;
+	unsigned int elapsed;
+};
+
+struct self {
+	enum substate substate;
+	struct msg msg;
+	struct battle_entity *source;
+	struct battle_entity *target;
+	struct battle_state state;
+	struct inventory_slot *slot;
+	int origin_x;
+};
+
+static bool
+update(struct battle_state *st, struct battle *bt, unsigned int ticks)
+{
+	struct self *self = st->data;
+
+	switch (self->substate) {
+	case SUBSTATE_ADVANCING:
+		/* Entity is updating from battle, so just inspect its status. */
+		if (battle_entity_update(self->source, 0)) {
+			self->substate = SUBSTATE_MESSAGE;
+			battle_entity_state_normal(self->source);
+		}
+		break;
+	case SUBSTATE_MESSAGE:
+		self->msg.elapsed += ticks;
+
+		if (self->msg.elapsed >= 2000) {
+			self->substate = SUBSTATE_RETURNING;
+			battle_entity_state_moving(self->source, self->origin_x, self->source->y);
+		}
+		break;
+	default:
+		if (battle_entity_update(self->source, 0)) {
+			battle_entity_state_normal(self->source);
+			battle_use(bt, self->slot->item, self->source->ch, self->target->ch);
+		}
+		break;
+	}
+
+	return false;
+}
+
+static void
+draw(const struct battle_state *st, const struct battle *bt)
+{
+	struct self *self = st->data;
+
+	if (self->substate == SUBSTATE_MESSAGE) {
+		frame_draw(&self->msg.frame);
+		label_draw(&self->msg.label);
+	}
+}
+
+static void
+finish(struct battle_state *st, struct battle *bt)
+{
+	(void)bt;
+
+	free(st->data);
+}
+
+void
+battle_state_item(struct battle *bt,
+                  struct character *source,
+                  struct character *target,
+                  struct inventory_slot *slot)
+{
+	assert(bt);
+	assert(source);
+	assert(target);
+	assert(slot);
+
+	struct self *self;
+	unsigned int lw, lh;
+
+	if (!(self = alloc_new0(sizeof (*self))))
+		panic();
+
+	self->source = battle_find(bt, source);
+	self->target = battle_find(bt, target);
+	self->slot = slot;
+	self->origin_x = self->source->x;
+
+	/* Prepare message frame. */
+	self->msg.frame.w = window.w / 3;
+	self->msg.frame.h = window.h / 15;
+	self->msg.frame.theme = bt->theme;
+
+	align(ALIGN_TOP,
+	    &self->msg.frame.x, &self->msg.frame.y, self->msg.frame.w, self->msg.frame.h,
+	    0, 20, window.w, window.h);
+
+	/* Prepare message label box. */
+	self->msg.label.text = slot->item->name;
+	self->msg.label.flags = LABEL_FLAGS_SHADOW;
+	self->msg.label.theme = bt->theme;
+	label_query(&self->msg.label, &lw, &lh);
+
+	align(ALIGN_CENTER,
+	    &self->msg.label.x, &self->msg.label.y, lw, lh,
+	    self->msg.frame.x, self->msg.frame.y, self->msg.frame.w, self->msg.frame.h);
+
+	self->state.data = self;
+	self->state.update = update;
+	self->state.draw = draw;
+	self->state.finish = finish;
+
+	battle_entity_state_moving(self->source, self->origin_x - 100, self->source->y);
+	battle_switch(bt, &self->state);
+}
--- a/libmlk-rpg/rpg/battle-state-menu.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/battle-state-menu.c	Fri Jan 08 12:56:10 2021 +0100
@@ -83,5 +83,6 @@
 		.handle = handle,
 	};
 
+	battle_bar_open_menu(&bt->bar);
 	battle_switch(bt, &self);
 }
--- a/libmlk-rpg/rpg/battle-state-selection.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/battle-state-selection.c	Fri Jan 08 12:56:10 2021 +0100
@@ -32,6 +32,8 @@
 #include "battle-bar.h"
 #include "battle-state.h"
 #include "character.h"
+#include "inventory.h"
+#include "selection.h"
 #include "spell.h"
 
 struct select {
@@ -40,6 +42,25 @@
 };
 
 static void
+use(const struct select *select, struct battle *bt)
+{
+	struct inventory_slot *slot;
+	struct character *source, *target;
+
+	if (bt->bar.sub_grid.selected >= INVENTORY_ITEM_MAX)
+		return;
+	if (!(slot = &bt->inventory->items[bt->bar.sub_grid.selected]))
+		return;
+
+	source = bt->order_cur->ch;
+	target = select->slt.index_side == 0
+		? bt->enemies[select->slt.index_character].ch
+		: bt->team[select->slt.index_character].ch;
+
+	battle_state_item(bt, source, target, slot);
+}
+
+static void
 attack(struct select *select, struct battle *bt)
 {
 	struct character *target;
@@ -121,6 +142,9 @@
 		case BATTLE_BAR_MENU_MAGIC:
 			cast(select, bt);
 			break;
+		case BATTLE_BAR_MENU_OBJECTS:
+			use(select, bt);
+			break;
 		default:
 			break;
 		}
--- a/libmlk-rpg/rpg/battle-state-sub.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/battle-state-sub.c	Fri Jan 08 12:56:10 2021 +0100
@@ -25,6 +25,9 @@
 
 #include <ui/theme.h>
 
+#include <rpg/inventory.h>
+#include <rpg/item.h>
+
 #include "battle.h"
 #include "battle-bar.h"
 #include "battle-state.h"
@@ -57,26 +60,58 @@
 static void
 start_select_object(struct battle *bt)
 {
-	(void)bt;
+	const struct selection slt = {
+		.allowed_kinds = SELECTION_KIND_ONE,
+		.allowed_sides = SELECTION_SIDE_TEAM | SELECTION_SIDE_ENEMY,
+		.index_side = 1,
+		.index_character = bt->order_curindex
+	};
+
+	battle_state_selection(bt, &slt);
+}
+
+static void
+draw_help(const struct battle *bt, const char *what)
+{
+	struct label label = {0};
+	unsigned int lw = 0, lh = 0;
+
+	label.flags = LABEL_FLAGS_SHADOW;
+	label.text = what;
+	label_query(&label, &lw, &lh);
+	label.x = bt->bar.sub_grid.x + (bt->bar.sub_grid.w / 2) - (lw / 2);
+	label.y = bt->bar.sub_grid.y - lh - BATTLE_THEME(bt)->padding;
+	label_draw(&label);
 }
 
 static void
 draw_spell_help(const struct battle *bt)
 {
 	const struct character *ch = bt->order_cur->ch;
-	const struct spell *sp = ch->spells[bt->bar.sub_grid.selected];
-	struct label label = {0};
-	unsigned int lw, lh;
+	const struct spell *sp;
 
-	if (!sp)
+	if (bt->bar.sub_grid.selected >= CHARACTER_SPELL_MAX)
+		return;
+	if ((sp = ch->spells[bt->bar.sub_grid.selected]))
 		return;
 
-	label.flags = LABEL_FLAGS_SHADOW;
-	label.text = sp->description;
-	label_query(&label, &lw, &lh);
-	label.x = bt->bar.sub_grid.x + (bt->bar.sub_grid.w / 2) - (lw / 2);
-	label.y = bt->bar.sub_grid.y - lh - BATTLE_THEME(bt)->padding;
-	label_draw(&label);
+	draw_help(bt, sp->description);
+}
+
+static void
+draw_object_help(const struct battle *bt)
+{
+	const struct inventory_slot *slot;
+
+	if (bt->bar.sub_grid.selected >= INVENTORY_ITEM_MAX)
+		return;
+
+	slot = &bt->inventory->items[bt->bar.sub_grid.selected];
+
+	if (!slot->item)
+		return;
+
+	draw_help(bt, slot->item->description);
 }
 
 static void
@@ -89,7 +124,6 @@
 		switch (ev->key.key) {
 		case KEY_ESCAPE:
 			/* Escape go to the previous state. */
-			bt->bar.state = BATTLE_BAR_STATE_MENU;
 			battle_state_menu(bt);
 			return;
 		default:
@@ -120,6 +154,8 @@
 
 	if (bt->bar.menu == BATTLE_BAR_MENU_MAGIC)
 		draw_spell_help(bt);
+	else if (bt->bar.menu == BATTLE_BAR_MENU_OBJECTS)
+		draw_object_help(bt);
 }
 
 void
--- a/libmlk-rpg/rpg/battle-state.h	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/battle-state.h	Fri Jan 08 12:56:10 2021 +0100
@@ -33,10 +33,10 @@
 
 #include <stdbool.h>
 
-#include "selection.h"
-
 struct battle;
 struct character;
+struct inventory_slot;
+struct selection;
 
 union event;
 
@@ -68,7 +68,10 @@
 battle_state_attacking(struct battle *bt, struct character *source, struct character *target);
 
 void
-battle_state_check(struct battle *bt);
+battle_state_item(struct battle *bt,
+                  struct character *source,
+                  struct character *target,
+                  struct inventory_slot *slot);
 
 void
 battle_state_check(struct battle *bt);
--- a/libmlk-rpg/rpg/battle.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/battle.c	Fri Jan 08 12:56:10 2021 +0100
@@ -40,6 +40,8 @@
 #include "battle-indicator.h"
 #include "battle-state.h"
 #include "character.h"
+#include "inventory.h"
+#include "item.h"
 #include "spell.h"
 
 struct indicator {
@@ -304,6 +306,24 @@
 }
 
 void
+battle_use(struct battle *bt, const struct item *item, struct character *owner, struct character *target)
+{
+	assert(bt);
+	assert(item);
+	assert(owner);
+	assert(target);
+
+	/*
+	 * Change the state to check prior to execute the item so it can change to something else
+	 * if needed.
+	 */
+	battle_state_check(bt);
+
+	inventory_consume(bt->inventory, item, 1);
+	item_exec_battle(item, bt, owner, target);
+}
+
+void
 battle_next(struct battle *bt)
 {
 	assert(bt);
@@ -345,7 +365,7 @@
 }
 
 void
-battle_indicator_hp(struct battle *bt, const struct character *target, unsigned int amount)
+battle_indicator_hp(struct battle *bt, const struct character *target, long amount)
 {
 	assert(bt);
 	assert(target);
@@ -354,7 +374,7 @@
 	struct indicator *id = alloc_new0(sizeof (*id));
 
 	id->bti.color = BATTLE_INDICATOR_HP_COLOR;
-	id->bti.amount = amount;
+	id->bti.amount = labs(amount);
 
 	/* TODO: positionate better. */
 	id->dw.x = et->x + target->sprites[CHARACTER_SPRITE_NORMAL]->cellw;
--- a/libmlk-rpg/rpg/battle.h	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/battle.h	Fri Jan 08 12:56:10 2021 +0100
@@ -37,6 +37,7 @@
 
 struct character;
 struct inventory;
+struct item;
 struct music;
 struct selection;
 struct spell;
@@ -78,44 +79,42 @@
 };
 
 void
-battle_start(struct battle *bt);
-
-void
-battle_next(struct battle *bt);
-
-struct battle_entity *
-battle_find(struct battle *bt, const struct character *ch);
+battle_start(struct battle *);
 
 void
-battle_switch(struct battle *bt, struct battle_state *st);
+battle_next(struct battle *);
+
+struct battle_entity *
+battle_find(struct battle *, const struct character *);
 
 void
-battle_order(struct battle *bt);
+battle_switch(struct battle *, struct battle_state *);
 
 void
-battle_attack(struct battle *bt,
-              struct character *source,
-              struct character *target);
+battle_order(struct battle *);
+
+void
+battle_attack(struct battle *, struct character *, struct character *);
 
 void
-battle_cast(struct battle *bt,
-            struct character *source,
-            const struct spell *spell,
-            const struct selection *slt);
+battle_cast(struct battle *, struct character *, const struct spell *, const struct selection *);
 
 void
-battle_indicator_hp(struct battle *bt, const struct character *target, unsigned int amount);
+battle_use(struct battle *, const struct item *, struct character *, struct character *);
+
+void
+battle_indicator_hp(struct battle *, const struct character *, long);
 
 void
-battle_handle(struct battle *bt, const union event *ev);
+battle_handle(struct battle *, const union event *);
 
 bool
-battle_update(struct battle *bt, unsigned int ticks);
+battle_update(struct battle *, unsigned int);
 
 void
-battle_draw(struct battle *bt);
+battle_draw(struct battle *);
 
 void
-battle_finish(struct battle *bt);
+battle_finish(struct battle *);
 
 #endif /* MOLKO_RPG_BATTLE_H */
--- a/libmlk-rpg/rpg/inventory.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/inventory.c	Fri Jan 08 12:56:10 2021 +0100
@@ -46,6 +46,7 @@
 	if (!slot)
 		return false;
 
+	slot->item = item;
 	slot->amount += amount;
 
 	return true;
@@ -59,6 +60,10 @@
 
 	struct inventory_slot *slot;
 
-	if (!(slot = find(iv, item)))
+	if ((slot = find(iv, item))) {
 		slot->amount -= amount;
+
+		if (slot->amount == 0)
+			slot->item = NULL;
+	}
 }
--- a/libmlk-rpg/rpg/item.c	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/item.c	Fri Jan 08 12:56:10 2021 +0100
@@ -21,19 +21,24 @@
 #include "item.h"
 
 void
-item_exec(const struct item *item, struct character *ch)
+item_exec_menu(const struct item *item, struct character *ch)
 {
 	assert(item);
 	assert(ch);
 
-	return item->exec(item, ch);
+	item->exec_menu(item, ch);
 }
 
-bool
-item_allowed(const struct item *item, struct character *ch)
+void
+item_exec_battle(const struct item *item,
+		 struct battle *bt,
+		 struct character *src,
+		 struct character *tgt)
 {
 	assert(item);
-	assert(ch);
+	assert(bt);
+	assert(src);
+	assert(tgt);
 
-	return item->allowed ? item->allowed(item, ch) : true;
+	item->exec_battle(item, bt, src, tgt);
 }
--- a/libmlk-rpg/rpg/item.h	Thu Jan 07 15:52:56 2021 +0100
+++ b/libmlk-rpg/rpg/item.h	Fri Jan 08 12:56:10 2021 +0100
@@ -19,8 +19,7 @@
 #ifndef MOLKO_RPG_ITEM_H
 #define MOLKO_RPG_ITEM_H
 
-#include <stdbool.h>
-
+struct battle;
 struct character;
 struct texture;
 
@@ -28,14 +27,22 @@
 	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);
+
+	void (*exec_menu)(const struct item *item, struct character *ch);
+
+	void (*exec_battle)(const struct item *item,
+	                    struct battle *bt,
+	                    struct character *src,
+	                    struct character *tgt);
 };
 
 void
-item_exec(const struct item *item, struct character *ch);
+item_exec_menu(const struct item *item, struct character *ch);
 
-bool
-item_allowed(const struct item *item, struct character *ch);
+void
+item_exec_battle(const struct item *item,
+                 struct battle *bt,
+                 struct character *src,
+                 struct character *tgt);
 
 #endif /* !MOLKO_RPG_ITEM_H */