diff libmlk-rpg/rpg/battle-bar.c @ 243:71b3b7036de7

misc: lot of cleanups, - prefix libraries with libmlk, - move assets from source directories closes #2520, - prefix header guards closes #2519
author David Demelier <markand@malikania.fr>
date Sat, 28 Nov 2020 22:37:30 +0100
parents librpg/rpg/battle-bar.c@76afe639fd72
children bfde372bf152
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libmlk-rpg/rpg/battle-bar.c	Sat Nov 28 22:37:30 2020 +0100
@@ -0,0 +1,360 @@
+/*
+ * battle-bar.h -- battle status bar and menu
+ *
+ * Copyright (c) 2020 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <core/event.h>
+#include <core/font.h>
+#include <core/window.h>
+#include <core/util.h>
+
+#include <ui/align.h>
+#include <ui/theme.h>
+
+#include "battle.h"
+#include "battle-bar.h"
+#include "character.h"
+#include "spell.h"
+#include "rpg_p.h"
+
+static void
+draw_status_character_stats(const struct battle *bt,
+                            const struct character *ch,
+                            int x,
+                            int y,
+                            unsigned int w,
+                            unsigned int h)
+{
+	struct theme *theme = BATTLE_THEME(bt);
+	struct label label;
+	unsigned int spacing, lw, lh;
+	char line[64];
+
+	/* Compute spacing between elements. */
+	spacing = h - (font_height(theme->fonts[THEME_FONT_INTERFACE]) * 3);
+	spacing /= 4;
+
+	/* Reuse the same label. */
+	label.theme = theme;
+	label.text = line;
+	label.flags = LABEL_FLAGS_SHADOW;
+
+	/* HP. */
+	snprintf(line, sizeof (line), "%d/%u", ch->hp, ch->hpmax);
+	label_query(&label, &lw, &lh);
+	label.x = x + w - lw - theme->padding;
+	label.y = y + spacing;
+	label_draw(&label);
+
+	/* MP. */
+	snprintf(line, sizeof (line), "%d/%u", ch->mp, ch->mpmax);
+	label_query(&label, &lw, &lh);
+	label.x = x + w - lw - theme->padding;
+	label.y = label.y + lh + spacing;
+	label_draw(&label);
+
+	/* Status. */
+	/* TODO: list all status. */
+}
+
+static void
+draw_status_character(const struct battle_bar *bar,
+                      const struct battle *bt,
+                      const struct character *ch,
+                      unsigned int index)
+{
+	int x, y;
+	unsigned int w, h;
+
+	/* Compute bounding box for rendering. */
+	w = bar->status_frame.w / BATTLE_TEAM_MAX;
+	h = bar->status_frame.h;
+	x = bar->status_frame.x + (index * w);
+	y = bar->status_frame.y;
+
+	draw_status_character_stats(bt, ch, x, y, w, h);
+}
+
+static void
+draw_status_characters(const struct battle_bar *bar, const struct battle *bt)
+{
+	const struct battle_entity *et;
+	unsigned int index = 0;
+
+	BATTLE_TEAM_FOREACH(bt, et) {
+		if (character_ok(et->ch))
+			draw_status_character(bar, bt, et->ch, index);
+
+		++index;
+	}
+}
+
+static void
+draw_status(const struct battle_bar *bar, const struct battle *bt)
+{
+	frame_draw(&bar->status_frame);
+	draw_status_characters(bar, bt);
+}
+
+static void
+draw_menu(const struct battle_bar *bar, const struct battle *bt)
+{
+	struct {
+		unsigned int w, h;
+		enum align align;
+		struct label label;
+	} buttons[] = {
+		{
+			.align = ALIGN_TOP,
+			.label = {
+				.text = _("Attack"),
+				.flags = LABEL_FLAGS_SHADOW
+			}
+		},
+		{
+			.align = ALIGN_RIGHT,
+			.label = {
+				.text = _("Magic"),
+				.flags = LABEL_FLAGS_SHADOW
+			}
+		},
+		{
+			.align = ALIGN_BOTTOM,
+			.label = {
+				.text = _("Objects"),
+				.flags = LABEL_FLAGS_SHADOW
+			}
+		},
+		{
+			.align = ALIGN_LEFT,
+			.label = {
+				.text = _("Special"),
+				.flags = LABEL_FLAGS_SHADOW
+			}
+		}
+	};
+
+	struct theme theme;
+	int bx, by;
+	unsigned int bw, bh;
+
+	/* Copy theme according to menu selection. */
+	theme_shallow(&theme, bt->theme);
+
+	/* Compute bounding box with margins removed. */
+	bx = bar->menu_frame.x + theme.padding;
+	by = bar->menu_frame.y + theme.padding;
+	bw = bar->menu_frame.w - theme.padding * 2;
+	bh = bar->menu_frame.h - theme.padding * 2;
+
+	/* Draw menu frame. */
+	frame_draw(&bar->menu_frame);
+
+	for (size_t i = 0; i < NELEM(buttons); ++i) {
+		buttons[i].label.theme = &theme;
+
+		label_query(&buttons[i].label, &buttons[i].w, &buttons[i].h);
+
+		/* Change theme if it's selected. */
+		if ((size_t)bar->menu == i && bar->state != BATTLE_BAR_STATE_NONE)
+			theme.colors[THEME_COLOR_NORMAL] = BATTLE_THEME(bt)->colors[THEME_COLOR_SELECTED];
+		else
+			theme.colors[THEME_COLOR_NORMAL] = BATTLE_THEME(bt)->colors[THEME_COLOR_NORMAL];
+
+		align(buttons[i].align,
+		    &buttons[i].label.x, &buttons[i].label.y, buttons[i].w, buttons[i].h,
+		    bx, by, bw, bh);
+		label_draw(&buttons[i].label);
+	}
+}
+
+static void
+draw_sub(const struct battle_bar *bar)
+{
+	gridmenu_draw(&bar->sub_grid);
+}
+
+static bool
+handle_keydown(struct battle_bar *bar, const union event *ev)
+{
+	assert(ev->type == EVENT_KEYDOWN);
+
+	switch (bar->state) {
+	case BATTLE_BAR_STATE_MENU:
+		/* We are selecting a main menu entry. */
+		switch (ev->key.key) {
+		case KEY_UP:
+			bar->menu = BATTLE_BAR_MENU_ATTACK;
+			break;
+		case KEY_RIGHT:
+			bar->menu = BATTLE_BAR_MENU_MAGIC;
+			break;
+		case KEY_DOWN:
+			bar->menu = BATTLE_BAR_MENU_OBJECTS;
+			break;
+		case KEY_LEFT:
+			bar->menu = BATTLE_BAR_MENU_SPECIAL;
+			break;
+		case KEY_ENTER:
+			return true;
+		default:
+			break;
+		}
+		break;
+	case BATTLE_BAR_STATE_SUB:
+		/* We are in the sub menu (objects/spells). */
+		gridmenu_handle(&bar->sub_grid, ev);
+		return bar->sub_grid.state == GRIDMENU_STATE_ACTIVATED;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static bool
+handle_clickdown(struct battle_bar *bar, const union event *ev)
+{
+	assert(ev->type == EVENT_CLICKDOWN);
+
+	switch (bar->state) {
+	case BATTLE_BAR_STATE_MENU:
+		/* We are selecting a main menu entry. */
+		/* TODO: implement click here too. */
+		break;
+	case BATTLE_BAR_STATE_SUB:
+		/* We are in the sub menu (objects/spells). */
+		gridmenu_handle(&bar->sub_grid, ev);
+		return bar->sub_grid.state == GRIDMENU_STATE_ACTIVATED;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+void
+battle_bar_positionate(struct battle_bar *bar, const struct battle *bt)
+{
+	assert(bar);
+
+	/* Menu in the middle of the bar (take 20%). */
+	bar->menu_frame.w = bar->w * 0.2;
+	bar->menu_frame.h = bar->h;
+	bar->menu_frame.x = bar->x + (bar->w / 2) - (bar->menu_frame.w / 2);
+	bar->menu_frame.y = window.h - bar->h;
+	bar->menu_frame.theme = bt->theme;
+
+	/* Status bar on the right. */
+	bar->status_frame.x = bar->menu_frame.x + bar->menu_frame.w;
+	bar->status_frame.y = bar->menu_frame.y;
+	bar->status_frame.w = (bar->w - bar->menu_frame.w) / 2;
+	bar->status_frame.h = bar->h;
+	bar->status_frame.theme = bt->theme;
+}
+
+bool
+battle_bar_handle(struct battle_bar *bar, const struct battle *bt, const union event *ev)
+{
+	/* Not needed yet. */
+	(void)bt;
+
+	assert(bar);
+	assert(bt);
+	assert(ev);
+
+	if (bar->state == BATTLE_BAR_STATE_NONE)
+		return false;
+
+	switch (ev->type) {
+	case EVENT_KEYDOWN:
+		return handle_keydown(bar, ev);
+	case EVENT_CLICKDOWN:
+		return handle_clickdown(bar, ev);
+	default:
+		break;
+	}
+
+	return false;
+}
+
+void
+battle_bar_reset(struct battle_bar *bar)
+{
+	gridmenu_reset(&bar->sub_grid);
+
+	bar->menu = BATTLE_BAR_MENU_ATTACK;
+	bar->state = BATTLE_BAR_STATE_NONE;
+}
+
+void
+battle_bar_open_menu(struct battle_bar *bar)
+{
+	bar->state = BATTLE_BAR_STATE_MENU;
+	bar->menu = BATTLE_BAR_MENU_ATTACK;
+}
+
+void
+battle_bar_open_spells(struct battle_bar *bar, const struct battle *bt, struct character *ch)
+{
+	/* Sub menu bar on the left, take same space as status. */
+	bar->sub_grid.x = bar->x;
+	bar->sub_grid.y = bar->menu_frame.y;
+	bar->sub_grid.w = bar->status_frame.w;
+	bar->sub_grid.h = bar->h;
+	bar->sub_grid.theme = bt->theme;
+	bar->sub_grid.nrows = 3;
+	bar->sub_grid.ncols = 4;
+
+	memset(bar->sub_grid.menu, 0, sizeof (bar->sub_grid.menu));
+
+	for (size_t i = 0; i < CHARACTER_SPELL_MAX; ++i) {
+		if (ch->spells[i])
+			bar->sub_grid.menu[i] = ch->spells[i]->name;
+	}
+
+	gridmenu_repaint(&bar->sub_grid);
+
+	bar->state = BATTLE_BAR_STATE_SUB;
+}
+
+void
+battle_bar_draw(const struct battle_bar *bar, const struct battle *bt)
+{
+	assert(bar);
+	assert(bt);
+
+	draw_status(bar, bt);
+	draw_menu(bar, bt);
+
+	/* Sub menu is only shown if state is set to it. */
+	if (bar->state == BATTLE_BAR_STATE_SUB)
+		draw_sub(bar);
+}
+
+void
+battle_bar_finish(struct battle_bar *bar)
+{
+	assert(bar);
+
+	gridmenu_finish(&bar->sub_grid);
+
+	memset(bar, 0, sizeof (*bar));
+}