Mercurial > molko
changeset 192:4ad7420ab678
rpg: add minimalist battle system, continue #2477 @60h
- Implement battle as states,
- Add basic support for spells (no calculation yet),
- Add won/lost state,
- Add animations and messages,
- Add order.
line wrap: on
line diff
--- a/examples/CMakeLists.txt Sat Nov 07 15:40:34 2020 +0100 +++ b/examples/CMakeLists.txt Sat Nov 07 16:00:39 2020 +0100 @@ -19,6 +19,24 @@ project(examples) molko_define_executable( + TARGET example-battle + SOURCES + ${examples_SOURCE_DIR}/example-battle.c + ${examples_SOURCE_DIR}/battle/spell-fire.c + ${examples_SOURCE_DIR}/battle/spell-fire.h + ${examples_SOURCE_DIR}/battle/registry.c + ${examples_SOURCE_DIR}/battle/registry.h + FOLDER examples + ASSETS + ${examples_SOURCE_DIR}/assets/images/haunted-wood.png + ${examples_SOURCE_DIR}/assets/images/black-cat.png + ${examples_SOURCE_DIR}/assets/sprites/cursor.png + ${examples_SOURCE_DIR}/assets/sprites/explosion.png + ${examples_SOURCE_DIR}/assets/sounds/fire.wav + LIBRARIES librpg libadventure +) + +molko_define_executable( TARGET example-action SOURCES example-action.c FOLDER examples
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/battle/character-john.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,48 @@ +/* + * character-john.c -- john character + * + * 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 <rpg/character.h> + +#include "character-john.h" +#include "spell-fire.h" +#include "registry.h" + +static void +adventurer_reset(struct character *ch) +{ + /* TODO: this function should compute the attack thanks to the level. */ + ch->hpmax = 120; + ch->mpmax = 50; + ch->atk = 50; + ch->def = 50; + ch->agt = 50; + ch->luck = 50; +} + +const struct character character_john = { + .name = "John ", + .type = "Adventurer", + .level = 1, + .hp = 120, + .mp = 50, + .reset = adventurer_reset, + .sprite = ®istry_sprites[REGISTRY_TEXTURE_JOHN], + .spells = { + &spell_fire + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/battle/character-john.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,24 @@ +/* + * character-john.h -- john character + * + * 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. + */ + +#ifndef EXAMPLES_BATTLE_CHARACTER_JOHN_H +#define EXAMPLES_BATTLE_CHARACTER_JOHN_H + +const struct character character_john; + +#endif /* !EXAMPLES_BATTLE_CHARACTER_JOHN_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/battle/registry.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,111 @@ +/* + * registry.h -- registry of resources + * + * 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 <stddef.h> + +#include <core/image.h> +#include <core/panic.h> +#include <core/util.h> + +#include <adventure/assets/sprites/john.h> + +#include <assets/images/haunted-wood.h> +#include <assets/images/black-cat.h> + +#include <assets/sprites/cursor.h> +#include <assets/sprites/explosion.h> + +#include <assets/sounds/fire.h> + +#include "registry.h" + +struct texture registry_textures[REGISTRY_TEXTURE_NUM]; +struct sprite registry_sprites[REGISTRY_TEXTURE_NUM]; +struct sound registry_sounds[REGISTRY_SOUND_NUM]; + +#define REGISTRY_TEXTURE(s, ptr, cw, ch) \ + { (s), (ptr), sizeof ((ptr)), (cw), (ch) } + +static const struct { + enum registry_texture index; + const void *data; + size_t datasz; + unsigned int cellw; + unsigned int cellh; +} textures[] = { + REGISTRY_TEXTURE(REGISTRY_TEXTURE_CURSOR, sprites_cursor, 24, 24), + REGISTRY_TEXTURE(REGISTRY_TEXTURE_EXPLOSION, sprites_explosion, 256, 256), + REGISTRY_TEXTURE(REGISTRY_TEXTURE_JOHN, sprites_john, 48, 48), + REGISTRY_TEXTURE(REGISTRY_TEXTURE_HAUNTED_WOOD, images_haunted_wood, 0, 0), + REGISTRY_TEXTURE(REGISTRY_TEXTURE_BLACK_CAT, images_black_cat, 0, 0) +}; + +#define REGISTRY_SOUND(s, ptr) \ + { (s), (ptr), sizeof ((ptr)) } + +static const struct { + enum registry_sound index; + const void *data; + size_t datasz; +} sounds[] = { + REGISTRY_SOUND(REGISTRY_SOUND_FIRE, sounds_fire) +}; + +static void +load_textures_and_sprites(void) +{ + for (size_t i = 0; i < NELEM(textures); ++i) { + struct texture *texture = ®istry_textures[textures[i].index]; + struct sprite *sprite = ®istry_sprites[textures[i].index]; + + if (!image_openmem(texture, textures[i].data, textures[i].datasz)) + panic(); + + if (textures[i].cellw == 0 || textures[i].cellh == 0) + sprite_init(sprite, texture, texture->w, texture->h); + else + sprite_init(sprite, texture, textures[i].cellw, textures[i].cellh); + } +} + +static void +load_sounds(void) +{ + for (size_t i = 0; i < NELEM(sounds); ++i) { + struct sound *sound = ®istry_sounds[sounds[i].index]; + + if (!sound_openmem(sound, sounds[i].data, sounds[i].datasz)) + panic(); + } +} + +void +registry_init(void) +{ + load_textures_and_sprites(); + load_sounds(); +} + +void +registry_finish(void) +{ + for (size_t i = 0; i < NELEM(registry_textures); ++i) + texture_finish(®istry_textures[i]); + for (size_t i = 0; i < NELEM(registry_sounds); ++i) + sound_finish(®istry_sounds[i]); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/battle/registry.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,59 @@ +/* + * registry.h -- registry of resources + * + * 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. + */ + +#ifndef EXAMPLES_BATTLE_REGISTRY_H +#define EXAMPLES_BATTLE_REGISTRY_H + +#include <core/sound.h> +#include <core/sprite.h> +#include <core/texture.h> + +enum registry_texture { + /* UI. */ + REGISTRY_TEXTURE_CURSOR, + + /* Animations. */ + REGISTRY_TEXTURE_EXPLOSION, + + /* Characters. */ + REGISTRY_TEXTURE_JOHN, + + /* Enemies. */ + REGISTRY_TEXTURE_HAUNTED_WOOD, + REGISTRY_TEXTURE_BLACK_CAT, + + /* Unused.*/ + REGISTRY_TEXTURE_NUM +}; + +enum registry_sound { + REGISTRY_SOUND_FIRE, + REGISTRY_SOUND_NUM +}; + +extern struct texture registry_textures[REGISTRY_TEXTURE_NUM]; +extern struct sprite registry_sprites[REGISTRY_TEXTURE_NUM]; +extern struct sound registry_sounds[REGISTRY_SOUND_NUM]; + +void +registry_init(void); + +void +registry_finish(void); + +#endif /* !EXAMPLES_BATTLE_REGISTRY_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/battle/spell-fire.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,116 @@ +/* + * spell-fire.c -- example of spell: fire + * + * 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 <stdlib.h> + +#include <core/action.h> +#include <core/animation.h> +#include <core/alloc.h> + +#include <ui/align.h> + +#include <rpg/battle.h> +#include <rpg/character.h> +#include <rpg/spell.h> + +#include "registry.h" +#include "spell-fire.h" + +struct data { + struct battle *battle; + struct animation animation; + struct action action; + unsigned int selection; +}; + +static bool +update(struct action *act, unsigned int ticks) +{ + struct data *data = act->data; + + return animation_update(&data->animation, ticks); +} + +static void +draw(struct action *act) +{ + const struct data *data = act->data; + const struct battle_entity *et = &data->battle->enemies[data->selection]; + int x, y; + + align(ALIGN_CENTER, + &x, &y, data->animation.sprite->cellw, data->animation.sprite->cellh, + et->x, et->y, et->ch->sprite->cellw, et->ch->sprite->cellh); + + animation_draw(&data->animation, x, y); +} + +static void +end(struct action *act) +{ + struct data *data = act->data; + struct character *ch = data->battle->enemies[data->selection].ch; + + /* TODO: compute damage. */ + const unsigned int damage = 100; + if ((unsigned int)ch->hp < damage) + ch->hp = 0; + else + ch->hp -= damage; + + battle_indicator_hp(data->battle, data->battle->enemies[data->selection].ch, 100); +} + +static void +finish(struct action *act) +{ + free(act->data); +} + +static void +fire_action(struct battle *bt, struct character *owner, unsigned int selection) +{ + struct data *data; + + (void)owner; + + data = alloc_zero(1, sizeof (*data)); + data->battle = bt; + data->selection = selection; + data->action.data = data; + data->action.update = update; + data->action.draw = draw; + data->action.finish = finish; + data->action.end = end; + + animation_init(&data->animation, ®istry_sprites[REGISTRY_TEXTURE_EXPLOSION], 12); + animation_start(&data->animation); + + sound_play(®istry_sounds[REGISTRY_SOUND_FIRE]); + + action_stack_add(&bt->actions[0], &data->action); +} + +const struct spell spell_fire = { + .name = "Fire", + .description = "A delicate fire.", + .mp = 5, + .type = SPELL_TYPE_FIRE, + .selection = SELECTION_ENEMY_ONE, + .action = fire_action +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/battle/spell-fire.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,24 @@ +/* + * spell-fire.c -- example of spell: fire + * + * 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. + */ + +#ifndef EXAMPLES_BATTLE_SPELL_FIRE_H +#define EXAMPLES_BATTLE_SPELL_FIRE_H + +extern const struct spell spell_fire; + +#endif /* !EXAMPLES_BATTLE_SPELL_FIRE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/example-battle.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,266 @@ +/* + * example-battle.c -- show how to use battle + * + * 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/clock.h> +#include <core/core.h> +#include <core/event.h> +#include <core/image.h> +#include <core/painter.h> +#include <core/panic.h> +#include <core/sprite.h> +#include <core/sys.h> +#include <core/texture.h> +#include <core/util.h> +#include <core/window.h> + +#include <ui/align.h> +#include <ui/label.h> +#include <ui/theme.h> +#include <ui/ui.h> + +#include <rpg/character.h> +#include <rpg/battle.h> +#include <rpg/rpg.h> +#include <rpg/spell.h> + +#include "battle/registry.h" +#include "battle/spell-fire.h" + +#define W 1280 +#define H 720 + +static void +adventurer_reset(struct character *ch) +{ + /* TODO: this function should compute the attack thanks to the level. */ + ch->hpmax = 120; + ch->mpmax = 50; + ch->atk = 50; + ch->def = 50; + ch->agt = 50; + ch->luck = 50; +} + +static void +haunted_wood_reset(struct character *ch) +{ + ch->hpmax = ch->hp = 2000; + ch->mpmax = ch->mp = 250; + ch->atk = 178; + ch->def = 80; + ch->agt = 80; + ch->luck = 100; +} + +static void +black_cat_reset(struct character *ch) +{ + ch->hpmax = ch->hp = 126; + ch->mpmax = ch->mp = 38; + ch->atk = 22; + ch->def = 19; + ch->agt = 21; + ch->luck = 14; +} + +static struct character team[] = { + { + .name = "Molko", + .type = "Adventurer", + .level = 1, + .hp = 120, + .mp = 50, + .reset = adventurer_reset, + .sprite = ®istry_sprites[REGISTRY_TEXTURE_JOHN], + .spells = { + &spell_fire + } + }, + { + .name = "Fake Molko", + .type = "Adventurer", + .level = 1, + .hp = 120, + .mp = 50, + .reset = adventurer_reset, + .sprite = ®istry_sprites[REGISTRY_TEXTURE_JOHN], + .spells = { + &spell_fire + } + } +}; + +static void +haunted_wood_strat(struct character *ch, struct battle *bt) +{ + (void)ch; + + /* TODO: Select randomly. */ + battle_attack(bt, bt->order_cur->ch, bt->team[0].ch); +} + +static void +black_cat_strat(struct character *ch, struct battle *bt) +{ + (void)ch; + + /* TODO: Select randomly. */ + battle_attack(bt, bt->order_cur->ch, bt->team[0].ch); +} + +static struct character haunted_wood = { + .name = "Haunted Wood", + .type = "Wood", + .level = 30, + .reset = haunted_wood_reset, + .sprite = ®istry_sprites[REGISTRY_TEXTURE_HAUNTED_WOOD], + .exec = haunted_wood_strat +}; + +static struct character black_cat = { + .name = "Black Cat", + .type = "Cat", + .level = 6, + .reset = black_cat_reset, + .sprite = ®istry_sprites[REGISTRY_TEXTURE_BLACK_CAT], + .exec = black_cat_strat +}; + +static void +init(void) +{ + struct clock clock = {0}; + + if (!core_init() || !ui_init() || !rpg_init()) + panic(); + if (!window_open("Example - Battle", W, H)) + panic(); + + /* Wait 1 second to let all events processed. */ + while (clock_elapsed(&clock) < 1000) + for (union event ev; event_poll(&ev); ) + if (ev.type == EVENT_QUIT) + return; + + registry_init(); + + /* Set cursor in default theme. */ + theme_default()->sprites[THEME_SPRITE_CURSOR] = ®istry_sprites[REGISTRY_TEXTURE_CURSOR]; +} + +static void +prepare_to_fight(struct battle *bt) +{ + assert(bt); + +// bt->enemies[0].ch = &haunted_wood; + bt->team[0].ch = &team[0]; + bt->team[1].ch = &team[1]; + + /* Positionate the single ennemy to the left. */ + align(ALIGN_LEFT, + &bt->enemies[0].x, &bt->enemies[0].y, haunted_wood.sprite->cellw, haunted_wood.sprite->cellh, + 0, 0, window.w, window.h); + + /* Black cat is near the previous monster. */ + bt->enemies[1].ch = &black_cat; + bt->enemies[1].x = 500; + bt->enemies[1].y = 100; + + battle_start(bt); +} + +static void +run(void) +{ + struct clock clock; + struct battle battle = {0}; + struct label info = { + .text = "Press <Space> to start a battle.", + .x = 10, + .y = 10, + .flags = LABEL_FLAGS_SHADOW + }; + + clock_start(&clock); + + for (;;) { + unsigned int elapsed = clock_elapsed(&clock); + + clock_start(&clock); + + for (union event ev; event_poll(&ev); ) { + switch (ev.type) { + case EVENT_QUIT: + return; + case EVENT_KEYDOWN: + if (ev.key.key == KEY_SPACE && !battle.state) + prepare_to_fight(&battle); + else if (battle.state) + battle_handle(&battle, &ev); + break; + default: + if (battle.state) + battle_handle(&battle, &ev); + break; + } + } + + if (battle.state) { + if (battle_update(&battle, elapsed)) + battle_finish(&battle); + else + battle_draw(&battle); + } else { + painter_set_color(0x4f8fbaff); + painter_clear(); + label_draw(&info); + } + + painter_present(); + + if ((elapsed = clock_elapsed(&clock)) < 20) + delay(1); + } +} + +static void +quit(void) +{ + registry_finish(); + theme_finish(); + window_finish(); + sys_finish(); +} + +int +main(int argc, char **argv) +{ + --argc; + ++argv; + + init(); + run(); + quit(); + + return 0; +}
--- a/examples/example-gridmenu.c Sat Nov 07 15:40:34 2020 +0100 +++ b/examples/example-gridmenu.c Sat Nov 07 16:00:39 2020 +0100 @@ -99,7 +99,7 @@ } } - if (menu.state == GRIDMENU_STATE_SELECTED) { + if (menu.state == GRIDMENU_STATE_ACTIVATED) { tracef("selected index: %u", (unsigned int)menu.selected); gridmenu_reset(&menu); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcore/core/spell.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,19 @@ +/* + * character.h -- character definition + * + * 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 "spell.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcore/core/spell.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,92 @@ +/* + * spell.h -- magic spells + * + * 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. + */ + +#ifndef MOLKO_SPELL_H +#define MOLKO_SPELL_H + +/** + * \file spell.h + * \brief Magic spells. + */ + +struct character; +struct battle; + +/** + * \brief Kind of spell. + */ +enum spell_type { + SPELL_TYPE_NEUTRAL, /*!< No type. */ + SPELL_TYPE_FIRE, /*!< Fire (affected by attack). */ + SPELL_TYPE_WIND, /*!< Wind (affected by agility). */ + SPELL_TYPE_WATER, /*!< Water (affected by luck). */ + SPELL_TYPE_EARTH, /*!< Earth (affected by defense). */ + SPELL_TYPE_CHAOS, /*!< Chaotic. */ + SPELL_TYPE_HOLY, /*!< Holy. */ + SPELL_TYPE_TIME /*!< Chrono. */ +}; + +/** + * \brief Kind of selection. + */ +enum spell_selection { + SPELL_SELECTION_SELF, /*!< Owner only. */ + SPELL_SELECTION_TEAM_ONE, /*!< One member of the team. */ + SPELL_SELECTION_TEAM_ALL, /*!< All members of the team. */ + SPELL_SELECTION_TEAM_COMBINED, /*!< One or all members of the team. */ + SPELL_SELECTION_ENEMY_ONE, /*!< One enemy. */ + SPELL_SELECTION_ENEMY_ALL, /*!< All enemies. */ + SPELL_SELECTION_ENEMY_COMBINED /*!< One or all enemies. */ +}; + +/** + * \brief Spell structure. + * + * A spell is a magical object owned by a character and can be used in a battle + * and/or outside of a battle. It costs a certain amount of magic points and is + * typed into a category (earth, fire, etc, …). + * + * A spell can select one character or all. + */ +struct spell { + const char *name; /*!< (RO) Spell name. */ + const char *description; /*!< (RO) Long description. */ + unsigned int mp; /*!< (RO) Number of MP required. */ + enum spell_type type; /*!< (RO) Kind of spell. */ + + /** + * Execute the spell in a battle. + * + * \param bt the current battle + * \param owner the spell owner + * \param selection the selection flags + */ + void (*action)(struct battle *bt, struct character *owner, int selection); + + /** + * Use the spell outside of a battle. + * + * This function is optional. + * + * \param owner the spell owner + * \param selection the selection flags + */ + void (*use)(struct character *owner, int selection); +}; + +#endif /* !MOLKO_SPELL_H */
--- a/libcore/core/sprite.c Sat Nov 07 15:40:34 2020 +0100 +++ b/libcore/core/sprite.c Sat Nov 07 16:00:39 2020 +0100 @@ -47,7 +47,7 @@ } bool -sprite_draw(struct sprite *sprite, unsigned int r, unsigned int c, int x, int y) +sprite_draw(const struct sprite *sprite, unsigned int r, unsigned int c, int x, int y) { assert(sprite_ok(sprite)); assert(r < sprite->nrows);
--- a/libcore/core/sprite.h Sat Nov 07 15:40:34 2020 +0100 +++ b/libcore/core/sprite.h Sat Nov 07 16:00:39 2020 +0100 @@ -106,6 +106,6 @@ * \return False in case of rendering error. */ bool -sprite_draw(struct sprite *sprite, unsigned int r, unsigned int c, int x, int y); +sprite_draw(const struct sprite *sprite, unsigned int r, unsigned int c, int x, int y); #endif /* !MOLKO_SPRITE_H */
--- a/librpg/CMakeLists.txt Sat Nov 07 15:40:34 2020 +0100 +++ b/librpg/CMakeLists.txt Sat Nov 07 16:00:39 2020 +0100 @@ -20,6 +20,34 @@ set( SOURCES + ${librpg_SOURCE_DIR}/rpg/battle.c + ${librpg_SOURCE_DIR}/rpg/battle.h + ${librpg_SOURCE_DIR}/rpg/battle-indicator.c + ${librpg_SOURCE_DIR}/rpg/battle-indicator.h + ${librpg_SOURCE_DIR}/rpg/battle-bar.c + ${librpg_SOURCE_DIR}/rpg/battle-bar.h + ${librpg_SOURCE_DIR}/rpg/battle-state.c + ${librpg_SOURCE_DIR}/rpg/battle-state.h + ${librpg_SOURCE_DIR}/rpg/battle-state-ai.c + ${librpg_SOURCE_DIR}/rpg/battle-state-ai.h + ${librpg_SOURCE_DIR}/rpg/battle-state-check.c + ${librpg_SOURCE_DIR}/rpg/battle-state-check.h + ${librpg_SOURCE_DIR}/rpg/battle-state-closing.c + ${librpg_SOURCE_DIR}/rpg/battle-state-closing.h + ${librpg_SOURCE_DIR}/rpg/battle-state-menu.c + ${librpg_SOURCE_DIR}/rpg/battle-state-menu.h + ${librpg_SOURCE_DIR}/rpg/battle-state-lost.c + ${librpg_SOURCE_DIR}/rpg/battle-state-lost.h + ${librpg_SOURCE_DIR}/rpg/battle-state-opening.c + ${librpg_SOURCE_DIR}/rpg/battle-state-opening.h + ${librpg_SOURCE_DIR}/rpg/battle-state-selection.c + ${librpg_SOURCE_DIR}/rpg/battle-state-selection.h + ${librpg_SOURCE_DIR}/rpg/battle-state-sub.c + ${librpg_SOURCE_DIR}/rpg/battle-state-sub.h + ${librpg_SOURCE_DIR}/rpg/battle-state-victory.c + ${librpg_SOURCE_DIR}/rpg/battle-state-victory.h + ${librpg_SOURCE_DIR}/rpg/character.c + ${librpg_SOURCE_DIR}/rpg/character.h ${librpg_SOURCE_DIR}/rpg/inventory.c ${librpg_SOURCE_DIR}/rpg/inventory.h ${librpg_SOURCE_DIR}/rpg/item.c @@ -32,6 +60,9 @@ ${librpg_SOURCE_DIR}/rpg/message.h ${librpg_SOURCE_DIR}/rpg/rpg.c ${librpg_SOURCE_DIR}/rpg/rpg.h + ${librpg_SOURCE_DIR}/rpg/selection.h + ${librpg_SOURCE_DIR}/rpg/spell.c + ${librpg_SOURCE_DIR}/rpg/spell.h ${librpg_SOURCE_DIR}/rpg/walksprite.c ${librpg_SOURCE_DIR}/rpg/walksprite.h )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-bar.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,356 @@ +/* + * 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" + +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) +{ + static 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) +{ + 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)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-bar.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,186 @@ +/* + * 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. + */ + +#ifndef MOLKO_BATTLE_BAR +#define MOLKO_BATTLE_BAR + +/** + * \file battle-bar.h + * \brief Battle status bar and menu. + * + * This module is a view and user input for the game battle. It is used by the + * \ref battle.h object. + */ + +#include <stdbool.h> + +#include <core/plat.h> + +#include <ui/frame.h> +#include <ui/gridmenu.h> + +struct battle; +struct character; + +union event; + +/** + * \brief Main menu selection. + */ +enum battle_bar_menu { + BATTLE_BAR_MENU_ATTACK = 0, /*!< Attack. */ + BATTLE_BAR_MENU_MAGIC = 1, /*!< Cast a spell. */ + BATTLE_BAR_MENU_OBJECTS = 2, /*!< Use an object*/ + BATTLE_BAR_MENU_SPECIAL = 3 /*!< Use a special character action. */ +}; + +/** + * \brief Bar state. + */ +enum battle_bar_state { + BATTLE_BAR_STATE_NONE, /*!< No state yet. */ + BATTLE_BAR_STATE_MENU, /*!< Main menu selection. */ + BATTLE_BAR_STATE_SUB /*!< Sub grid menu opened. */ +}; + +/** + * \brief Battle bar. + * + * The bar is split into three individual pieces. + * + * ``` + * +------------+--------+------------+ + * | Grid menu | Menu | Status | + * +------------+--------+------------+ + * ``` + * + * The left grid menu is only shown when member field state is set to \ref + * BATTLE_BAR_STATE_SUB, it is usually opened when user select a magic or an + * object. + * + * The menu in the middle is the main selection and contains the enumeration + * \ref battle_bar_menu using top, right, bottom left edges for short selection. + * + * The last part on the right is the status pane where the team characters stats + * are listed. + * + * By itself, the bar never modify the battle object, it is the responsability + * of the battle object or the battle state to change the bar object and to + * track change on events. As such, the battle object is always passed as const + * parameter. + */ +struct battle_bar { + int x; /*!< (+) Position in x. */ + int y; /*!< (+) Position in y. */ + unsigned int w; /*!< (+) Width. */ + unsigned int h; /*!< (+) Height. */ + enum battle_bar_state state; /*!< (-) Current state. */ + + /* Right status frame. */ + struct frame status_frame; /*!< (-) Right status frame. */ + + /* Main menu selection. */ + struct frame menu_frame; /*!< (-) Main menu frame. */ + enum battle_bar_menu menu; /*!< (-) Main menu selection. */ + + /* Sub menu selection (spells/objects). */ + struct gridmenu sub_grid; /*!< (-) Sub menu object. */ +}; + +/** + * Repositionate every elements in the bar. + * + * Call this function at least once, when you have set the x, y, w and h fields + * of the bar. + * + * \pre bar != NULL + * \pre bt != NULL + * \param bar the bar to set + * \param bt the current battle + */ +void +battle_bar_positionate(struct battle_bar *bar, const struct battle *bt); + +/** + * Handle an event. + * + * Depending on the current bar state, it will either update the main menu or + * the sub menu if opened. The function returns true if an element is considered + * activated (usually with the return key). + * + * You must not ignore the return value because the battle state should change. + * + * \pre bar != NULL + * \pre bt != NULL + * \pre ev != NULL + * \param bar this bar + * \param bt the current battle + * \param ev the current event + * \return True if an element has been activated. + */ +bool +battle_bar_handle(struct battle_bar *bar, + const struct battle *bt, + const union event *ev) PLAT_NODISCARD; + +/** + * Reset the battle bar selection, state and grid. + * + * Call this function each time a new player is selected. + * + * \pre bar != NULL + * \param bar the bar to reset + */ +void +battle_bar_reset(struct battle_bar *bar); + +void +battle_bar_open_menu(struct battle_bar *bar); + +/** + * Open the view to select a spell by opening the grid menu. + * + * \pre bar != NULL + * \pre character_ok(ch) + * \param bar this bar + * \param ch the owner of spells + */ +void +battle_bar_open_spells(struct battle_bar *bar, const struct battle *bt, struct character *ch); + +/** + * Draw the bar. + * + * \pre bar != NULL + * \pre bt != NULL + * \param bar the bar to draw + * \param bt the current battle + */ +void +battle_bar_draw(const struct battle_bar *bar, const struct battle *bt); + +/** + * Close internal resources if necessary. + * + * \pre bar != NULL + * \param bar the bar to clear + */ +void +battle_bar_finish(struct battle_bar *bar); + +#endif /* !MOLKO_BATTLE_BAR */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-indicator.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,134 @@ +/* + * battle-indicator.c -- drawable for rendering a hp/mp count usage + * + * 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 <math.h> +#include <stdio.h> +#include <string.h> + +#include <core/color.h> +#include <core/font.h> +#include <core/panic.h> + +#include <ui/theme.h> + +#include "battle-indicator.h" + +#define THEME(bti) ((bti)->theme ? (bti)->theme : theme_default()) +#define STEP (2) +#define DELAY (5) + +static inline unsigned int +inc(int cmp, int tgt) +{ + return tgt > cmp ? fmin(cmp + STEP, tgt) : fmax(cmp - STEP, tgt); +} + +static inline bool +colored(const struct battle_indicator *bti) +{ + /* Only check r, g, b and ignore alpha. */ + return COLOR_R(bti->cur) == COLOR_R(bti->color) && + COLOR_G(bti->cur) == COLOR_G(bti->color) && + COLOR_B(bti->cur) == COLOR_B(bti->color); +} + +void +battle_indicator_start(struct battle_indicator *bti) +{ + assert(bti); + + char buf[128]; + const struct theme *theme = THEME(bti); + + snprintf(buf, sizeof (buf), "%u", bti->amount); + + bti->cur = 0xffffffff; + bti->elapsed = 0; + bti->alpha = 250; + + if (!font_render(theme->fonts[THEME_FONT_INTERFACE], &bti->tex[0], buf, bti->cur) || + !font_render(theme->fonts[THEME_FONT_INTERFACE], &bti->tex[1], buf, 0x000000ff)) + panic(); +} + +bool +battle_indicator_completed(const struct battle_indicator *bti) +{ + assert(battle_indicator_ok(bti)); + + return colored(bti) && bti->alpha == 0; +} + +bool +battle_indicator_ok(const struct battle_indicator *bti) +{ + return bti && texture_ok(&bti->tex[0]) && texture_ok(&bti->tex[1]); +} + +bool +battle_indicator_update(struct battle_indicator *bti, unsigned int ticks) +{ + assert(battle_indicator_ok(bti)); + + bti->elapsed += ticks; + + if (bti->elapsed > DELAY) { + bti->elapsed = 0; + + if (!colored(bti)) { + /* Update colors first. */ + bti->cur = COLOR_HEX( + inc(COLOR_R(bti->cur), COLOR_R(bti->color)), + inc(COLOR_G(bti->cur), COLOR_G(bti->color)), + inc(COLOR_B(bti->cur), COLOR_B(bti->color)), + 255 + ); + + texture_set_color_mod(&bti->tex[0], bti->cur); + } else { + /* Update alpha next. */ + bti->alpha -= 10; + + texture_set_alpha_mod(&bti->tex[0], bti->alpha); + texture_set_alpha_mod(&bti->tex[1], bti->alpha); + } + } + + return battle_indicator_completed(bti); +} + +void +battle_indicator_draw(const struct battle_indicator *bti, int x, int y) +{ + assert(battle_indicator_ok(bti)); + + texture_draw(&bti->tex[1], x + 1, y + 1); + texture_draw(&bti->tex[0], x, y); +} + +void +battle_indicator_finish(struct battle_indicator *bti) +{ + assert(bti); + + texture_finish(&bti->tex[0]); + texture_finish(&bti->tex[1]); + + memset(bti, 0, sizeof (*bti)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-indicator.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,122 @@ +/* + * battle-indicator.h -- drawable for rendering a hp/mp count usage + * + * 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. + */ + +#ifndef MOLKO_BATTLE_INDICATOR_H +#define MOLKO_BATTLE_INDICATOR_H + +/** + * \file battle-indicator.h + * \brief Drawable for rendering a hp/mp count usage. + * + * This small module creates an animation drawable that shows a label with a + * smooth color/fading transition, used to display hp/mp damage. + */ + +#include <stdbool.h> + +#include <core/texture.h> + +/** + * \brief Default color for HP indicator. + */ +#define BATTLE_INDICATOR_HP_COLOR (0xa5303000) + +/** + * \brief Default color for MP indicator. + */ +#define BATTLE_INDICATOR_MP_COLOR (0xa23e8c00) + +struct theme; + +/** + * \brief Structure context for indicator animation. + * + * You must zero-initialize this structure and fill the color and amount field + * with the color target and the amount of damage to show respectively. Then use + * start, update and draw function in order until it completes. + */ +struct battle_indicator { + unsigned int color; /*!< (+) Destination color. */ + unsigned int amount; /*!< (+) Amount text to draw. */ + const struct theme *theme; /*!< (+&?) Optional theme to use. */ + unsigned int cur; /*!< (-) Current color target. */ + unsigned int elapsed; /*!< (-) Elapsed time in transition. */ + unsigned int alpha; /*!< (-) Current alpha. */ + struct texture tex[2]; /*!< (*) Rendered texture. */ +}; + +/** + * Start the battle indicator. You must call this function only once. + * + * \pre bti != NULL + * \param bti the indicator + */ +void +battle_indicator_start(struct battle_indicator *bti); + +/** + * Tells if the indicator has completed. + * + * \pre battle_indicator_ok(bti) + * \param bti the indicator to check + * \return True if completed. + */ +bool +battle_indicator_completed(const struct battle_indicator *bti); + +/** + * Tells if the indicator is valid. + * + * \param bti the indicator to check (may be NULL) + * \return True if correctly initialized. + */ +bool +battle_indicator_ok(const struct battle_indicator *bti); + +/** + * Update the indicator. + * + * \pre battle_indicator_ok(bti) + * \param bti the indicator + * \param ticks the elapsed milliseconds since last frame + * \return True if completed. + */ +bool +battle_indicator_update(struct battle_indicator *bti, unsigned int ticks); + +/** + * Draw the indicator. + * + * \pre battle_indicator_ok(bti) + * \param bti the indicator + * \param x the x coordinate + * \param y the y coordinate + */ +void +battle_indicator_draw(const struct battle_indicator *bti, int x, int y); + +/** + * Dispose resources. + * + * \pre battle_indicator_ok(bti) + * \param bti the indicator + */ +void +battle_indicator_finish(struct battle_indicator *bti); + +#endif /* !MOLKO_BATTLE_INDICATOR_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-ai.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,55 @@ +/* + * battle-state-ai.c -- battle state (enemy is playing) + * + * 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 "battle.h" +#include "battle-state.h" +#include "battle-state-ai.h" +#include "battle-state-check.h" +#include "character.h" + +static bool +update(struct battle_state *st, struct battle *bt, unsigned int ticks) +{ + (void)st; + (void)ticks; + + struct character *ch = bt->order_cur->ch; + + /* + * Immediately invoke the enemy exec strategy and put the battle state + * to check. + */ + character_exec(ch, bt); + battle_state_check(bt); + + return false; +} + +void +battle_state_ai(struct battle *bt) +{ + assert(bt); + + static struct battle_state self = { + .update = update + }; + + battle_switch(bt, &self); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-ai.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,34 @@ +/* + * battle-state-ai.h -- battle state (enemy is playing) + * + * 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. + */ + +#ifndef MOLKO_BATTLE_STATE_AI_H +#define MOLKO_BATTLE_STATE_AI_H + +struct battle; + +/** + * Switch to battle state AI. + * + * \pre bt != NULL + * \param bt the battle to change state + * \post bt->state->update is set + */ +void +battle_state_ai(struct battle *bt); + +#endif /* !MOLKO_BATTLE_STATE_AI_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-check.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,168 @@ +/* + * battle-state-check.c -- battle state (check status) + * + * 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 <stdlib.h> +#include <string.h> + +#include <core/alloc.h> +#include <core/panic.h> +#include <core/sprite.h> +#include <core/texture.h> + +#include "battle.h" +#include "battle-state.h" +#include "battle-state-check.h" +#include "battle-state-lost.h" +#include "battle-state-victory.h" +#include "character.h" + +struct fadeout { + struct character *ch; + int x; + int y; + struct action action; + unsigned int alpha; + unsigned int elapsed; +}; + +static bool +fadeout_update(struct action *act, unsigned int ticks) +{ + struct fadeout *fade = act->data; + struct character *ch = fade->ch; + + fade->elapsed += ticks; + + if (fade->elapsed >= 8) { + fade->elapsed = 0; + + if (fade->alpha == 0) { + return true; + } + + fade->alpha -= 10; + texture_set_alpha_mod(ch->sprite->texture, fade->alpha); + } + + return false; +} + +static void +fadeout_draw(struct action *act) +{ + const struct fadeout *fade = act->data; + + sprite_draw(fade->ch->sprite, 0, 0, fade->x, fade->y); +} + +static void +fadeout_finish(struct action *act) +{ + free(act->data); +} + +static void +fadeout(struct battle *bt, struct battle_entity *et) +{ + struct fadeout *fade; + + if (!(fade = alloc_zero(1, sizeof (*fade)))) + panic(); + + fade->ch = et->ch; + fade->x = et->x; + fade->y = et->y; + fade->alpha = 250; + fade->action.data = fade; + fade->action.draw = fadeout_draw; + fade->action.update = fadeout_update; + fade->action.finish = fadeout_finish; + + if (!action_stack_add(&bt->actions[1], &fade->action)) + free(fade); + + memset(et, 0, sizeof (*et)); +} + +static bool +is_dead(const struct battle *bt) +{ + const struct battle_entity *et; + + BATTLE_TEAM_FOREACH(bt, et) { + if (!character_ok(et->ch)) + continue; + if (et->ch->hp > 0) + return false; + } + + return true; +} + +static bool +is_won(const struct battle *bt) +{ + const struct battle_entity *et; + + BATTLE_ENEMY_FOREACH(bt, et) + if (character_ok(et->ch)) + return false; + + return true; +} + +static void +clean(struct battle *bt) +{ + struct battle_entity *et; + + BATTLE_ENEMY_FOREACH(bt, et) + if (character_ok(et->ch) && et->ch->hp == 0) + fadeout(bt, et); +} + +static bool +update(struct battle_state *st, struct battle *bt, unsigned int ticks) +{ + (void)st; + (void)ticks; + + clean(bt); + + if (is_dead(bt)) + battle_state_lost(bt); + else if (is_won(bt)) + battle_state_victory(bt); + else + battle_next(bt); + + return false; +} + +void +battle_state_check(struct battle *bt) +{ + assert(bt); + + static struct battle_state self = { + .update = update + }; + + battle_switch(bt, &self); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-check.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,34 @@ +/* + * battle-state-check.h -- battle state (check status) + * + * 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. + */ + +#ifndef MOLKO_BATTLE_STATE_CHECK_H +#define MOLKO_BATTLE_STATE_CHECK_H + +struct battle; + +/** + * Switch to battle state check. + * + * \pre bt != NULL + * \param bt the battle to change state + * \post bt->state->update is set + */ +void +battle_state_check(struct battle *bt); + +#endif /* !MOLKO_BATTLE_STATE_CHECK_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-closing.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,108 @@ +/* + * battle-state-closing.c -- battle state (closing) + * + * 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 <stdlib.h> + +#include <core/alloc.h> +#include <core/painter.h> +#include <core/panic.h> +#include <core/texture.h> +#include <core/window.h> + +#include "battle.h" +#include "battle-state-closing.h" + +struct closing { + struct battle_state self; + struct texture texture; + unsigned int alpha; + unsigned int elapsed; +}; + + +static bool +update(struct battle_state *st, struct battle *bt, unsigned int ticks) +{ + (void)bt; + + struct closing *closing = st->data; + + closing->elapsed += ticks; + + if (closing->elapsed > 8) { + closing->elapsed = 0; + + if (closing->alpha == 255) + return true; + + closing->alpha += 5; + texture_set_alpha_mod(&closing->texture, closing->alpha); + } + + return false; +} + +static void +draw(const struct battle_state *st, const struct battle *bt) +{ + (void)bt; + + const struct closing *closing = st->data; + + texture_draw(&closing->texture, 0, 0); +} + +static void +finish(struct battle_state *st, struct battle *bt) +{ + (void)bt; + + struct closing *closing = st->data; + + texture_finish(&closing->texture); + free(closing); +} + +void +battle_state_closing(struct battle *bt) +{ + assert(bt); + + struct closing *closing; + + if (!(closing = alloc_zero(1, sizeof (*closing))) || + !texture_new(&closing->texture, window.w, window.h)) + panic(); + + PAINTER_BEGIN(&closing->texture); + texture_set_blend_mode(&closing->texture, TEXTURE_BLEND_BLEND); + painter_set_color(0x000000ff); + painter_clear(); + painter_draw_rectangle(0, 0, window.w, window.h); + PAINTER_END(); + + texture_set_alpha_mod(&closing->texture, 0); + + closing->self.data = closing; + closing->self.update = update; + closing->self.draw = draw; + closing->self.finish = finish; + + battle_switch(bt, &closing->self); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-closing.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,33 @@ +/* + * battle-state-closing.h -- battle state (closing) + * + * 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. + */ + +#ifndef MOLKO_BATTLE_STATE_CLOSING_H +#define MOLKO_BATTLE_STATE_CLOSING_H + +struct battle; + +/** + * Switch to battle state closing. + * + * \pre bt != NULL + * \param bt the battle to change state + */ +void +battle_state_closing(struct battle *bt); + +#endif /* !MOLKO_BATTLE_STATE_CLOSING_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-lost.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,102 @@ +/* + * battle-state-lost.c -- battle state (lost) + * + * 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 <core/alloc.h> +#include <core/panic.h> +#include <core/window.h> + +#include <rpg/message.h> + +#include "battle.h" +#include "battle-state.h" +#include "battle-state-closing.h" +#include "battle-state-victory.h" + +struct lost { + struct battle_state self; + struct message msg; +}; + +static void +handle(struct battle_state *st, struct battle *bt, const union event *ev) +{ + (void)bt; + + struct lost *lost = st->data; + + message_handle(&lost->msg, ev); +} + +static bool +update(struct battle_state *st, struct battle *bt, unsigned int ticks) +{ + (void)bt; + + struct lost *lost = st->data; + + if (message_update(&lost->msg, ticks)) + battle_state_closing(bt); + + return false; +} + +static void +draw(const struct battle_state *st, const struct battle *bt) +{ + (void)bt; + + const struct lost *lost = st->data; + + message_draw(&lost->msg); +} + +void +battle_state_lost(struct battle *bt) +{ + assert(bt); + + struct lost *lost; + + if (!(lost = alloc_zero(1, sizeof (*lost)))) + panic(); + + lost->self.data = lost; + lost->self.handle = handle; + lost->self.update = update; + lost->self.draw = draw; + + lost->msg.text[0] = "You have been defeated..."; + lost->msg.theme = bt->theme; + lost->msg.flags = MESSAGE_FLAGS_AUTOMATIC | + MESSAGE_FLAGS_FADEIN | + MESSAGE_FLAGS_FADEOUT; + lost->msg.timeout = MESSAGE_TIMEOUT_DEFAULT; + lost->msg.delay = MESSAGE_DELAY_DEFAULT; + + message_start(&lost->msg); + message_query(&lost->msg, NULL, &lost->msg.h); + + lost->msg.w = window.w * 0.6; + lost->msg.y = window.h * 0.1; + lost->msg.x = (window.w - lost->msg.w) / 2; + + bt->status = BATTLE_STATUS_LOST; + battle_switch(bt, &lost->self); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-lost.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,33 @@ +/* + * battle-state-lost.h -- battle state (lost) + * + * 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. + */ + +#ifndef MOLKO_BATTLE_STATE_LOST_H +#define MOLKO_BATTLE_STATE_LOST_H + +struct battle; + +/** + * Switch to battle state lost. + * + * \pre bt != NULL + * \param bt the battle to change state + */ +void +battle_state_lost(struct battle *bt); + +#endif /* !MOLKO_BATTLE_STATE_LOST_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-menu.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,70 @@ +/* + * battle-state-menu.h -- battle state (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 <stdbool.h> + +#include "battle.h" +#include "battle-bar.h" +#include "battle-state-menu.h" +#include "battle-state-sub.h" +#include "battle-state.h" +#include "character.h" +#include "spell.h" + +static void +open_spells(struct battle *bt) +{ + battle_bar_open_spells(&bt->bar, bt, bt->order_cur->ch); + battle_state_sub(bt); +} + +static void +handle(struct battle_state *st, struct battle *bt, const union event *ev) +{ + (void)st; + + if (battle_bar_handle(&bt->bar, bt, ev)) { + switch (bt->bar.menu) { + case BATTLE_BAR_MENU_ATTACK: + /* TODO: do selection for attacking. */ + break; + case BATTLE_BAR_MENU_MAGIC: + open_spells(bt); + break; + case BATTLE_BAR_MENU_OBJECTS: + break; + case BATTLE_BAR_MENU_SPECIAL: + break; + default: + break; + } + } +} + +void +battle_state_menu(struct battle *bt) +{ + assert(bt); + + static struct battle_state self = { + .handle = handle, + }; + + battle_switch(bt, &self); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-menu.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,34 @@ +/* + * battle-state-menu.h -- battle state (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. + */ + +#ifndef MOLKO_BATTLE_STATE_MENU_H +#define MOLKO_BATTLE_STATE_MENU_H + +struct battle; + +/** + * Switch to battle state menu. + * + * \pre bt != NULL + * \param bt the battle to change state + * \post bt->state->handle is set + */ +void +battle_state_menu(struct battle *bt); + +#endif /* !MOLKO_BATTLE_STATE_MENU_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-opening.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,101 @@ +/* + * battle-state-opening.c -- battle state (opening) + * + * 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 <stdbool.h> +#include <stdlib.h> + +#include <core/alloc.h> +#include <core/painter.h> +#include <core/panic.h> +#include <core/window.h> + +#include "battle.h" +#include "battle-state.h" +#include "battle-state-opening.h" +#include "battle-state-menu.h" + +#define DELAY (1000U) + +struct opening { + struct battle_state self; + unsigned int elapsed; +}; + +static bool +update(struct battle_state *st, struct battle *bt, unsigned int ticks) +{ + assert(bt); + + struct opening *opening = st->data; + + opening->elapsed += ticks; + + /* + * Those function will effectively change state accordingly to the + * order of playing. + */ + if (opening->elapsed >= DELAY) { + battle_order(bt); + battle_next(bt); + } + + return false; +} + +static void +draw(const struct battle_state *st, const struct battle *bt) +{ + assert(bt); + + const struct opening *opening = st->data; + const unsigned int w = window.w; + const unsigned int h = window.h / 2; + const unsigned int ch = opening->elapsed * h / DELAY; + + /* Draw some bezels opening. */ + painter_set_color(0x000000ff); + painter_draw_rectangle(0, 0, w, h - ch); + painter_draw_rectangle(0, h + ch, w, h - ch); +} + +static void +finish(struct battle_state *st, struct battle *bt) +{ + (void)bt; + + free(st); +} + +void +battle_state_opening(struct battle *bt) +{ + assert(bt); + + struct opening *opening; + + if (!(opening = alloc_zero(1, sizeof (*opening)))) + panic(); + + opening->self.data = opening; + opening->self.update = update; + opening->self.draw = draw; + opening->self.finish = finish; + + battle_switch(bt, &opening->self); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-opening.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,37 @@ +/* + * battle-state-opening.h -- battle state (opening) + * + * 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. + */ + +#ifndef MOLKO_BATTLE_STATE_OPENING_H +#define MOLKO_BATTLE_STATE_OPENING_H + +struct battle; + +/** + * Switch to battle state opening. + * + * \pre bt != NULL + * \param bt the battle to change state + * \post bt->state->data is not NULL + * \post bt->state->update is set + * \post bt->state->draw is set + * \post bt->state->finish is set + */ +void +battle_state_opening(struct battle *bt); + +#endif /* !MOLKO_BATTLE_STATE_OPENING_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-selection.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,221 @@ +/* + * battle-state-selection.h -- battle state (selection) + * + * 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 <stdlib.h> + +#include <core/alloc.h> +#include <core/event.h> +#include <core/panic.h> +#include <core/sprite.h> +#include <core/util.h> + +#include <ui/theme.h> + +#include "battle.h" +#include "battle-bar.h" +#include "battle-state.h" +#include "battle-state-check.h" +#include "battle-state-menu.h" +#include "battle-state-selection.h" +#include "battle-state-sub.h" +#include "character.h" +#include "spell.h" + +struct select { + struct battle_state state; + enum selection type; + unsigned int selection; +}; + +static void +select_adj_in(struct select *select, const struct battle_entity entities[], size_t entitiesz, int step) +{ + assert(select->selection != (unsigned int)-1); + + unsigned int newselection = select->selection; + + if (step < 0) { + while (newselection > 0) { + if (character_ok(entities[--newselection].ch)) { + select->selection = newselection; + break; + } + } + } else { + while (newselection < entitiesz) { + if (character_ok(entities[++newselection].ch)) { + select->selection = newselection; + break; + } + } + } +} + +static void +select_adj(struct select *select, const struct battle *bt, int step) +{ + switch (select->type) { + case SELECTION_TEAM_ONE: + case SELECTION_TEAM_COMBINED: + select_adj_in(select, bt->team, NELEM(bt->team), step); + break; + case SELECTION_ENEMY_ONE: + case SELECTION_ENEMY_COMBINED: + select_adj_in(select, bt->enemies, NELEM(bt->enemies), step); + default: + break; + } +} + +static void +handle_keydown(struct battle_state *st, struct battle *bt, const union event *ev) +{ + assert(ev->type == EVENT_KEYDOWN); + + struct select *select = st->data; + struct character *source = bt->order_cur->ch; + const struct spell *sp = source->spells[bt->bar.sub_grid.selected]; + + switch (ev->key.key) { + case KEY_ESCAPE: + battle_state_sub(bt); + break; + case KEY_ENTER: + battle_cast(bt, source, sp, select->selection); + battle_state_check(bt); + break; + case KEY_LEFT: + case KEY_UP: + select_adj(select, bt, -1); + break; + case KEY_RIGHT: + case KEY_DOWN: + select_adj(select, bt, +1); + break; + default: + break; + } +} + +static void +draw_cursor(const struct battle *bt, const struct battle_entity *et) +{ + const struct theme *theme = BATTLE_THEME(bt); + const struct sprite *cursor = theme->sprites[THEME_SPRITE_CURSOR]; + int x, y; + unsigned int lh; + + if (!cursor) + return; + + label_query(&et->name, NULL, &lh); + + x = et->name.x - cursor->cellw - theme->padding; + y = et->name.y + (((int)(lh) - (int)(cursor->cellh)) / 2); + + sprite_draw(cursor, 1, 2, x, y); +} + +static void +draw_cursors(const struct battle_state *st, + const struct battle *bt, + const struct battle_entity entities[], + size_t entitiesz) +{ + const struct select *select = st->data; + + if (select->selection == (unsigned int)-1) { + for (size_t i = 0; i < entitiesz; ++i) { + const struct battle_entity *et = &entities[i]; + + if (character_ok(et->ch)) + draw_cursor(bt, et); + } + } else + draw_cursor(bt, &entities[select->selection]); +} + +static void +handle(struct battle_state *st, struct battle *bt, const union event *ev) +{ + (void)st; + + switch (ev->type) { + case EVENT_KEYDOWN: + handle_keydown(st, bt, ev); + break; + default: + break; + } +} + +static void +draw(const struct battle_state *st, const struct battle *bt) +{ + const struct select *select = st->data; + + switch (select->type) { + case SELECTION_SELF: + draw_cursor(bt, bt->order_cur); + break; + case SELECTION_ENEMY_ALL: + case SELECTION_ENEMY_ONE: + case SELECTION_ENEMY_COMBINED: + draw_cursors(st, bt, bt->enemies, NELEM(bt->enemies)); + break; + case SELECTION_TEAM_ALL: + case SELECTION_TEAM_ONE: + case SELECTION_TEAM_COMBINED: + draw_cursors(st, bt, bt->team, NELEM(bt->team)); + break; + default: + break; + } + +} + +static void +finish(struct battle_state *st, struct battle *bt) +{ + (void)bt; + + free(st->data); +} + +void +battle_state_selection(struct battle *bt, + enum selection type, + unsigned int selection) +{ + assert(bt); + + struct select *select; + + if (!(select = alloc_zero(1, sizeof (*select)))) + panic(); + + select->type = type; + select->selection = selection; + select->state.data = select; + select->state.handle = handle; + select->state.draw = draw; + select->state.finish = finish; + + battle_switch(bt, &select->state); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-selection.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,41 @@ +/* + * battle-state-selection.h -- battle state (selection) + * + * 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. + */ + +#ifndef MOLKO_BATTLE_STATE_SELECTION_H +#define MOLKO_BATTLE_STATE_SELECTION_H + +#include "selection.h" + +struct battle; + +/** + * Switch to battle state selection. + * + * \pre bt != NULL + * \param bt the battle to change state + * \post bt->state->data is not NULL + * \post bt->state->handle is set + * \post bt->state->draw is set + * \post bt->state->finish is set + */ +void +battle_state_selection(struct battle *bt, + enum selection type, + unsigned int selection); + +#endif /* !MOLKO_BATTLE_STATE_SELECTION_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-sub.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,167 @@ +/* + * battle-state-sub.c -- battle state (sub) + * + * 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 <stdbool.h> + +#include <core/event.h> +#include <core/sprite.h> +#include <core/trace.h> + +#include <ui/theme.h> + +#include "battle.h" +#include "battle-bar.h" +#include "battle-state.h" +#include "battle-state-menu.h" +#include "battle-state-sub.h" +#include "battle-state-selection.h" +#include "character.h" +#include "spell.h" + +static void +start_select_spell(struct battle *bt) +{ + const struct character *ch = bt->order_cur->ch; + const struct spell *sp = ch->spells[bt->bar.sub_grid.selected]; + unsigned int selection = 0; + + /* Don't forget to reset the gridmenu state. */ + gridmenu_reset(&bt->bar.sub_grid); + + if (!sp || sp->mp > (unsigned int)(ch->mp)) + return; + + /* + * When starting the selection state we need to initialize the first + * selection depending on the spell selection type. For example, if the + * spell require to select exactly one enemy, we need to find the first + * one in the battle that is not NULL. + */ + switch (sp->selection) { + case SELECTION_SELF: + case SELECTION_TEAM_COMBINED: + case SELECTION_TEAM_ONE: + selection = bt->order_cur - bt->team; + break; + case SELECTION_TEAM_ALL: + case SELECTION_ENEMY_ALL: + selection = -1; + break; + case SELECTION_ENEMY_COMBINED: + case SELECTION_ENEMY_ONE: + /* Find first available. */ + for (size_t i = 0; i < BATTLE_ENEMY_MAX; ++i) { + if (character_ok(bt->enemies[i].ch)) { + selection = i; + break; + } + } + break; + default: + selection = 0; + break; + } + + battle_state_selection(bt, sp->selection, selection); + + /* A cursor should be present. */ + if (!sprite_ok(BATTLE_THEME(bt)->sprites[THEME_SPRITE_CURSOR])) + tracef("battle: no cursor sprite in theme"); +} + +static void +start_select_object(struct battle *bt) +{ + (void)bt; +} + +static void +draw_spell_help(const struct battle *bt) +{ + const struct character *ch = bt->order_cur->ch; + const struct spell *sp = ch->spells[bt->bar.sub_grid.selected]; + struct label label = {0}; + unsigned int lw, lh; + + if (!sp) + return; + + label.flags = LABEL_FLAGS_SHADOW; + label.text = sp->description; + label_query(&label, &lw, &lh); + label.x = bt->bar.sub_grid.x + (bt->bar.sub_grid.w / 2) - (lw / 2); + label.y = bt->bar.sub_grid.y - lh - BATTLE_THEME(bt)->padding; + label_draw(&label); +} + +static void +handle(struct battle_state *st, struct battle *bt, const union event *ev) +{ + (void)st; + + switch (ev->type) { + case EVENT_KEYDOWN: + switch (ev->key.key) { + case KEY_ESCAPE: + /* Escape go to the previous state. */ + bt->bar.state = BATTLE_BAR_STATE_MENU; + battle_state_menu(bt); + return; + default: + break; + } + default: + break; + } + + if (battle_bar_handle(&bt->bar, bt, ev)) { + switch (bt->bar.menu) { + case BATTLE_BAR_MENU_MAGIC: + start_select_spell(bt); + break; + default: + start_select_object(bt); + break; + } + } +} + +static void +draw(const struct battle_state *st, const struct battle *bt) +{ + (void)st; + + battle_bar_draw(&bt->bar, bt); + + if (bt->bar.menu == BATTLE_BAR_MENU_MAGIC) + draw_spell_help(bt); +} + +void +battle_state_sub(struct battle *bt) +{ + assert(bt); + + static struct battle_state self = { + .handle = handle, + .draw = draw + }; + + battle_switch(bt, &self); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-sub.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,35 @@ +/* + * battle-state-sub.h -- battle state (sub) + * + * 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. + */ + +#ifndef MOLKO_BATTLE_STATE_SUB_H +#define MOLKO_BATTLE_STATE_SUB_H + +struct battle; + +/** + * Switch to battle state sub. + * + * \pre bt != NULL + * \param bt the battle to change state + * \post bt->state->handle is set + * \post bt->state->draw is set + */ +void +battle_state_sub(struct battle *bt); + +#endif /* !MOLKO_BATTLE_STATE_SUB_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-victory.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,103 @@ +/* + * battle-state-victory.c -- battle state (victory) + * + * 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 <core/alloc.h> +#include <core/panic.h> +#include <core/window.h> + +#include <rpg/message.h> + +#include "battle.h" +#include "battle-state.h" +#include "battle-state-closing.h" +#include "battle-state-victory.h" + +struct victory { + struct battle_state self; + struct message msg; +}; + +static void +handle(struct battle_state *st, struct battle *bt, const union event *ev) +{ + (void)bt; + + struct victory *vic = st->data; + + message_handle(&vic->msg, ev); +} + +static bool +update(struct battle_state *st, struct battle *bt, unsigned int ticks) +{ + (void)bt; + + struct victory *vic = st->data; + + if (message_update(&vic->msg, ticks)) + battle_state_closing(bt); + + return false; +} + +static void +draw(const struct battle_state *st, const struct battle *bt) +{ + (void)bt; + + const struct victory *vic = st->data; + + message_draw(&vic->msg); +} + +void +battle_state_victory(struct battle *bt) +{ + assert(bt); + + struct victory *vic; + + if (!(vic = alloc_zero(1, sizeof (*vic)))) + panic(); + + /* TODO: compute money, xp and drop. */ + vic->self.data = vic; + vic->self.handle = handle; + vic->self.update = update; + vic->self.draw = draw; + + vic->msg.text[0] = "Victory!"; + vic->msg.theme = bt->theme; + vic->msg.flags = MESSAGE_FLAGS_AUTOMATIC | + MESSAGE_FLAGS_FADEIN | + MESSAGE_FLAGS_FADEOUT; + vic->msg.timeout = MESSAGE_TIMEOUT_DEFAULT; + vic->msg.delay = MESSAGE_DELAY_DEFAULT; + + message_start(&vic->msg); + message_query(&vic->msg, NULL, &vic->msg.h); + + vic->msg.w = window.w * 0.6; + vic->msg.y = window.h * 0.1; + vic->msg.x = (window.w - vic->msg.w) / 2; + + bt->status = BATTLE_STATUS_WON; + battle_switch(bt, &vic->self); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state-victory.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,33 @@ +/* + * battle-state-victory.h -- battle state (victory) + * + * 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. + */ + +#ifndef MOLKO_BATTLE_STATE_VICTORY_H +#define MOLKO_BATTLE_STATE_VICTORY_H + +struct battle; + +/** + * Switch to battle state victory. + * + * \pre bt != NULL + * \param bt the battle to change state + */ +void +battle_state_victory(struct battle *bt); + +#endif /* !MOLKO_BATTLE_STATE_VICTORY_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,64 @@ +/* + * battle-state.c -- battle abstract state + * + * 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 "battle-state.h" + +void +battle_state_handle(struct battle_state *st, struct battle *bt, const union event *ev) +{ + assert(st); + assert(bt); + assert(ev); + + if (st->handle) + st->handle(st, bt, ev); +} + +bool +battle_state_update(struct battle_state *st, struct battle *bt, unsigned int ticks) +{ + assert(st); + assert(bt); + + if (st->update) + return st->update(st, bt, ticks); + + return false; +} + +void +battle_state_draw(const struct battle_state *st, const struct battle *bt) +{ + assert(st); + assert(bt); + + if (st->draw) + st->draw(st, bt); +} + +void +battle_state_finish(struct battle_state *st, struct battle *bt) +{ + assert(st); + assert(bt); + + if (st->finish) + st->finish(st, bt); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle-state.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,137 @@ +/* + * battle-state.h -- battle abstract state + * + * 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. + */ + +#ifndef MOLKO_BATTLE_STATE_H +#define MOLKO_BATTLE_STATE_H + +/** + * \file battle-state.h + * \brief Battle abstract state. + * + * As a game battle is split into different steps, they are implemented as + * multiple states to facilitate transitions and have a less complicated code. + * + * Each state can handle, update and draw the game logic. The battle itself + * does only few things by itself like playing music, handling custom actions + * and drawables and dispatch the rest to the current state. + */ + +#include <stdbool.h> + +struct battle; + +union event; + +/** + * \brief Battle abstract state. + */ +struct battle_state { + /** + * (+&?) Optional data for the state. + */ + void *data; + + /** + * (+?) Handle an event. + * + * \pre bt != NULL + * \pre ev != NULL + * \param bt the current battle + * \param ev the event + */ + void (*handle)(struct battle_state *st, struct battle *bt, const union event *ev); + + /** + * (+?) Update the battle state. + * + * \pre bt != NULL + * \param bt the current battle + * \param ticks the number of milliseconds since last frame + * \return True if the battle is considered complete. + */ + bool (*update)(struct battle_state *st, struct battle *bt, unsigned int ticks); + + /** + * (+?) Draw the battle state. + * + * Note, the battle itself already draw the background and game entities + * see the \ref battle_draw function for more details. + * + * \pre bt != NULL + * \param bt the current battle + */ + void (*draw)(const struct battle_state *st, const struct battle *bt); + + /** + * (+?) Close internal resources if necessary. + * + * \pre bt != NULL + * \param bt the current battle + */ + void (*finish)(struct battle_state *st, struct battle *bt); +}; + +/** + * Shortcut for st->handle (if not NULL). + * + * \pre st != NULL + * \pre bt != NULL + * \pre ev != NULL + * \param st this state + * \param bt the current battle + * \param ev the input event + */ +void +battle_state_handle(struct battle_state *st, struct battle *bt, const union event *ev); + +/** + * Shortcut for st->update (if not NULL). + * + * \pre st != NULL + * \pre bt != NULL + * \param st this state + * \param bt the current battle + * \param ticks the number of milliseconds since last frame + * \return The result of st->update if not NULL or false otherwise. + */ +bool +battle_state_update(struct battle_state *st, struct battle *bt, unsigned int ticks); + +/** + * Shortcut for st->draw (if not NULL). + * + * \pre st != NULL + * \pre bt != NULL + * \param st this state + * \param bt the current battle + */ +void +battle_state_draw(const struct battle_state *st, const struct battle *bt); + +/** + * Shortcut for st->finish (if not NULL). + * + * \pre st != NULL + * \pre bt != NULL + * \param st this state + * \param bt the current battle + */ +void +battle_state_finish(struct battle_state *st, struct battle *bt); + +#endif /* !MOLKO_BATTLE_STATE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,442 @@ +/* + * battle.c -- battles + * + * 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 <stdlib.h> +#include <string.h> + +#include <core/alloc.h> +#include <core/event.h> +#include <core/font.h> +#include <core/painter.h> +#include <core/sprite.h> +#include <core/texture.h> +#include <core/util.h> +#include <core/window.h> + +#include <ui/align.h> +#include <ui/frame.h> +#include <ui/label.h> +#include <ui/theme.h> + +#include "battle.h" +#include "battle-indicator.h" +#include "battle-state.h" +#include "battle-state-opening.h" +#include "battle-state-menu.h" +#include "battle-state-ai.h" +#include "character.h" +#include "spell.h" + +struct indicator { + struct drawable dw; + struct battle_indicator bti; +}; + +static bool +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 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 et1->ch->agt < et2->ch->agt; +} + +static bool +is_team(const struct battle *bt, const struct battle_entity *et) +{ + for (size_t i = 0; i < BATTLE_TEAM_MAX; ++i) + if (&bt->team[i] == et) + return true; + + return false; +} + +static void +positionate_name(struct battle_entity *et, const struct battle *bt) +{ + unsigned int lw; + + /* Show the character name below its sprite. */ + et->name.text = et->ch->name; + et->name.flags = LABEL_FLAGS_SHADOW; + label_query(&et->name, &lw, NULL); + et->name.y = et->y + et->ch->sprite->cellh + BATTLE_THEME(bt)->padding; + et->name.x = et->x + (et->ch->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 (character_ok(et->ch) && sprite_ok(et->ch->sprite)) { + nmemb++; + requirement += et->ch->sprite->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 (character_ok(et->ch) && sprite_ok(et->ch->sprite)) { + et->x = x; + et->y = y; + y += et->ch->sprite->cellh + spacing; + } + } +} + +static void +positionate_bar(struct battle *bt) +{ + /* Bar is located at bottom. */ + bt->bar.w = window.w; + bt->bar.h = window.h * 0.12; + bt->bar.x = 0; + bt->bar.y = window.h - bt->bar.h; + + battle_bar_positionate(&bt->bar, bt); +} + +static void +draw_entity_sprite(const struct battle_entity *et) +{ + struct sprite *sprite = et->ch->sprite; + int row; + + /* + * Ennemies are usually defined with a single image as such the + * sprite may contain only one cell/row. Otherwise if the user + * have provided a structured sprite, use appropriate row. + */ + if (sprite->nrows >= 6) + row = 6; + else + row = 0; + + sprite_draw(et->ch->sprite, row, 0, et->x, et->y); +} + +static void +draw_entity_name(const struct battle_entity *et, const struct battle *bt) +{ + struct theme theme; + struct label label = et->name; + + if (et == bt->order_cur) { + theme_shallow(&theme, bt->theme); + label.theme = &theme; + theme.colors[THEME_COLOR_NORMAL] = theme.colors[THEME_COLOR_SELECTED]; + } + + label_draw(&label); +} + +static void +draw_entity(const struct battle_entity *et, const struct battle *bt) +{ + draw_entity_sprite(et); + draw_entity_name(et, bt); +} + +static void +draw_entities(const struct battle *bt, struct battle_entity *entities, size_t entitiesz) +{ + for (size_t i = 0; i < entitiesz; ++i) { + if (character_ok(entities[i].ch) && sprite_ok(entities[i].ch->sprite)) + draw_entity(&entities[i], bt); + } +} + +void +battle_start(struct battle *bt) +{ + struct battle_entity *et; + + BATTLE_TEAM_FOREACH(bt, et) { + if (character_ok(et->ch)) { + character_reset(et->ch); + texture_set_alpha_mod(et->ch->sprite->texture, 255); + } + } + BATTLE_ENEMY_FOREACH(bt, et) { + if (character_ok(et->ch)) { + character_reset(et->ch); + texture_set_alpha_mod(et->ch->sprite->texture, 255); + } + } + + positionate_team(bt); + positionate_bar(bt); + positionate_names(bt); + + /* Start the state "opening" animation. */ + battle_state_opening(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 *et, **porder; + + /* First, put a pointer for every enemy/team member. */ + porder = &bt->order[0]; + + BATTLE_TEAM_FOREACH(bt, et) + if (character_ok(et->ch)) + *porder++ = et; + BATTLE_ENEMY_FOREACH(bt, et) + if (character_ok(et->ch)) + *porder++ = et; + + /* Now sort. */ + qsort(bt->order, porder - bt->order, sizeof (*porder), cmp_order); +} + +void +battle_attack(struct battle *bt, struct character *source, struct character *target) +{ + assert(bt); + assert(character_ok(source)); + assert(character_ok(target)); + + /* TODO: Extreme computation of damage. */ + unsigned int count = 10; + + if (count > (unsigned int)target->hp) + target->hp = 0; + else + target->hp -= count; + + battle_indicator_hp(bt, target, count); +} + +void +battle_cast(struct battle *bt, + struct character *source, + const struct spell *spell, + unsigned int selection) +{ + assert(bt); + assert(source); + assert(spell); + assert((unsigned int)source->mp >= spell->mp); + + source->mp -= spell->mp; + spell_action(spell, bt, source, selection); +} + +void +battle_next(struct battle *bt) +{ + assert(bt); + + battle_bar_reset(&bt->bar); + + if (!bt->order_cur) + bt->order_cur = bt->order[bt->order_curindex = 0]; + else { + /* End of turn. */ + if (++bt->order_curindex >= BATTLE_ENTITY_MAX || !bt->order[bt->order_curindex]) { + battle_order(bt); + bt->order_cur = bt->order[bt->order_curindex = 0]; + } else + bt->order_cur = bt->order[bt->order_curindex]; + } + + /* Change state depending on the kind of entity. */ + if (is_team(bt, bt->order_cur)) { + battle_bar_open_menu(&bt->bar); + battle_state_menu(bt); + } else + battle_state_ai(bt); +} + +void +battle_indicator_hp(struct battle *bt, const struct character *target, unsigned int amount) +{ + assert(bt); + assert(target); + + const struct battle_entity *et = find(bt, target); + struct indicator *id = alloc_zero(1, sizeof (*id)); + + id->bti.color = BATTLE_INDICATOR_HP_COLOR; + id->bti.amount = amount; + + id->dw.x = et->x + target->sprite->cellw; + id->dw.y = et->y + target->sprite->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)) + drawable_finish(&id->dw); +} + +void +battle_handle(struct battle *bt, const union event *ev) +{ + assert(bt && bt->state); + assert(ev); + + /* Handle actions. */ + action_stack_handle(&bt->actions[0], ev); + action_stack_handle(&bt->actions[1], ev); + + /* Handling of action is disallowed if there are pending actions. */ + if (action_stack_completed(&bt->actions[0])) + battle_state_handle(bt->state, bt, ev); +} + +bool +battle_update(struct battle *bt, unsigned int ticks) +{ + assert(bt && bt->state); + + action_stack_update(&bt->actions[0], ticks); + action_stack_update(&bt->actions[1], ticks); + drawable_stack_update(&bt->effects, ticks); + + /* Game cannot update if the actions[0] stack isn't completed. */ + if (!action_stack_completed(&bt->actions[0])) + return false; + + return battle_state_update(bt->state, bt, ticks); +} + +void +battle_draw(struct battle *bt) +{ + assert(bt && bt->state); + + painter_set_color(0xffffffff); + painter_clear(); + + if (bt->background && texture_ok(bt->background)) + texture_draw(bt->background, 0, 0); + + /* Draw entities. */ + draw_entities(bt, bt->team, NELEM(bt->team)); + draw_entities(bt, bt->enemies, NELEM(bt->enemies)); + + battle_bar_draw(&bt->bar, bt); + + action_stack_draw(&bt->actions[0]); + action_stack_draw(&bt->actions[1]); + + drawable_stack_draw(&bt->effects); + + return battle_state_draw(bt->state, bt); +} + +void +battle_finish(struct battle *bt) +{ + assert(bt); + + if (bt->state) + battle_state_finish(bt->state, bt); + + drawable_stack_finish(&bt->effects); + + action_stack_finish(&bt->actions[0]); + action_stack_finish(&bt->actions[1]); + + memset(bt, 0, sizeof (*bt)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/battle.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,264 @@ +/* + * battle.h -- battles + * + * 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. + */ + +#ifndef MOLKO_BATTLE_H +#define MOLKO_BATTLE_H + +#include <stdbool.h> + +#include <core/action.h> +#include <core/drawable.h> + +#include <ui/frame.h> +#include <ui/label.h> +#include <ui/gridmenu.h> + +#include "battle-bar.h" +#include "battle-state.h" +#include "selection.h" +#include "spell.h" + +union event; + +struct character; +struct sound; +struct spell; +struct theme; + +#define BATTLE_TEAM_MAX (4) /*!< Maximum team members. */ +#define BATTLE_ENEMY_MAX (8) /*!< Maximum enemies. */ +#define BATTLE_ENTITY_MAX \ + (BATTLE_TEAM_MAX + BATTLE_ENEMY_MAX) + +#define BATTLE_TEAM_FOREACH(bt, iter) \ + for (size_t i = 0; i < BATTLE_TEAM_MAX && ((iter) = &(bt)->team[i]); ++i) +#define BATTLE_ENEMY_FOREACH(bt, iter) \ + for (size_t i = 0; i < BATTLE_ENEMY_MAX && ((iter) = &(bt)->enemies[i]); ++i) + +#define BATTLE_THEME(bt) ((bt)->theme ? (bt)->theme : theme_default()) + +/** + * \brief In battle entity. + */ +struct battle_entity { + struct character *ch; /*!< (+&?) Character to use. */ + int x; /*!< (+) Position on screen. */ + int y; /*!< (+) Position on screen. */ + struct label name; /*!< (*) Where its name label is located. */ +}; + +/** + * \brief Generic battle status. + */ +enum battle_status { + BATTLE_STATUS_NONE, /*!< Battle is not even started. */ + BATTLE_STATUS_RUNNING, /*!< Battle is running. */ + BATTLE_STATUS_WON, /*!< Battle has been won. */ + BATTLE_STATUS_LOST, /*!< Battle has been lost. */ +}; + +/** + * \brief Battle structure. + */ +struct battle { + /** + * (+&?) Battle state. + */ + struct battle_state *state; + + /** + * (-) Battle status. + * + * This information is provided as final result once the battle has + * completed, modifying by the user won't change the battle routines. + */ + enum battle_status status; + + /** + * (+) Member of team. + */ + struct battle_entity team[BATTLE_TEAM_MAX]; + + /** + * (+) Ennemies to fight. + */ + struct battle_entity enemies[BATTLE_ENEMY_MAX]; + + /** + * (+&?) Order of playing. + */ + struct battle_entity *order[BATTLE_ENTITY_MAX]; + + /** + * (-&?) Current entity playing. + */ + struct battle_entity *order_cur; + + /** + * (-) Current entity playing index. + */ + size_t order_curindex; + + /** + * (+&?) An optional background. + */ + struct texture *background; + + /** + * (+&?) A music to play. + */ + struct sound *music; + + /** + * (+&?) Theme to use. + */ + struct theme *theme; + + /** + * (+) Stack of animations. + * + * The drawing animations are ran in parallel. + */ + struct drawable_stack effects; + + /** + * (+) Stack of actions. + * + * The array [0] contains actions that must complete to continue the + * battle, it can be used to write some messages while blocking the + * battle. + * + * The array [1] is a set of actions that run in "parallel" and don't + * prevent the battle from continuing. + */ + struct action_stack actions[2]; + + /** + * (-) Bottom bar. + */ + struct battle_bar bar; +}; + +/** + * Call this function before using the battle but after calling \a battle_init. + * + * If the team entities are positioned to 0, 0 they will be placed + * automatically. + * + * \pre bt != NULL + * \param bt the battle object + */ +void +battle_start(struct battle *bt); + +/** + * Select next member in team. + * + * \pre bt != NULL + * \param bt the battle object + */ +void +battle_next(struct battle *bt); + +/** + * Change battle state. + * + * \pre bt != NULL + * \pre st != NULL + * \param bt the battle object + * \param st the state (referenced) + * \warning This will immediately close the current state and call finish field + * function if defined. + */ +void +battle_switch(struct battle *bt, struct battle_state *st); + +/** + * Compute battle ordering. + * + * \pre bt != NULL + * \param bt the battle object + */ +void +battle_order(struct battle *bt); + +/** + * Default function to calculate damage calculation from the source to the + * given target and then add an amount indicator as a drawable. + * + * \pre bt != NULL + * \pre source != NULL + * \pre target != NULL + * \param bt the battle object + * \param source + */ +void +battle_attack(struct battle *bt, struct character *source, struct character *target); + +/** + * Default function to cast a spell. + */ +void +battle_cast(struct battle *bt, + struct character *source, + const struct spell *spell, + unsigned int selection); + +void +battle_indicator_hp(struct battle *bt, const struct character *target, unsigned int amount); + +/** + * Handle an event. + * + * \pre bt != NULL + * \pre ev != NULL + * \param bt the battle object + * \param ev the event + */ +void +battle_handle(struct battle *bt, const union event *ev); + +/** + * Update the battle. + * + * \pre bt != NULL + * \param bt the battle object + * \param ticks the number of milliseconds since last frame + */ +bool +battle_update(struct battle *bt, unsigned int ticks); + +/** + * Draw the battle. + * + * \pre bt != NULL + * \param bt the battle object + */ +void +battle_draw(struct battle *bt); + +/** + * Finish and dispose resources if necessary. + * + * \pre bt != NULL + * \param bt the battle object + */ +void +battle_finish(struct battle *bt); + +#endif /* MOLKO_BATTLE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/character.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,56 @@ +/* + * character.c -- character definition + * + * 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 "character.h" + +bool +character_ok(struct character *ch) +{ + return ch && ch->name && ch->type && ch->reset; +} + +const char * +character_status_string(enum character_status status) +{ + /* We have to use a switch-case as it is a bitmask. */ + switch (status) { + case CHARACTER_STATUS_POISON: + return "poison"; + default: + return "normal"; + } +} + +void +character_reset(struct character *ch) +{ + assert(ch); + + ch->reset(ch); +} + +void +character_exec(struct character *ch, struct battle *bt) +{ + assert(character_ok(ch)); + + if (ch->exec) + ch->exec(ch, bt); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/character.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,151 @@ +/* + * character.h -- character definition + * + * 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. + */ + +#ifndef MOLKO_CHARACTER_H +#define MOLKO_CHARACTER_H + +/** + * \file character.h + * \brief Character definition. + */ + +#include <stdbool.h> + +struct battle; +struct sprite; +struct spell; + +/** + * \brief Maximum number of spells in a character. + */ +#define CHARACTER_SPELL_MAX (64) + +/** + * \brief Character status + */ +enum character_status { + CHARACTER_STATUS_NORMAL, /*!< No status. */ + CHARACTER_STATUS_POISON = (1 << 0) /*!< Character is poisoned. */ +}; + +/** + * \brief Character object + * + * This structure owns the current character statistics used in battle. + */ +struct character { + const char *name; /*!< (+) Character name. */ + const char *type; /*!< (+) Type or class name. */ + unsigned int level; /*!< (+) Character level. */ + enum character_status status; /*!< (+) Character status. */ + int hp; /*!< (+) Heal points. */ + unsigned int hpmax; /*!< (+) Maximum heal points. */ + unsigned int hpbonus; /*!< (+) User heal points bonus. */ + int mp; /*!< (+) Magic points. */ + unsigned int mpmax; /*!< (+) Maximum magic points. */ + unsigned int mpbonus; /*!< (+) User magic points bonus. */ + int atk; /*!< (+) Current attack points (increase fire based spells too). */ + unsigned int atkbonus; /*!< (+) User attack bonus. */ + int def; /*!< (+) Current defense points (increase earth based spells too). */ + unsigned int defbonus; /*!< (+) User defense bonus. */ + int agt; /*!< (+) Current agility (increase wind based spells too). */ + unsigned int agtbonus; /*!< (+) User agility bonus. */ + int luck; /*!< (+) Current luck points (increase */ + unsigned int luckbonus; /*!< (+) User luck bonus. */ + struct sprite *sprite; /*!< (+&) Sprite to use. */ + + /** + * (+&?) List of spells for this character. + */ + const struct spell *spells[CHARACTER_SPELL_MAX]; + + /** + * (+) Reset statistics from this character class. + * + * This function must reset the following member variables according + * to the class characteristics: + * + * - hpmax + * - mpmax + * - atk + * - def + * - agt + * - luck + * + * \param owner this owner + */ + void (*reset)(struct character *owner); + + /** + * (+?) Execute an action. + * + * This function should be present for AI enemies in a battle, it should + * be kept NULL for team players unless they have automatic actions + * which in that case would skip user input. + * + * \param owner this owner + * \param bt the battle object + */ + void (*exec)(struct character *owner, struct battle *bt); +}; + +/** + * Check if this is a valid character object. + * + * \pre ch != NULL + * \param ch the character object + */ +bool +character_ok(struct character *ch); + +/** + * Get a string name for the given status. + * + * Since status is a bitmask you have to select only one status. + * + * \pre status must be valid + * \param status the status + * \return A const string. + */ +const char * +character_status_string(enum character_status status); + +/** + * Shortcut for ch->reset. + * + * This function is usually called after an equipment change, a level change + * or and of a battle. + * + * \pre ch != NULL + * \param ch the character object + */ +void +character_reset(struct character *ch); + +/** + * Shortcut for ch->exec (if not NULL) + * + * \pre character_ok(ch) + * \pre bt != NULL + * \param ch the character + * \param bt the battle object + */ +void +character_exec(struct character *ch, struct battle *bt); + +#endif /* !MOLKO_CHARACTER_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/selection.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,35 @@ +/* + * selection.h -- kind of selection + * + * 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. + */ + +#ifndef MOLKO_SELECTION_H +#define MOLKO_SELECTION_H + +/** + * \brief Kind of selection. + */ +enum selection { + SELECTION_SELF, /*!< Owner only. */ + SELECTION_TEAM_ONE, /*!< One member of the team. */ + SELECTION_TEAM_ALL, /*!< All members of the team. */ + SELECTION_TEAM_COMBINED, /*!< One or all members of the team. */ + SELECTION_ENEMY_ONE, /*!< One enemy. */ + SELECTION_ENEMY_ALL, /*!< All enemies. */ + SELECTION_ENEMY_COMBINED /*!< One or all enemies. */ +}; + +#endif /* !MOLKO_SELECTION_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/spell.c Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,41 @@ +/* + * spell.c -- magic spells + * + * 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 "spell.h" + +void +spell_action(const struct spell *s, struct battle *bt, struct character *owner, unsigned int selection) +{ + assert(s && s->action); + assert(bt); + assert(owner); + + s->action(bt, owner, selection); +} + +void +spell_use(struct spell *s, struct character *owner, int selection) +{ + assert(s && s->use); + assert(owner); + + if (s->use) + s->use(owner, selection); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/spell.h Sat Nov 07 16:00:39 2020 +0100 @@ -0,0 +1,108 @@ +/* + * spell.h -- magic spells + * + * 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. + */ + +#ifndef MOLKO_SPELL_H +#define MOLKO_SPELL_H + +/** + * \file spell.h + * \brief Magic spells. + */ + +#include "selection.h" + +struct character; +struct battle; + +/** + * \brief Kind of spell. + */ +enum spell_type { + SPELL_TYPE_NEUTRAL, /*!< No type. */ + SPELL_TYPE_FIRE, /*!< Fire (affected by attack). */ + SPELL_TYPE_WIND, /*!< Wind (affected by agility). */ + SPELL_TYPE_WATER, /*!< Water (affected by luck). */ + SPELL_TYPE_EARTH, /*!< Earth (affected by defense). */ + SPELL_TYPE_CHAOS, /*!< Chaotic. */ + SPELL_TYPE_HOLY, /*!< Holy. */ + SPELL_TYPE_TIME /*!< Chrono. */ +}; + +/** + * \brief Spell structure. + * + * A spell is a magical object owned by a character and can be used in a battle + * and/or outside of a battle. It costs a certain amount of magic points and is + * typed into a category (earth, fire, etc, …). + * + * A spell can select one character or all. + */ +struct spell { + const char *name; /*!< (+&) Spell name. */ + const char *description; /*!< (+&) Long description. */ + unsigned int mp; /*!< (+) Number of MP required. */ + enum spell_type type; /*!< (+) Kind of spell. */ + enum selection selection; /*!< (+) Kind of selection. */ + + /** + * (+) Execute the spell in a battle. + * + * \param bt the current battle + * \param owner the spell owner + * \param selection the selection (-1 == all) + */ + void (*action)(struct battle *bt, struct character *owner, unsigned int selection); + + /** + * (+) Use the spell outside of a battle. + * + * This function is optional. + * + * \param owner the spell owner + * \param selection the selection flags + */ + void (*use)(struct character *owner, int selection); +}; + +/** + * Cast this spell within a battle. + * + * \pre s != NULL && s->action + * \pre bt != NULL + * \pre owner != NULL + * \param s the spell + * \param bt the battle + * \param owner the owner + * \param selection the selection (index or -1 for all) + */ +void +spell_action(const struct spell *s, struct battle *bt, struct character *owner, unsigned int selection); + +/** + * Use this spell immediately. + * + * \pre s != NULL && s->use + * \pre owner != NULL + * \param s the spell + * \param owner the owner + * \param selection the selection flags + */ +void +spell_use(struct spell *s, struct character *owner, int selection); + +#endif /* !MOLKO_SPELL_H */
--- a/libui/ui/gridmenu.c Sat Nov 07 15:40:34 2020 +0100 +++ b/libui/ui/gridmenu.c Sat Nov 07 16:00:39 2020 +0100 @@ -194,7 +194,7 @@ menu->selected -= 1; break; case KEY_ENTER: - menu->state = GRIDMENU_STATE_SELECTED; + menu->state = GRIDMENU_STATE_ACTIVATED; break; default: break; @@ -224,8 +224,11 @@ } } - if (save != menu->selected) + /* A click immediately active the widget. */ + if (save != menu->selected) { gridmenu_repaint(menu); + menu->state = GRIDMENU_STATE_ACTIVATED; + } } void
--- a/libui/ui/gridmenu.h Sat Nov 07 15:40:34 2020 +0100 +++ b/libui/ui/gridmenu.h Sat Nov 07 16:00:39 2020 +0100 @@ -44,7 +44,7 @@ */ enum gridmenu_state { GRIDMENU_STATE_NONE, /*!< No state yet. */ - GRIDMENU_STATE_SELECTED /*!< An entry has been selected. */ + GRIDMENU_STATE_ACTIVATED /*!< An entry has been selected. */ }; /**
--- a/libui/ui/theme.c Sat Nov 07 15:40:34 2020 +0100 +++ b/libui/ui/theme.c Sat Nov 07 16:00:39 2020 +0100 @@ -66,7 +66,7 @@ struct font font; } default_fonts[] = { FONT(fonts_opensans_light, 12, THEME_FONT_DEBUG), - FONT(fonts_opensans_regular, 12, THEME_FONT_INTERFACE) + FONT(fonts_opensans_regular, 14, THEME_FONT_INTERFACE) }; bool
--- a/libui/ui/theme.h Sat Nov 07 15:40:34 2020 +0100 +++ b/libui/ui/theme.h Sat Nov 07 16:00:39 2020 +0100 @@ -27,11 +27,12 @@ #include <stdbool.h> +struct button; struct checkbox; -struct button; struct font; struct frame; struct label; +struct sprite; /** * \brief Font component. @@ -54,6 +55,14 @@ }; /** + * \brief Special sprites. + */ +enum theme_sprite { + THEME_SPRITE_CURSOR, /*!< Sprite cursor. */ + THEME_SPRITE_LAST, +}; + +/** * \brief Abstract theme structure. */ struct theme { @@ -63,6 +72,11 @@ struct font *fonts[THEME_FONT_LAST]; /** + * (+&?) Special sprites catalog. + */ + const struct sprite *sprites[THEME_SPRITE_LAST]; + + /** * (+) User interface colors. */ unsigned long colors[THEME_COLOR_LAST];