diff --git a/src/dusk/rpg/cutscene/cutscene.h b/src/dusk/rpg/cutscene/cutscene.h index 9fc70116..bf9d322e 100644 --- a/src/dusk/rpg/cutscene/cutscene.h +++ b/src/dusk/rpg/cutscene/cutscene.h @@ -144,6 +144,16 @@ typedef struct cutscene_s { #define CUTSCENE_FADE_FROM_WHITE(DURATION) \ CUTSCENE_FADE(COLOR_WHITE, COLOR_TRANSPARENT_WHITE, DURATION, EASING_LINEAR) +#define CUTSCENE_EMOJI(ENTITY_INDEX, EMOJI_TYPE, DURATION) \ + { \ + .type = CUTSCENE_ITEM_TYPE_EMOJI, \ + .emoji = { \ + .entityIndex = ENTITY_INDEX, \ + .emojiType = EMOJI_TYPE, \ + .duration = DURATION \ + } \ + } + #define CUTSCENE_SET_PAUSE(FLAGS) \ { .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) } diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index bf80836a..95432dda 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -89,6 +89,10 @@ void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) { cutsceneStartBattleStart(item, data); break; + case CUTSCENE_ITEM_TYPE_EMOJI: + cutsceneEmojiStart(item, data); + break; + default: break; } @@ -153,6 +157,9 @@ bool_t cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) case CUTSCENE_ITEM_TYPE_START_BATTLE: return cutsceneStartBattleUpdate(item, data); + case CUTSCENE_ITEM_TYPE_EMOJI: + return cutsceneEmojiUpdate(item, data); + default: return false; } diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h index faffe912..84164c50 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.h @@ -20,6 +20,7 @@ #include "ui/cutscenetextmini.h" #include "ui/cutscenetextminihide.h" #include "ui/cutscenefade.h" +#include "ui/cutsceneemoji.h" #include "item/cutsceneitemgive.h" #include "maparea/cutscenemapareaadd.h" #include "maparea/cutscenemaparearemove.h" @@ -49,7 +50,8 @@ typedef enum { CUTSCENE_ITEM_TYPE_MAP_AREA_ADD, CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, - CUTSCENE_ITEM_TYPE_START_BATTLE + CUTSCENE_ITEM_TYPE_START_BATTLE, + CUTSCENE_ITEM_TYPE_EMOJI } cutsceneitemtype_t; struct cutsceneitem_s { @@ -76,6 +78,7 @@ struct cutsceneitem_s { cutscenemaparearemove_t mapAreaRemove; cutscenemapareawait_t mapAreaWait; cutscenestartbattle_t startBattle; + cutsceneemoji_t emoji; }; }; diff --git a/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt b/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt index 7649be71..2cee571a 100644 --- a/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt +++ b/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt @@ -9,4 +9,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} cutscenetextmini.c cutscenetextminihide.c cutscenefade.c + cutsceneemoji.c ) diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c new file mode 100644 index 00000000..2cd52bb7 --- /dev/null +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "rpg/cutscene/item/cutsceneitem.h" +#include "rpg/cutscene/cutscenesystem.h" +#include "rpg/entity/entity.h" + +void cutsceneEmojiStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + entity_t *entity = cutsceneSystemGetEntity(item->emoji.entityIndex); + uiEmojiAdd(entity->id, item->emoji.duration, item->emoji.emojiType); +} + +bool_t cutsceneEmojiUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +) { + return true; +} diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h new file mode 100644 index 00000000..1c9deeca --- /dev/null +++ b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "ui/rpg/uiemoji.h" + +typedef struct cutsceneitem_s cutsceneitem_t; +typedef union cutsceneitemdata_u cutsceneitemdata_t; + +typedef struct { + uint8_t entityIndex; + float_t duration; + uiemojitype_t emojiType; +} cutsceneemoji_t; + +/** + * Starts an emoji step (shows an emoji above the entity for the given + * duration, then completes immediately). + * + * @param item The cutscene item. + * @param data Runtime data storage. + */ +void cutsceneEmojiStart( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); + +/** + * Updates an emoji step (always completes immediately). + * + * @param item The cutscene item. + * @param data Runtime data storage. + * @returns true always. + */ +bool_t cutsceneEmojiUpdate( + const cutsceneitem_t *item, + cutsceneitemdata_t *data +); diff --git a/src/dusk/rpg/cutscene/scene/testcutscene.h b/src/dusk/rpg/cutscene/scene/testcutscene.h index 16ae4703..10f7ba5f 100755 --- a/src/dusk/rpg/cutscene/scene/testcutscene.h +++ b/src/dusk/rpg/cutscene/scene/testcutscene.h @@ -17,6 +17,9 @@ CUTSCENE(TEST_TWO, 0, DEFAULT, CUTSCENE_TEXT("Test Two."), CUTSCENE_ENTITY_ADD(ENTITY_TYPE_NPC, 4, 4, 0), CUTSCENE_TEXT_MINI("Hello!", 4, 4, 0, 3.0f), + CUTSCENE_EMOJI( + CUTSCENE_ENTITY_LAST_CREATED, UI_EMOJI_EXCLAMATION_MARK, 2.0f + ), CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_LAST_CREATED, 8, 2, 0), // CUTSCENE_CONCURRENT( // CUTSCENE_ENTITY_WALK_TO(CUTSCENE_ENTITY_INTERACT, 4, 4, 0), diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c index 6a92cf72..6e04ae7b 100644 --- a/src/dusk/rpg/rpg.c +++ b/src/dusk/rpg/rpg.c @@ -23,6 +23,8 @@ #include "assert/assert.h" #include "console/console.h" +#include "ui/rpg/uiemoji.h" + void rpgTestAreaCallback(entity_t *entity, const uint8_t trigger) { consolePrint("rpgTestAreaCallback: trigger=%u", trigger); } diff --git a/src/dusk/ui/rpg/CMakeLists.txt b/src/dusk/ui/rpg/CMakeLists.txt index a8ae223b..eb3441de 100644 --- a/src/dusk/ui/rpg/CMakeLists.txt +++ b/src/dusk/ui/rpg/CMakeLists.txt @@ -3,4 +3,10 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -add_subdirectory(textbox) +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + uiemoji.c +) + + +add_subdirectory(textbox) \ No newline at end of file diff --git a/src/dusk/ui/rpg/uiemoji.c b/src/dusk/ui/rpg/uiemoji.c new file mode 100644 index 00000000..21a3cbf2 --- /dev/null +++ b/src/dusk/ui/rpg/uiemoji.c @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uiemoji.h" +#include "display/spritebatch/spritebatch.h" +#include "time/time.h" +#include "util/memory.h" +#include "display/shader/shaderunlit.h" +#include "rpg/entity/entity.h" +#include "rpg/rpgcamera.h" + +uiemoji_t UI_EMOJI; + +errorret_t uiEmojiInit(void) { + memoryZero(&UI_EMOJI, sizeof(UI_EMOJI)); + errorOk(); +} + +void uiEmojiAdd( + const uint8_t entity, + const float_t duration, + const uiemojitype_t type +) { + uint8_t slot = 0xFF; + // Find an available slot if any + for(uint8_t i = 0; i < UI_EMOJI_ACTIVE_COUNT; i++) { + if(UI_EMOJI.items[i].time <= 0.0f) { + slot = i; + break; + } + + if(UI_EMOJI.items[i].entity == entity) { + slot = i; + break; + } + + if(UI_EMOJI.items[i].type == UI_EMOJI_NULL) { + slot = i; + break; + } + } + + if(slot == 0xFF) { + // Whichever ends soonest + float_t minTime = UI_EMOJI.items[0].time; + slot = 0; + for(uint8_t i = 1; i < UI_EMOJI_ACTIVE_COUNT; i++) { + if(UI_EMOJI.items[i].time >= minTime) continue; + minTime = UI_EMOJI.items[i].time; + slot = i; + } + } + + uiemojiitem_t *item = &UI_EMOJI.items[slot]; + item->entity = entity; + item->time = duration; + item->type = type; +} + +errorret_t uiEmojiUpdate(void) { + for(uint8_t i = 0; i < UI_EMOJI_ACTIVE_COUNT; i++) { + uiemojiitem_t *item = &UI_EMOJI.items[i]; + if(item->type == UI_EMOJI_NULL) continue; + + item->time -= TIME.delta; + + if(item->time <= 0.0f) { + item->type = UI_EMOJI_NULL; + item->entity = 0xFF; + } + } + + errorOk(); +} + +errorret_t uiEmojiDraw(void) { + spritebatchsprite_t sprites[UI_EMOJI_ACTIVE_COUNT]; + uint8_t count = 0; + + for(uint8_t i = 0; i < UI_EMOJI_ACTIVE_COUNT; i++) { + uiemojiitem_t *item = &UI_EMOJI.items[i]; + if(item->type == UI_EMOJI_NULL) continue; + + vec2 screenPos; + rpgCameraToScreen(ENTITIES[item->entity].renderPosition, screenPos); + + spritebatchsprite_t *sprite = &sprites[count++]; + sprite->min[0] = screenPos[0]; + sprite->min[1] = screenPos[1] - UI_EMOJI_SIZE; + sprite->min[2] = 0.0f; + sprite->max[0] = screenPos[0] + UI_EMOJI_SIZE; + sprite->max[1] = screenPos[1]; + sprite->max[2] = 0.0f; + sprite->uvMin[0] = 0.0f; + sprite->uvMin[1] = 0.0f; + sprite->uvMax[0] = 1.0f; + sprite->uvMax[1] = 1.0f; + } + + if(count == 0) errorOk(); + + shadermaterial_t mat = { + .unlit = { + .color = COLOR_WHITE, + .texture = NULL + } + }; + errorChain(spriteBatchBuffer(sprites, count, &SHADER_UNLIT, mat)); + errorChain(spriteBatchFlush()); + errorOk(); +} + +errorret_t uiEmojiDispose(void) { + errorOk(); +} diff --git a/src/dusk/ui/rpg/uiemoji.h b/src/dusk/ui/rpg/uiemoji.h new file mode 100644 index 00000000..6dc8cc00 --- /dev/null +++ b/src/dusk/ui/rpg/uiemoji.h @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +#define UI_EMOJI_ACTIVE_COUNT 4 +#define UI_EMOJI_SIZE 32.0f + +typedef enum { + UI_EMOJI_NULL, + + UI_EMOJI_QUESTION_MARK, + UI_EMOJI_EXCLAMATION_MARK, + + UI_EMOJI_COUNT, +} uiemojitype_t; + +typedef struct { + uint8_t entity; + float_t time; + uiemojitype_t type; +} uiemojiitem_t; + +typedef struct { + uiemojiitem_t items[UI_EMOJI_ACTIVE_COUNT]; +} uiemoji_t; + +extern uiemoji_t UI_EMOJI; + +/** + * Initialize the UI emoji. + */ +errorret_t uiEmojiInit(void); + +/** + * Show a UI emoji for a specific entity. + * + * @param entity The entity to show the emoji for. + * @param duration The duration to display the emoji. + * @param type The type of emoji to display. + */ +void uiEmojiAdd( + const uint8_t entity, + const float_t duration, + const uiemojitype_t type +); + +/** + * Update the UI emoji. + */ +errorret_t uiEmojiUpdate(void); + +/** + * Draw the UI emoji. + */ +errorret_t uiEmojiDraw(void); + +/** + * Dispose of the UI emoji. + */ +errorret_t uiEmojiDispose(void); \ No newline at end of file diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index 877251c3..ce39a599 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -23,6 +23,7 @@ #include "ui/frame/uiconfirm.h" #include "ui/rpg/textbox/uitextboxmain.h" #include "ui/rpg/textbox/uitextboxminilist.h" +#include "ui/rpg/uiemoji.h" uielement_t UI_ELEMENTS[] = { { @@ -30,7 +31,6 @@ uielement_t UI_ELEMENTS[] = { .dispose = uiFrameDispose }, - // Fullbox under: above scene, below system UI. { .init = uiFullboxUnderInit, @@ -38,19 +38,21 @@ uielement_t UI_ELEMENTS[] = { .draw = uiFullboxUnderDraw }, + // in world stuff + { + .init = uiEmojiInit, + .update = uiEmojiUpdate, + .draw = uiEmojiDraw, + .dispose = uiEmojiDispose + }, + + // Ingame menus { .init = uiGameMenuInit, .draw = uiGameMenuDraw, .dispose = uiGameMenuDispose }, - { - .init = uiSettingsInit, - .update = uiSettingsUpdate, - .draw = uiSettingsDraw, - .dispose = uiSettingsDispose - }, - { .init = uiBattleMenuInit, .update = uiBattleMenuUpdate, @@ -63,6 +65,14 @@ uielement_t UI_ELEMENTS[] = { .dispose = uiBackpackDispose }, + { + .init = uiSettingsInit, + .update = uiSettingsUpdate, + .draw = uiSettingsDraw, + .dispose = uiSettingsDispose + }, + + // Text stuffs { .init = uiConfirmInit, .draw = uiConfirmDraw,