comparison libmlk-rpg/mlk/rpg/battle.c @ 434:4e78f045e8c0

rpg: cleanup hierarchy
author David Demelier <markand@malikania.fr>
date Sat, 15 Oct 2022 21:24:17 +0200
parents src/libmlk-rpg/rpg/battle.c@862b15c3a3ae
children 9c3b3935f0aa
comparison
equal deleted inserted replaced
433:862b15c3a3ae 434:4e78f045e8c0
1 /*
2 * battle.c -- battles
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 <mlk/core/alloc.h>
25 #include <mlk/core/event.h>
26 #include <mlk/core/font.h>
27 #include <mlk/core/music.h>
28 #include <mlk/core/sprite.h>
29 #include <mlk/core/texture.h>
30 #include <mlk/core/trace.h>
31 #include <mlk/core/util.h>
32 #include <mlk/core/window.h>
33
34 #include <mlk/ui/align.h>
35 #include <mlk/ui/frame.h>
36 #include <mlk/ui/label.h>
37 #include <mlk/ui/theme.h>
38
39 #include "battle-bar.h"
40 #include "battle-indicator.h"
41 #include "battle-state-ai.h"
42 #include "battle-state-attacking.h"
43 #include "battle-state-check.h"
44 #include "battle-state-menu.h"
45 #include "battle-state-opening.h"
46 #include "battle-state.h"
47 #include "battle.h"
48 #include "character.h"
49 #include "inventory.h"
50 #include "item.h"
51 #include "spell.h"
52
53 struct indicator {
54 struct drawable dw;
55 struct battle_indicator bti;
56 };
57
58 static int
59 indicator_update(struct drawable *dw, unsigned int ticks)
60 {
61 struct indicator *id = dw->data;
62
63 return battle_indicator_update(&id->bti, ticks);
64 }
65
66 static void
67 indicator_draw(struct drawable *dw)
68 {
69 const struct indicator *id = dw->data;
70
71 battle_indicator_draw(&id->bti, dw->x, dw->y);
72 }
73
74 static void
75 indicator_free(struct drawable *dw)
76 {
77 struct indicator *id = dw->data;
78
79 battle_indicator_finish(&id->bti);
80 free(id);
81 }
82
83 static struct battle_entity *
84 find(struct battle *bt, const struct character *ch)
85 {
86 struct battle_entity *et;
87
88 BATTLE_TEAM_FOREACH(bt, et)
89 if (et->ch == ch)
90 return et;
91 BATTLE_ENEMY_FOREACH(bt, et)
92 if (et->ch == ch)
93 return et;
94
95 return NULL;
96 }
97
98 static struct battle_entity *
99 random_select(struct battle_entity **group, size_t groupsz)
100 {
101 struct battle_entity *ret = NULL, *et = NULL;
102
103 do {
104 et = group[util_nrand(0, groupsz - 1)];
105
106 if (et && et->ch)
107 ret = et;
108 } while (!ret);
109
110 return ret;
111 }
112
113 static int
114 cmp_order(const void *d1, const void *d2)
115 {
116 const struct battle_entity *et1 = *(const struct battle_entity **)d1;
117 const struct battle_entity *et2 = *(const struct battle_entity **)d2;
118
119 return et2->ch->agt < et1->ch->agt;
120 }
121
122 static int
123 is_team(const struct battle *bt, const struct character *ch)
124 {
125 for (size_t i = 0; i < bt->teamsz; ++i)
126 if (bt->team[i] && bt->team[i]->ch == ch)
127 return 1;
128
129 return 0;
130 }
131
132 static void
133 positionate_name(struct battle_entity *et, const struct battle *bt)
134 {
135 unsigned int lw;
136 struct sprite *sprite;
137
138 /* Show the character name below its sprite. */
139 sprite = et->ch->sprites[CHARACTER_SPRITE_NORMAL];
140
141 et->name.text = et->ch->name;
142 et->name.flags = LABEL_FLAGS_SHADOW;
143 label_query(&et->name, &lw, NULL);
144 et->name.y = et->y + sprite->cellh + BATTLE_THEME(bt)->padding;
145 et->name.x = et->x + (sprite->cellw / 2) - (lw / 2);
146 }
147
148 static void
149 positionate_names(struct battle *bt)
150 {
151 struct battle_entity *et;
152
153 BATTLE_TEAM_FOREACH(bt, et)
154 if (character_ok(et->ch))
155 positionate_name(et, bt);
156 BATTLE_ENEMY_FOREACH(bt, et)
157 if (character_ok(et->ch))
158 positionate_name(et, bt);
159 }
160
161 static void
162 positionate_team(struct battle *bt)
163 {
164 struct battle_entity *et;
165 unsigned int requirement = 0, nmemb = 0, spacing;
166 int x, y;
167
168 BATTLE_TEAM_FOREACH(bt, et) {
169 /* Stop in case any member of the team has been positionated. */
170 if (et->x != 0 || et->y != 0)
171 return;
172
173 if (battle_entity_ok(bt->team[i])) {
174 nmemb++;
175 requirement += et->ch->sprites[CHARACTER_SPRITE_NORMAL]->cellh;
176 }
177 }
178
179 /* TODO: compute a correct x placement. */
180 spacing = (window.h - requirement) / (nmemb + 1);
181 x = window.w - 200;
182 y = spacing;
183
184 BATTLE_TEAM_FOREACH(bt, et) {
185 if (battle_entity_ok(et)) {
186 et->x = x;
187 et->y = y;
188 y += et->ch->sprites[CHARACTER_SPRITE_NORMAL]->cellh + spacing;
189 }
190 }
191 }
192
193 static void
194 draw_entities(const struct battle *bt, struct battle_entity **entities, size_t entitiesz)
195 {
196 for (size_t i = 0; i < entitiesz; ++i) {
197 if (battle_entity_ok(entities[i]))
198 battle_entity_draw(entities[i], bt);
199 }
200 }
201
202 static void
203 update_entities(struct battle_entity **entities, size_t entitiesz, unsigned int ticks)
204 {
205 for (size_t i = 0; i < entitiesz; ++i) {
206 if (battle_entity_ok(entities[i]))
207 battle_entity_update(entities[i], ticks);
208 }
209 }
210
211 void
212 battle_init(struct battle *bt)
213 {
214 assert(bt);
215
216 memset(bt, 0, sizeof (*bt));
217 }
218
219 int
220 battle_ok(const struct battle *bt)
221 {
222 return bt && bt->state && bt->bar && bt->enemiesz && bt->team;
223 }
224
225 void
226 battle_start(struct battle *bt)
227 {
228 assert(bt);
229
230 struct battle_entity *et;
231
232 BATTLE_TEAM_FOREACH(bt, et)
233 if (battle_entity_ok(et))
234 battle_entity_init(et);
235 BATTLE_ENEMY_FOREACH(bt, et)
236 if (battle_entity_ok(et))
237 battle_entity_init(et);
238
239 positionate_team(bt);
240 positionate_names(bt);
241
242 /* Start the state "opening" animation. */
243 battle_state_opening(bt);
244
245 /* Play music if present. */
246 if (bt->music[0])
247 music_play(bt->music[0], MUSIC_LOOP);
248
249 battle_order(bt);
250 }
251
252 void
253 battle_switch(struct battle *bt, struct battle_state *st)
254 {
255 assert(bt);
256 assert(st);
257
258 if (bt->state)
259 battle_state_finish(bt->state, bt);
260
261 bt->state = st;
262 }
263
264 void
265 battle_order(struct battle *bt)
266 {
267 struct battle_entity **porder;
268
269 /* Create a pointer list to every entity. */
270 bt->order = alloc_rearray0(bt->order, bt->ordersz,
271 bt->teamsz + bt->enemiesz, sizeof (*bt->order));
272 bt->ordersz = bt->teamsz + bt->enemiesz;
273 bt->ordercur = porder = bt->order;
274
275 for (size_t i = 0; i < bt->teamsz; ++i)
276 if (bt->team[i] && character_ok(bt->team[i]->ch))
277 *porder++ = bt->team[i];
278 for (size_t i = 0; i < bt->enemiesz; ++i)
279 if (bt->enemies[i] && character_ok(bt->enemies[i]->ch))
280 *porder++ = bt->enemies[i];
281
282 /* Now sort. */
283 qsort(bt->order, bt->ordersz, sizeof (*bt->order), cmp_order);
284 }
285
286 struct battle_entity *
287 battle_current(const struct battle *bt)
288 {
289 assert(bt);
290
291 return *bt->ordercur;
292 }
293
294 size_t
295 battle_index(const struct battle *bt)
296 {
297 assert(bt);
298
299 return bt->ordercur - bt->order;
300 }
301
302 void
303 battle_attack(struct battle *bt,
304 struct character *source,
305 struct character *target)
306 {
307 assert(bt);
308 assert(character_ok(source));
309
310 /* Target is empty? select randomly. */
311 if (!target) {
312 if (is_team(bt, source))
313 target = random_select(bt->enemies, bt->enemiesz)->ch;
314 else
315 target = random_select(bt->team, bt->teamsz)->ch;
316 }
317
318 battle_state_attacking(battle_find(bt, source), battle_find(bt, target), bt);
319 }
320
321 void
322 battle_cast(struct battle *bt,
323 struct character *source,
324 const struct spell *spell,
325 const struct selection *selection)
326 {
327 assert(bt);
328 assert(source);
329 assert(spell);
330 assert((unsigned int)source->mp >= spell->mp);
331
332 /* TODO: animate. */
333 source->mp -= spell->mp;
334 spell_action(spell, bt, source, selection);
335 }
336
337 void
338 battle_use(struct battle *bt, const struct item *item, struct character *owner, struct character *target)
339 {
340 assert(bt);
341 assert(item);
342 assert(owner);
343 assert(target);
344
345 /*
346 * Change the state to check prior to execute the item so it can change to something else
347 * if needed.
348 */
349 battle_state_check(bt);
350
351 inventory_consume(bt->inventory, item, 1);
352 item_exec_battle(item, bt, owner, target);
353 }
354
355 void
356 battle_next(struct battle *bt)
357 {
358 assert(bt);
359
360 if (!bt->ordercur)
361 battle_order(bt);
362 else {
363 if (bt->ordercur - bt->order + (size_t)1U >= bt->ordersz)
364 battle_order(bt);
365 else
366 bt->ordercur++;
367 }
368
369 if (is_team(bt, battle_current(bt)->ch)) {
370 battle_bar_start(bt->bar, bt);
371 battle_state_menu(bt);
372 } else
373 battle_state_ai(bt);
374 }
375
376 struct battle_entity *
377 battle_find(struct battle *bt, const struct character *ch)
378 {
379 assert(bt);
380
381 return find(bt, ch);
382 }
383
384 void
385 battle_indicator_hp(struct battle *bt, const struct character *target, long amount)
386 {
387 assert(bt);
388 assert(target);
389
390 const struct battle_entity *et = find(bt, target);
391 struct indicator *id;
392
393 if (!(bt->effects)) {
394 tracef("unable to add id without a drawable_stack");
395 return;
396 }
397
398 id = alloc_new0(sizeof (*id));
399 id->bti.color = BATTLE_INDICATOR_HP_COLOR;
400 id->bti.amount = labs(amount);
401
402 /* TODO: positionate better. */
403 id->dw.x = et->x + target->sprites[CHARACTER_SPRITE_NORMAL]->cellw;
404 id->dw.y = et->y + target->sprites[CHARACTER_SPRITE_NORMAL]->cellh;
405 id->dw.data = id;
406 id->dw.update = indicator_update;
407 id->dw.draw = indicator_draw;
408 id->dw.finish = indicator_free;
409
410 battle_indicator_start(&id->bti);
411
412 if (drawable_stack_add(bt->effects, &id->dw) < 0)
413 drawable_finish(&id->dw);
414 }
415
416 void
417 battle_handle_component(struct battle *bt, const union event *ev, enum battle_component comp)
418 {
419 assert(bt);
420 assert(ev);
421
422 if (comp & BATTLE_COMPONENT_BAR)
423 battle_bar_handle(bt->bar, bt, ev);
424 if ((comp & BATTLE_COMPONENT_ACTIONS) && bt->actions)
425 action_stack_handle(bt->actions, ev);
426 }
427
428 void
429 battle_handle(struct battle *bt, const union event *ev)
430 {
431 assert(bt);
432 assert(ev);
433
434 battle_state_handle(bt->state, bt, ev);
435 }
436
437 void
438 battle_update_component(struct battle *bt, unsigned int ticks, enum battle_component comp)
439 {
440 assert(bt);
441
442 if (comp & BATTLE_COMPONENT_ENTITIES) {
443 update_entities(bt->team, bt->teamsz, ticks);
444 update_entities(bt->enemies, bt->enemiesz, ticks);
445 }
446 if (comp & BATTLE_COMPONENT_BAR)
447 battle_bar_update(bt->bar, bt, ticks);
448 if ((comp & BATTLE_COMPONENT_ACTIONS) && bt->actions)
449 action_stack_update(bt->actions, ticks);
450 if ((comp & BATTLE_COMPONENT_DRAWABLES) && bt->effects)
451 drawable_stack_update(bt->effects, ticks);
452 }
453
454 int
455 battle_update(struct battle *bt, unsigned int ticks)
456 {
457 assert(bt && bt->state);
458
459 return battle_state_update(bt->state, bt, ticks);
460 }
461
462 void
463 battle_draw_component(const struct battle *bt, enum battle_component comp)
464 {
465 assert(bt);
466
467 if ((comp & BATTLE_COMPONENT_BACKGROUND) && texture_ok(bt->background))
468 texture_scale(bt->background,
469 0, 0, bt->background->w, bt->background->h,
470 0, 0, window.w, window.h,
471 0.f);
472 if (comp & BATTLE_COMPONENT_ENTITIES) {
473 draw_entities(bt, bt->team, bt->teamsz);
474 draw_entities(bt, bt->enemies, bt->enemiesz);
475 }
476 if (comp & BATTLE_COMPONENT_BAR)
477 battle_bar_draw(bt->bar, bt);
478 if ((comp & BATTLE_COMPONENT_ACTIONS) && bt->actions)
479 action_stack_draw(bt->actions);
480 if ((comp & BATTLE_COMPONENT_DRAWABLES) && bt->effects)
481 drawable_stack_draw(bt->effects);
482 }
483
484 void
485 battle_draw(const struct battle *bt)
486 {
487 assert(battle_ok(bt));
488
489 battle_state_draw(bt->state, bt);
490 }
491
492 void
493 battle_finish(struct battle *bt)
494 {
495 assert(bt);
496
497 if (bt->state)
498 battle_state_finish(bt->state, bt);
499
500 free(bt->order);
501 memset(bt, 0, sizeof (*bt));
502 }