Mercurial > molko
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-rpg/mlk/rpg/battle.c Sat Oct 15 21:24:17 2022 +0200 @@ -0,0 +1,502 @@ +/* + * battle.c -- battles + * + * Copyright (c) 2020-2022 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 <stdlib.h> +#include <string.h> + +#include <mlk/core/alloc.h> +#include <mlk/core/event.h> +#include <mlk/core/font.h> +#include <mlk/core/music.h> +#include <mlk/core/sprite.h> +#include <mlk/core/texture.h> +#include <mlk/core/trace.h> +#include <mlk/core/util.h> +#include <mlk/core/window.h> + +#include <mlk/ui/align.h> +#include <mlk/ui/frame.h> +#include <mlk/ui/label.h> +#include <mlk/ui/theme.h> + +#include "battle-bar.h" +#include "battle-indicator.h" +#include "battle-state-ai.h" +#include "battle-state-attacking.h" +#include "battle-state-check.h" +#include "battle-state-menu.h" +#include "battle-state-opening.h" +#include "battle-state.h" +#include "battle.h" +#include "character.h" +#include "inventory.h" +#include "item.h" +#include "spell.h" + +struct indicator { + struct drawable dw; + struct battle_indicator bti; +}; + +static int +indicator_update(struct drawable *dw, unsigned int ticks) +{ + struct indicator *id = dw->data; + + return battle_indicator_update(&id->bti, ticks); +} + +static void +indicator_draw(struct drawable *dw) +{ + const struct indicator *id = dw->data; + + battle_indicator_draw(&id->bti, dw->x, dw->y); +} + +static void +indicator_free(struct drawable *dw) +{ + struct indicator *id = dw->data; + + battle_indicator_finish(&id->bti); + free(id); +} + +static struct battle_entity * +find(struct battle *bt, const struct character *ch) +{ + struct battle_entity *et; + + BATTLE_TEAM_FOREACH(bt, et) + if (et->ch == ch) + return et; + BATTLE_ENEMY_FOREACH(bt, et) + if (et->ch == ch) + return et; + + return NULL; +} + +static struct battle_entity * +random_select(struct battle_entity **group, size_t groupsz) +{ + struct battle_entity *ret = NULL, *et = NULL; + + do { + et = group[util_nrand(0, groupsz - 1)]; + + if (et && et->ch) + ret = et; + } while (!ret); + + return ret; +} + +static int +cmp_order(const void *d1, const void *d2) +{ + const struct battle_entity *et1 = *(const struct battle_entity **)d1; + const struct battle_entity *et2 = *(const struct battle_entity **)d2; + + return et2->ch->agt < et1->ch->agt; +} + +static int +is_team(const struct battle *bt, const struct character *ch) +{ + for (size_t i = 0; i < bt->teamsz; ++i) + if (bt->team[i] && bt->team[i]->ch == ch) + return 1; + + return 0; +} + +static void +positionate_name(struct battle_entity *et, const struct battle *bt) +{ + unsigned int lw; + struct sprite *sprite; + + /* Show the character name below its sprite. */ + sprite = et->ch->sprites[CHARACTER_SPRITE_NORMAL]; + + et->name.text = et->ch->name; + et->name.flags = LABEL_FLAGS_SHADOW; + label_query(&et->name, &lw, NULL); + et->name.y = et->y + sprite->cellh + BATTLE_THEME(bt)->padding; + et->name.x = et->x + (sprite->cellw / 2) - (lw / 2); +} + +static void +positionate_names(struct battle *bt) +{ + struct battle_entity *et; + + BATTLE_TEAM_FOREACH(bt, et) + if (character_ok(et->ch)) + positionate_name(et, bt); + BATTLE_ENEMY_FOREACH(bt, et) + if (character_ok(et->ch)) + positionate_name(et, bt); +} + +static void +positionate_team(struct battle *bt) +{ + struct battle_entity *et; + unsigned int requirement = 0, nmemb = 0, spacing; + int x, y; + + BATTLE_TEAM_FOREACH(bt, et) { + /* Stop in case any member of the team has been positionated. */ + if (et->x != 0 || et->y != 0) + return; + + if (battle_entity_ok(bt->team[i])) { + nmemb++; + requirement += et->ch->sprites[CHARACTER_SPRITE_NORMAL]->cellh; + } + } + + /* TODO: compute a correct x placement. */ + spacing = (window.h - requirement) / (nmemb + 1); + x = window.w - 200; + y = spacing; + + BATTLE_TEAM_FOREACH(bt, et) { + if (battle_entity_ok(et)) { + et->x = x; + et->y = y; + y += et->ch->sprites[CHARACTER_SPRITE_NORMAL]->cellh + spacing; + } + } +} + +static void +draw_entities(const struct battle *bt, struct battle_entity **entities, size_t entitiesz) +{ + for (size_t i = 0; i < entitiesz; ++i) { + if (battle_entity_ok(entities[i])) + battle_entity_draw(entities[i], bt); + } +} + +static void +update_entities(struct battle_entity **entities, size_t entitiesz, unsigned int ticks) +{ + for (size_t i = 0; i < entitiesz; ++i) { + if (battle_entity_ok(entities[i])) + battle_entity_update(entities[i], ticks); + } +} + +void +battle_init(struct battle *bt) +{ + assert(bt); + + memset(bt, 0, sizeof (*bt)); +} + +int +battle_ok(const struct battle *bt) +{ + return bt && bt->state && bt->bar && bt->enemiesz && bt->team; +} + +void +battle_start(struct battle *bt) +{ + assert(bt); + + struct battle_entity *et; + + BATTLE_TEAM_FOREACH(bt, et) + if (battle_entity_ok(et)) + battle_entity_init(et); + BATTLE_ENEMY_FOREACH(bt, et) + if (battle_entity_ok(et)) + battle_entity_init(et); + + positionate_team(bt); + positionate_names(bt); + + /* Start the state "opening" animation. */ + battle_state_opening(bt); + + /* Play music if present. */ + if (bt->music[0]) + music_play(bt->music[0], MUSIC_LOOP); + + battle_order(bt); +} + +void +battle_switch(struct battle *bt, struct battle_state *st) +{ + assert(bt); + assert(st); + + if (bt->state) + battle_state_finish(bt->state, bt); + + bt->state = st; +} + +void +battle_order(struct battle *bt) +{ + struct battle_entity **porder; + + /* Create a pointer list to every entity. */ + bt->order = alloc_rearray0(bt->order, bt->ordersz, + bt->teamsz + bt->enemiesz, sizeof (*bt->order)); + bt->ordersz = bt->teamsz + bt->enemiesz; + bt->ordercur = porder = bt->order; + + for (size_t i = 0; i < bt->teamsz; ++i) + if (bt->team[i] && character_ok(bt->team[i]->ch)) + *porder++ = bt->team[i]; + for (size_t i = 0; i < bt->enemiesz; ++i) + if (bt->enemies[i] && character_ok(bt->enemies[i]->ch)) + *porder++ = bt->enemies[i]; + + /* Now sort. */ + qsort(bt->order, bt->ordersz, sizeof (*bt->order), cmp_order); +} + +struct battle_entity * +battle_current(const struct battle *bt) +{ + assert(bt); + + return *bt->ordercur; +} + +size_t +battle_index(const struct battle *bt) +{ + assert(bt); + + return bt->ordercur - bt->order; +} + +void +battle_attack(struct battle *bt, + struct character *source, + struct character *target) +{ + assert(bt); + assert(character_ok(source)); + + /* Target is empty? select randomly. */ + if (!target) { + if (is_team(bt, source)) + target = random_select(bt->enemies, bt->enemiesz)->ch; + else + target = random_select(bt->team, bt->teamsz)->ch; + } + + battle_state_attacking(battle_find(bt, source), battle_find(bt, target), bt); +} + +void +battle_cast(struct battle *bt, + struct character *source, + const struct spell *spell, + const struct selection *selection) +{ + assert(bt); + assert(source); + assert(spell); + assert((unsigned int)source->mp >= spell->mp); + + /* TODO: animate. */ + source->mp -= spell->mp; + spell_action(spell, bt, source, selection); +} + +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); + + if (!bt->ordercur) + battle_order(bt); + else { + if (bt->ordercur - bt->order + (size_t)1U >= bt->ordersz) + battle_order(bt); + else + bt->ordercur++; + } + + if (is_team(bt, battle_current(bt)->ch)) { + battle_bar_start(bt->bar, bt); + battle_state_menu(bt); + } else + battle_state_ai(bt); +} + +struct battle_entity * +battle_find(struct battle *bt, const struct character *ch) +{ + assert(bt); + + return find(bt, ch); +} + +void +battle_indicator_hp(struct battle *bt, const struct character *target, long amount) +{ + assert(bt); + assert(target); + + const struct battle_entity *et = find(bt, target); + struct indicator *id; + + if (!(bt->effects)) { + tracef("unable to add id without a drawable_stack"); + return; + } + + id = alloc_new0(sizeof (*id)); + id->bti.color = BATTLE_INDICATOR_HP_COLOR; + id->bti.amount = labs(amount); + + /* TODO: positionate better. */ + id->dw.x = et->x + target->sprites[CHARACTER_SPRITE_NORMAL]->cellw; + id->dw.y = et->y + target->sprites[CHARACTER_SPRITE_NORMAL]->cellh; + id->dw.data = id; + id->dw.update = indicator_update; + id->dw.draw = indicator_draw; + id->dw.finish = indicator_free; + + battle_indicator_start(&id->bti); + + if (drawable_stack_add(bt->effects, &id->dw) < 0) + drawable_finish(&id->dw); +} + +void +battle_handle_component(struct battle *bt, const union event *ev, enum battle_component comp) +{ + assert(bt); + assert(ev); + + if (comp & BATTLE_COMPONENT_BAR) + battle_bar_handle(bt->bar, bt, ev); + if ((comp & BATTLE_COMPONENT_ACTIONS) && bt->actions) + action_stack_handle(bt->actions, ev); +} + +void +battle_handle(struct battle *bt, const union event *ev) +{ + assert(bt); + assert(ev); + + battle_state_handle(bt->state, bt, ev); +} + +void +battle_update_component(struct battle *bt, unsigned int ticks, enum battle_component comp) +{ + assert(bt); + + if (comp & BATTLE_COMPONENT_ENTITIES) { + update_entities(bt->team, bt->teamsz, ticks); + update_entities(bt->enemies, bt->enemiesz, ticks); + } + if (comp & BATTLE_COMPONENT_BAR) + battle_bar_update(bt->bar, bt, ticks); + if ((comp & BATTLE_COMPONENT_ACTIONS) && bt->actions) + action_stack_update(bt->actions, ticks); + if ((comp & BATTLE_COMPONENT_DRAWABLES) && bt->effects) + drawable_stack_update(bt->effects, ticks); +} + +int +battle_update(struct battle *bt, unsigned int ticks) +{ + assert(bt && bt->state); + + return battle_state_update(bt->state, bt, ticks); +} + +void +battle_draw_component(const struct battle *bt, enum battle_component comp) +{ + assert(bt); + + if ((comp & BATTLE_COMPONENT_BACKGROUND) && texture_ok(bt->background)) + texture_scale(bt->background, + 0, 0, bt->background->w, bt->background->h, + 0, 0, window.w, window.h, + 0.f); + if (comp & BATTLE_COMPONENT_ENTITIES) { + draw_entities(bt, bt->team, bt->teamsz); + draw_entities(bt, bt->enemies, bt->enemiesz); + } + if (comp & BATTLE_COMPONENT_BAR) + battle_bar_draw(bt->bar, bt); + if ((comp & BATTLE_COMPONENT_ACTIONS) && bt->actions) + action_stack_draw(bt->actions); + if ((comp & BATTLE_COMPONENT_DRAWABLES) && bt->effects) + drawable_stack_draw(bt->effects); +} + +void +battle_draw(const struct battle *bt) +{ + assert(battle_ok(bt)); + + battle_state_draw(bt->state, bt); +} + +void +battle_finish(struct battle *bt) +{ + assert(bt); + + if (bt->state) + battle_state_finish(bt->state, bt); + + free(bt->order); + memset(bt, 0, sizeof (*bt)); +}