Mercurial > molko
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 } |