comparison src/libmlk-rpg/rpg/battle-bar-default.c @ 385:3f13dc6c0e37

rpg: separate battle and the bar, closes #2522
author David Demelier <markand@malikania.fr>
date Tue, 15 Feb 2022 14:45:11 +0100
parents
children 7d5032755b7d
comparison
equal deleted inserted replaced
384:c458441ff472 385:3f13dc6c0e37
1 /*
2 * battle-bar-default.c -- default battle status bar and menu implementation
3 *
4 * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr>
5 *
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <assert.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23
24 #include <core/alloc.h>
25 #include <core/event.h>
26 #include <core/font.h>
27 #include <core/sprite.h>
28 #include <core/trace.h>
29 #include <core/util.h>
30 #include <core/window.h>
31
32 #include <ui/align.h>
33 #include <ui/theme.h>
34
35 #include "battle-bar-default.h"
36 #include "battle-bar.h"
37 #include "battle-state-item.h"
38 #include "battle-state-selection.h"
39 #include "battle.h"
40 #include "character.h"
41 #include "inventory.h"
42 #include "item.h"
43 #include "rpg_p.h"
44 #include "spell.h"
45
46 struct self {
47 struct battle_bar_default data;
48 struct battle_bar bar;
49 };
50
51 /*
52 * The following validate_* functions are called when the user has validated a
53 * selection depending on the current menu (e.g. Magic, Items).
54 *
55 * They change the battle state to the appropriate one.
56 */
57
58 static void
59 validate_attack(struct battle_bar_default *bar, struct battle *bt, const struct selection *sel)
60 {
61 (void)bar;
62
63 struct character *target;
64
65 if (sel->index_side == 0)
66 target = bt->enemies[sel->index_character].ch;
67 else
68 target = bt->team[sel->index_character].ch;
69
70 battle_attack(bt, bt->order_cur->ch, target);
71 }
72
73 static void
74 validate_magic(struct battle_bar_default *bar, struct battle *bt, const struct selection *sel)
75 {
76 struct character *source = bt->order_cur->ch;
77 const struct spell *spell = source->spells[bar->sub_grid.selected];
78
79 battle_cast(bt, source, spell, sel);
80 }
81
82 static void
83 validate_item(struct battle_bar_default *bar, struct battle *bt, const struct selection *sel)
84 {
85 struct inventory_slot *slot;
86 struct battle_entity *source, *target;
87
88 if (bar->sub_grid.selected >= INVENTORY_ITEM_MAX)
89 return;
90 if (!(slot = &bt->inventory->items[bar->sub_grid.selected]))
91 return;
92
93 source = bt->order_cur;
94 target = sel->index_side == 0
95 ? &bt->enemies[sel->index_character]
96 : &bt->team[sel->index_character];
97
98 /* TODO: battle_use? */
99 battle_state_item(bt, source, target, slot);
100 }
101
102 /*
103 * The following functions are used to switch to the battle selection state
104 * using the appropriate selector algorithm. For example, an item can only be
105 * used on a unique target while a spell can have multiple choices.
106 */
107
108 static void
109 switch_selection_attack(struct battle *bt)
110 {
111 struct selection sel = {
112 .allowed_kinds = SELECTION_KIND_ONE,
113 .allowed_sides = SELECTION_SIDE_ENEMY,
114 .index_side = 0
115 };
116
117 /* Just make sure */
118
119 selection_first(&sel, bt);
120 battle_state_selection(bt, &sel);
121 }
122
123 static void
124 switch_selection_spell(struct battle_bar_default *bar, struct battle *bt)
125 {
126 const struct character *ch = bt->order_cur->ch;
127 const struct spell *sp = ch->spells[bar->sub_grid.selected];
128 struct selection sel = {0};
129
130 /* Don't forget to reset the gridmenu state. */
131 gridmenu_reset(&bar->sub_grid);
132
133 if (bar->sub_grid.selected > CHARACTER_SPELL_MAX)
134 return;
135 if (!(sp = ch->spells[bar->sub_grid.selected]) || sp->mp > (unsigned int)(ch->mp))
136 return;
137
138 /* Use the spell selection algorithm to fill default values. */
139 spell_select(sp, bt, &sel);
140 battle_state_selection(bt, &sel);
141
142 /* A cursor should be present. */
143 if (!sprite_ok(BATTLE_THEME(bt)->sprites[THEME_SPRITE_CURSOR]))
144 tracef("battle: no cursor sprite in theme");
145 }
146
147 static void
148 switch_selection_item(struct battle *bt)
149 {
150 const struct selection slt = {
151 .allowed_kinds = SELECTION_KIND_ONE,
152 .allowed_sides = SELECTION_SIDE_TEAM | SELECTION_SIDE_ENEMY,
153 .index_side = 1,
154 .index_character = bt->order_curindex
155 };
156
157 battle_state_selection(bt, &slt);
158 }
159
160 /*
161 * The following functions actually draw the bar and their components depending
162 * on the current selected menu.
163 */
164
165 static void
166 draw_help(const struct battle_bar_default *bar, const struct battle *bt, const char *what)
167 {
168 struct label label = {0};
169 unsigned int lw = 0, lh = 0;
170
171 label.flags = LABEL_FLAGS_SHADOW;
172 label.text = what;
173 label_query(&label, &lw, &lh);
174 label.x = bar->sub_grid.x + (bar->sub_grid.w / 2) - (lw / 2);
175 label.y = bar->sub_grid.y - lh - BATTLE_THEME(bt)->padding;
176 label_draw(&label);
177 }
178
179 static void
180 draw_spell_help(const struct battle_bar_default *bar, const struct battle *bt)
181 {
182 const struct character *ch = bt->order_cur->ch;
183 const struct spell *sp;
184
185 if (bar->sub_grid.selected >= CHARACTER_SPELL_MAX)
186 return;
187 if (!(sp = ch->spells[bar->sub_grid.selected]))
188 return;
189
190 draw_help(bar, bt, sp->description);
191 }
192
193 static void
194 draw_item_help(const struct battle_bar_default *bar, const struct battle *bt)
195 {
196 const struct inventory_slot *slot;
197
198 if (bar->sub_grid.selected >= INVENTORY_ITEM_MAX)
199 return;
200
201 slot = &bt->inventory->items[bar->sub_grid.selected];
202
203 if (!slot->item)
204 return;
205
206 draw_help(bar, bt, slot->item->description);
207 }
208
209 static void
210 draw_status_character_stats(const struct battle *bt,
211 const struct character *ch,
212 int x,
213 int y,
214 unsigned int w,
215 unsigned int h)
216 {
217 struct theme *theme = BATTLE_THEME(bt);
218 struct label label;
219 unsigned int spacing, lw, lh;
220 char line[64];
221
222 /* Compute spacing between elements. */
223 spacing = h - (font_height(theme->fonts[THEME_FONT_INTERFACE]) * 3);
224 spacing /= 4;
225
226 /* Reuse the same label. */
227 label.theme = theme;
228 label.text = line;
229 label.flags = LABEL_FLAGS_SHADOW;
230
231 /* HP. */
232 snprintf(line, sizeof (line), "%d/%u", ch->hp, ch->hpmax);
233 label_query(&label, &lw, &lh);
234 label.x = x + w - lw - theme->padding;
235 label.y = y + spacing;
236 label_draw(&label);
237
238 /* MP. */
239 snprintf(line, sizeof (line), "%d/%u", ch->mp, ch->mpmax);
240 label_query(&label, &lw, &lh);
241 label.x = x + w - lw - theme->padding;
242 label.y = label.y + lh + spacing;
243 label_draw(&label);
244
245 /* Status. */
246 /* TODO: list all status. */
247 }
248
249 static void
250 draw_status_character(const struct battle_bar_default *bar,
251 const struct battle *bt,
252 const struct character *ch,
253 unsigned int index)
254 {
255 int x, y;
256 unsigned int w, h;
257
258 /* Compute bounding box for rendering. */
259 w = bar->status_frame.w / BATTLE_TEAM_MAX;
260 h = bar->status_frame.h;
261 x = bar->status_frame.x + (index * w);
262 y = bar->status_frame.y;
263
264 draw_status_character_stats(bt, ch, x, y, w, h);
265 }
266
267 static void
268 draw_status_characters(const struct battle_bar_default *bar, const struct battle *bt)
269 {
270 const struct battle_entity *et;
271 unsigned int index = 0;
272
273 BATTLE_TEAM_FOREACH(bt, et) {
274 if (character_ok(et->ch))
275 draw_status_character(bar, bt, et->ch, index);
276
277 ++index;
278 }
279 }
280
281 static void
282 draw_status(const struct battle_bar_default *bar, const struct battle *bt)
283 {
284 frame_draw(&bar->status_frame);
285 draw_status_characters(bar, bt);
286 }
287
288 static void
289 draw_menu(const struct battle_bar_default *bar, const struct battle *bt)
290 {
291 struct {
292 unsigned int w, h;
293 enum align align;
294 struct label label;
295 } buttons[] = {
296 {
297 .align = ALIGN_TOP,
298 .label = {
299 .text = _("Attack"),
300 .flags = LABEL_FLAGS_SHADOW
301 }
302 },
303 {
304 .align = ALIGN_RIGHT,
305 .label = {
306 .text = _("Magic"),
307 .flags = LABEL_FLAGS_SHADOW
308 }
309 },
310 {
311 .align = ALIGN_BOTTOM,
312 .label = {
313 .text = _("Objects"),
314 .flags = LABEL_FLAGS_SHADOW
315 }
316 },
317 {
318 .align = ALIGN_LEFT,
319 .label = {
320 .text = _("Special"),
321 .flags = LABEL_FLAGS_SHADOW
322 }
323 }
324 };
325
326 struct theme theme;
327 int bx, by;
328 unsigned int bw, bh;
329
330 /* Copy theme according to menu selection. */
331 theme_shallow(&theme, bt->theme);
332
333 /* Compute bounding box with margins removed. */
334 bx = bar->menu_frame.x + theme.padding;
335 by = bar->menu_frame.y + theme.padding;
336 bw = bar->menu_frame.w - theme.padding * 2;
337 bh = bar->menu_frame.h - theme.padding * 2;
338
339 /* Draw menu frame. */
340 frame_draw(&bar->menu_frame);
341
342 for (size_t i = 0; i < UTIL_SIZE(buttons); ++i) {
343 buttons[i].label.theme = &theme;
344
345 label_query(&buttons[i].label, &buttons[i].w, &buttons[i].h);
346
347 /* Change theme if it's selected. */
348 if ((size_t)bar->menu == i /*&& bar->state != BATTLE_BAR_DEFAULT_STATE_NONE*/)
349 theme.colors[THEME_COLOR_NORMAL] = BATTLE_THEME(bt)->colors[THEME_COLOR_SELECTED];
350 else
351 theme.colors[THEME_COLOR_NORMAL] = BATTLE_THEME(bt)->colors[THEME_COLOR_NORMAL];
352
353 align(buttons[i].align,
354 &buttons[i].label.x, &buttons[i].label.y, buttons[i].w, buttons[i].h,
355 bx, by, bw, bh);
356 label_draw(&buttons[i].label);
357 }
358 }
359
360 /*
361 * This function is called only in the first level of the bar menu: selecting
362 * one of the Attack, Magic, Item and Special items.
363 */
364 static void
365 handle_keydown_menu(struct battle_bar_default *bar, struct battle *bt, const union event *ev)
366 {
367 (void)bt;
368
369 switch (ev->key.key) {
370 case KEY_UP:
371 bar->menu = BATTLE_BAR_DEFAULT_MENU_ATTACK;
372 break;
373 case KEY_RIGHT:
374 bar->menu = BATTLE_BAR_DEFAULT_MENU_MAGIC;
375 break;
376 case KEY_DOWN:
377 bar->menu = BATTLE_BAR_DEFAULT_MENU_ITEM;
378 break;
379 case KEY_LEFT:
380 bar->menu = BATTLE_BAR_DEFAULT_MENU_SPECIAL;
381 break;
382 case KEY_ENTER:
383 /*
384 * At this step, attack does not require opening the sub menu so
385 * we change selection state immediately if needed.
386 */
387 switch (bar->menu) {
388 case BATTLE_BAR_DEFAULT_MENU_ATTACK:
389 switch_selection_attack(bt);
390 break;
391 case BATTLE_BAR_DEFAULT_MENU_ITEM:
392 battle_bar_default_open_item(bar, bt);
393 break;
394 case BATTLE_BAR_DEFAULT_MENU_MAGIC:
395 battle_bar_default_open_magic(bar, bt, bt->order_cur->ch);
396 break;
397 default:
398 break;
399 }
400 break;
401 default:
402 break;
403 }
404 }
405
406 /*
407 * This function is called when we're selecting a submenu entry from Items
408 * and Magic.
409 */
410 static void
411 handle_keydown_grid(struct battle_bar_default *bar, struct battle *bt, const union event *ev)
412 {
413 /* Go back to main menu if I press escape. */
414 if (ev->key.key == KEY_ESCAPE) {
415 gridmenu_reset(&bar->sub_grid);
416 bar->state = BATTLE_BAR_DEFAULT_STATE_MENU;
417 return;
418 }
419
420 gridmenu_handle(&bar->sub_grid, ev);
421
422 if (bar->sub_grid.state == GRIDMENU_STATE_ACTIVATED) {
423 gridmenu_reset(&bar->sub_grid);
424
425 switch (bar->menu) {
426 case BATTLE_BAR_DEFAULT_MENU_MAGIC:
427 switch_selection_spell(bar, bt);
428 break;
429 case BATTLE_BAR_DEFAULT_MENU_ITEM:
430 switch_selection_item(bt);
431 break;
432 default:
433 break;
434 }
435 }
436 }
437
438 static void
439 handle_keydown(struct battle_bar_default *bar, struct battle *bt, const union event *ev)
440 {
441 assert(ev->type == EVENT_KEYDOWN);
442
443 static void (*handlers[])(struct battle_bar_default *, struct battle *, const union event *) = {
444 [BATTLE_BAR_DEFAULT_STATE_MENU] = handle_keydown_menu,
445 [BATTLE_BAR_DEFAULT_STATE_GRID] = handle_keydown_grid
446 };
447
448 handlers[bar->state](bar, bt, ev);
449 }
450
451 #if 0
452
453 static void
454 handle_clickdown(struct battle_bar_default *bar, struct battle *bt, const union event *ev)
455 {
456 (void)bar;
457 (void)bt;
458 (void)ev;
459 assert(ev->type == EVENT_CLICKDOWN);
460
461 switch (bar->state) {
462 case BATTLE_BAR_DEFAULT_STATE_MENU:
463 /* We are selecting a main menu entry. */
464 /* TODO: implement click here too. */
465 break;
466 case BATTLE_BAR_DEFAULT_STATE_SUB:
467 /* We are in the sub menu (objects/spells). */
468 gridmenu_handle(&bar->sub_grid, ev);
469
470 if (bar->sub_grid.state == GRIDMENU_STATE_ACTIVATED)
471 default:
472 break;
473 }
474
475 return 0;
476 }
477
478 #endif
479
480 static void
481 init_gridmenu(struct battle_bar_default *bar, const struct battle *bt)
482 {
483 bar->sub_grid.x = bar->x;
484 bar->sub_grid.y = bar->menu_frame.y;
485 bar->sub_grid.w = bar->status_frame.w;
486 bar->sub_grid.h = bar->h;
487 bar->sub_grid.theme = bt->theme;
488 bar->sub_grid.nrows = 3;
489 bar->sub_grid.ncols = 4;
490
491 memset(bar->sub_grid.menu, 0, sizeof (bar->sub_grid.menu));
492 }
493
494 static void
495 start(struct battle_bar *bar, struct battle *bt)
496 {
497 (void)bt;
498
499 battle_bar_default_start(bar->data);
500 }
501
502 static void
503 select(struct battle_bar *bar, struct battle *bt, const struct selection *sel)
504 {
505 battle_bar_default_select(bar->data, bt, sel);
506 }
507
508 static void
509 handle(struct battle_bar *bar, struct battle *bt, const union event *ev)
510 {
511 battle_bar_default_handle(bar->data, bt, ev);
512 }
513
514 static void
515 draw(const struct battle_bar *bar, const struct battle *bt)
516 {
517 battle_bar_default_draw(bar->data, bt);
518 }
519
520 static void
521 finish(struct battle_bar *bar, struct battle *bt)
522 {
523 (void)bt;
524
525 battle_bar_default_finish(bar->data);
526 free(bar->data);
527 }
528
529 void
530 battle_bar_default_positionate(struct battle_bar_default *bar, const struct battle *bt)
531 {
532 assert(bar);
533 assert(bt);
534
535 bar->w = window.w;
536 bar->h = window.h * 0.12;
537 bar->x = 0;
538 bar->y = window.h - bar->h;
539
540 /* Menu in the middle of the bar (take 20%). */
541 bar->menu_frame.w = bar->w * 0.2;
542 bar->menu_frame.h = bar->h;
543 bar->menu_frame.x = bar->x + (bar->w / 2) - (bar->menu_frame.w / 2);
544 bar->menu_frame.y = window.h - bar->h;
545 bar->menu_frame.theme = bt->theme;
546
547 /* Status bar on the right. */
548 bar->status_frame.x = bar->menu_frame.x + bar->menu_frame.w;
549 bar->status_frame.y = bar->menu_frame.y;
550 bar->status_frame.w = (bar->w - bar->menu_frame.w) / 2;
551 bar->status_frame.h = bar->h;
552 bar->status_frame.theme = bt->theme;
553 }
554
555 void
556 battle_bar_default_open_magic(struct battle_bar_default *bar, const struct battle *bt, struct character *ch)
557 {
558 assert(bar);
559 assert(bt);
560 assert(ch);
561
562 init_gridmenu(bar, bt);
563
564 for (size_t i = 0; i < CHARACTER_SPELL_MAX; ++i)
565 if (ch->spells[i])
566 bar->sub_grid.menu[i] = ch->spells[i]->name;
567
568 gridmenu_repaint(&bar->sub_grid);
569
570 bar->state = BATTLE_BAR_DEFAULT_STATE_GRID;
571 }
572
573 void
574 battle_bar_default_open_item(struct battle_bar_default *bar, const struct battle *bt)
575 {
576 asssert(bar);
577 assert(bt);
578
579 init_gridmenu(bar, bt);
580
581 for (size_t i = 0; i < INVENTORY_ITEM_MAX; ++i) {
582 if (bt->inventory->items[i].item) {
583 snprintf(bar->sub_items[i], sizeof (bar->sub_items[i]), "%-16s %u",
584 bt->inventory->items[i].item->name, bt->inventory->items[i].amount);
585 bar->sub_grid.menu[i] = bar->sub_items[i];
586 }
587 }
588
589 gridmenu_repaint(&bar->sub_grid);
590
591 bar->state = BATTLE_BAR_DEFAULT_STATE_GRID;
592 }
593
594 void
595 battle_bar_default_start(struct battle_bar_default *bar)
596 {
597 assert(bar);
598
599 gridmenu_reset(&bar->sub_grid);
600
601 bar->menu = BATTLE_BAR_DEFAULT_MENU_ATTACK;
602 bar->state = BATTLE_BAR_DEFAULT_STATE_MENU;
603 }
604
605 /*
606 * Apply the battle selection for the current menu item. This function is called
607 * from the battle-state-selection state when the user validated the selection.
608 */
609 void
610 battle_bar_default_select(struct battle_bar_default *bar, struct battle *bt, const struct selection *sel)
611 {
612 assert(bar);
613 assert(bt);
614 assert(sel);
615
616 static void (*validate[])(struct battle_bar_default *, struct battle *, const struct selection *) = {
617 [BATTLE_BAR_DEFAULT_MENU_ATTACK] = validate_attack,
618 [BATTLE_BAR_DEFAULT_MENU_ITEM] = validate_item,
619 [BATTLE_BAR_DEFAULT_MENU_MAGIC] = validate_magic,
620 [BATTLE_BAR_DEFAULT_MENU_SPECIAL] = NULL
621 };
622
623 if (validate[bar->menu])
624 validate[bar->menu](bar, bt, sel);
625 }
626
627 void
628 battle_bar_default_handle(struct battle_bar_default *bar, struct battle *bt, const union event *ev)
629 {
630 assert(bar);
631 assert(bt);
632 assert(ev);
633
634 static void (*handlers[])(struct battle_bar_default *, struct battle *, const union event *) = {
635 [EVENT_KEYDOWN] = handle_keydown,
636 [EVENT_NUM] = NULL
637 };
638
639 if (handlers[ev->type])
640 handlers[ev->type](bar, bt, ev);
641 }
642
643 void
644 battle_bar_default_draw(const struct battle_bar_default *bar, const struct battle *bt)
645 {
646 assert(bar);
647 assert(bt);
648
649 draw_status(bar, bt);
650 draw_menu(bar, bt);
651
652 if (bar->state == BATTLE_BAR_DEFAULT_STATE_GRID) {
653 switch (bar->menu) {
654 case BATTLE_BAR_DEFAULT_MENU_MAGIC:
655 draw_spell_help(bar, bt);
656 break;
657 case BATTLE_BAR_DEFAULT_MENU_ITEM:
658 draw_item_help(bar, bt);
659 break;
660 default:
661 break;
662 }
663 }
664
665 /* Sub menu is only shown if state is set to it. */
666 if (bar->state == BATTLE_BAR_DEFAULT_STATE_GRID)
667 gridmenu_draw(&bar->sub_grid);
668 }
669
670 void
671 battle_bar_default_finish(struct battle_bar_default *bar)
672 {
673 assert(bar);
674
675 gridmenu_finish(&bar->sub_grid);
676
677 memset(bar, 0, sizeof (*bar));
678 }
679
680 void
681 battle_bar_default(struct battle *bt)
682 {
683 assert(bt);
684
685 struct self *self;
686
687 self = alloc_new0(sizeof (*self));
688 self->bar.data = self;
689 self->bar.start = start;
690 self->bar.select = select;
691 self->bar.handle = handle;
692 self->bar.draw = draw;
693 self->bar.finish = finish;
694
695 battle_bar_default_positionate(&self->data, bt);
696
697 bt->bar = &self->bar;
698 }