diff --git a/.gitignore b/.gitignore index 0a45f844..556046ee 100644 --- a/.gitignore +++ b/.gitignore @@ -91,7 +91,7 @@ assets/borrowed /archive0 /archive1 -# /archive +/archive __pycache__ diff --git a/assets/audio/audiotest.mp3 b/assets/audio/audiotest.mp3 deleted file mode 100644 index 5f46ebfd..00000000 Binary files a/assets/audio/audiotest.mp3 and /dev/null differ diff --git a/assets/audio/audiotest.wav b/assets/audio/audiotest.wav deleted file mode 100644 index 1a2628c3..00000000 Binary files a/assets/audio/audiotest.wav and /dev/null differ diff --git a/assets/battle/moves.jsonc b/assets/battle/moves.jsonc deleted file mode 100644 index 1ab6a329..00000000 --- a/assets/battle/moves.jsonc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "moves": { - "fire": { - // Target flags - "enemies": true, - "allies": true, - "multiple": false, - "self": true, - "default": "enemy", - "target": "SINGLE_ENEMY" - }, - - "slash": { - "enemies": true, - "allies": true, - "self": false, - "default": "enemy", - "target": "SINGLE_ENEMY" - } - } -} diff --git a/assets/cutscenes/battle/moves/fire.jsonc b/assets/cutscenes/battle/moves/fire.jsonc deleted file mode 100644 index 8c88770a..00000000 --- a/assets/cutscenes/battle/moves/fire.jsonc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "items": [ - { - "type": "MODAL", - "title": "Fire", - "message": "A burst of flame scorches the target." - }, - { - "type": "WAIT", - "seconds": 2 - }, - { - "type": "MODAL_CLOSE" - }, - { - "type": "BATTLE_FIGHTER_DAMAGE", - "fighter": "target", - "amount": 12 - } - ] -} diff --git a/assets/cutscenes/battle/moves/slash.jsonc b/assets/cutscenes/battle/moves/slash.jsonc deleted file mode 100644 index 0e80637f..00000000 --- a/assets/cutscenes/battle/moves/slash.jsonc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "items": [ - { - "type": "MODAL", - "title": "Slash", - "message": "A quick blade strike cuts across the target." - }, - { - "type": "WAIT", - "seconds": 2 - }, - { - "type": "MODAL_CLOSE" - }, - { - "type": "BATTLE_FIGHTER_DAMAGE", - "fighter": "target", - "amount": 8 - } - ] -} diff --git a/assets/cutscenes/battletest.jsonc b/assets/cutscenes/battletest.jsonc deleted file mode 100644 index cf39d091..00000000 --- a/assets/cutscenes/battletest.jsonc +++ /dev/null @@ -1,18 +0,0 @@ -{ - "items": [ - { - "type": "BATTLE_START", - "enemies": [ - { - "attack": 8, "defense": 4, "magic": 0, "speed": 5, "luck": 2, - "healthMax": 30, "mpMax": 0, - "abilities": ["fire"] - }, - { - "attack": 6, "defense": 3, "magic": 0, "speed": 7, "luck": 2, - "healthMax": 20, "mpMax": 0 - } - ] - } - ] -} diff --git a/assets/cutscenes/initial.jsonc b/assets/cutscenes/initial.jsonc deleted file mode 100644 index 75068b7b..00000000 --- a/assets/cutscenes/initial.jsonc +++ /dev/null @@ -1,68 +0,0 @@ -{ - "items": [ - // First thing, try and find a save device, used for settings and what not. - { - "type": "MODAL", - "title": "initial.checking_save.title", - "message": "initial.checking_save.message" - }, - { - "type": "WAIT", - "seconds": 0.2 - }, - { - "type": "SAVE_DEVICE_CHECK", - "successMarker": "CONTINUE", - "failureMarker": "SAVE_DEVICE_NOT_FOUND" - }, - - // No save device found - { - "type": "MARKER", - "name": "SAVE_DEVICE_NOT_FOUND" - }, - { - "type": "MODAL_CLOSE" - }, - { - "type": "MODAL_OPTIONS_MARKERS", - "title": "initial.no_device.title", - "message": "initial.no_device.message", - "options": [ - { - "text": "initial.no_device.retry", - "marker": "SAVE_DEVICE_RETRY" - }, - { - "text": "initial.no_device.continue", - "marker": "CONTINUE" - } - ] - }, - - // Save device not found, user to retry, just close modal and loop - { - "type": "MARKER", - "name": "SAVE_DEVICE_RETRY" - }, - { - "type": "MODAL_CLOSE" - }, - { - "type": "RESTART" - }, - - // Save device found (or continuing without one) - { - "type": "MARKER", - "name": "CONTINUE" - }, - { - "type": "MODAL_CLOSE" - }, - { - "type": "CUTSCENE", - "name": "cutscenes/main_menu.jsonc" - } - ] -} diff --git a/assets/cutscenes/main_menu.jsonc b/assets/cutscenes/main_menu.jsonc deleted file mode 100644 index e58939b5..00000000 --- a/assets/cutscenes/main_menu.jsonc +++ /dev/null @@ -1,140 +0,0 @@ -{ - "items": [ - { - "type": "MARKER", - "name": "MAIN_MENU_IDLE" - }, - { - "type": "UI_SHOW", - "name": "main_menu" - }, - - // Options button goes here - no options screen yet, so this is just - // an alias landing right back on the idle loop below. - { - "type": "MARKER", - "name": "OPTIONS" - }, - { - "type": "IDLE" - }, - - // New Game button goes here. - { - "type": "MARKER", - "name": "NEW_GAME" - }, - { - "type": "MODAL", - "title": "main_menu.checking_save.title", - "message": "main_menu.checking_save.message" - }, - { - "type": "WAIT", - "seconds": 0.2 - }, - { - "type": "SAVE_DEVICE_CHECK", - "successMarker": "SAVE_DEVICE_FOUND", - "failureMarker": "SAVE_DEVICE_NOT_FOUND" - }, - - // Save device not found - { - "type": "MARKER", - "name": "SAVE_DEVICE_NOT_FOUND" - }, - { - "type": "MODAL_CLOSE" - }, - { - "type": "MODAL_OPTIONS_MARKERS", - "title": "main_menu.no_device.title", - "message": "main_menu.no_device.message", - "options": [ - { - "text": "main_menu.no_device.retry", - "marker": "NEW_GAME" - }, - { - "text": "main_menu.no_device.continue", - "marker": "SAVE_DEVICE_FOUND" - } - ] - }, - - // Save data failed to load (e.g. corrupt/unreadable) - only option is - // to retry, no "continue without saving" here since we already know a - // device is present. - { - "type": "MARKER", - "name": "LOAD_ERROR" - }, - { - "type": "MODAL_OPTIONS_MARKERS", - "title": "main_menu.save_load_error.title", - "message": "main_menu.save_load_error.message", - "options": [ - { - "text": "main_menu.save_load_error.retry", - "marker": "NEW_GAME" - } - ] - }, - - // Quit Game button goes here. Cancelling jumps back to - // MAIN_MENU_IDLE; confirming falls through to QUIT_GAME, which - // ends the process directly. - { - "type": "MARKER", - "name": "QUIT" - }, - { - "type": "MODAL_OPTIONS_MARKERS", - "title": "main_menu.quit_confirm", - "options": [ - { - "text": "ui.confirm.confirm", - "marker": "QUIT_CONFIRMED" - }, - { - "text": "ui.confirm.cancel", - "marker": "MAIN_MENU_IDLE" - } - ] - }, - - { - "type": "MARKER", - "name": "QUIT_CONFIRMED" - }, - { - "type": "QUIT_GAME" - }, - - // main menu just listens for cutscene ending to show the select save modal - // so we have that here. - { - "type": "MARKER", - "name": "SAVE_DEVICE_FOUND" - }, - { - "type": "MODAL_CLOSE" - }, - { - "type": "SAVE_LOAD_ALL_SLOTS", - "successMarker": "LOADED", - "failureMarker": "LOAD_ERROR" - }, - - // Must be the very last item - falling off the end here is what - // completes the cutscene and fires uiMainMenuOpenSelectSave (see - // cutsceneSystemSetOnComplete in ui/screen/mainmenu/uimainmenu.c). - // Placing this anywhere earlier would fall through into whatever - // comes next instead (e.g. the QUIT block, if placed just before it). - { - "type": "MARKER", - "name": "LOADED" - } - ] -} diff --git a/assets/cutscenes/new_game.jsonc b/assets/cutscenes/new_game.jsonc deleted file mode 100644 index 01bd3ac2..00000000 --- a/assets/cutscenes/new_game.jsonc +++ /dev/null @@ -1,11 +0,0 @@ -{ - // Placeholder for the new-game intro flow - just shows a modal for now. - "items": [ - { - "type": "MODAL", - "title": "New Game", - "message": "This is a placeholder for the new game intro.", - "blocking": true - } - ] -} diff --git a/assets/items/items.jsonc b/assets/items/items.jsonc deleted file mode 100644 index 05f02667..00000000 --- a/assets/items/items.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "items": { - "potion": { "type": "MEDICINE" }, - "potato": { "type": "FOOD" }, - "apple": { "type": "FOOD" } - } -} diff --git a/assets/locale/en_US.po b/assets/locale/en_US.po deleted file mode 100644 index 9f1242b0..00000000 --- a/assets/locale/en_US.po +++ /dev/null @@ -1,289 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: ExampleApp 1.0\n" -"Language: en\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : (n<7 ? 2 : 3));\n" - -# Initial Scene -msgid "initial.checking_save.title" -msgstr "Checking for save data" - -msgid "initial.checking_save.message" -msgstr "Please wait..." - -msgid "initial.no_device.title" -msgstr "No Save Device Found" - -msgid "initial.no_device.message" -msgstr "Could not find a save device, ensure it is connected and try again. You can continue, but progress will not be saved." - -msgid "initial.no_device.retry" -msgstr "Try again" - -msgid "initial.no_device.continue" -msgstr "Continue without saving" - - -# Main Menu Scene -msgid "main_menu.start_game" -msgstr "Start Game" - -msgid "main_menu.options" -msgstr "Options" - -msgid "main_menu.quit" -msgstr "Quit Game" - -msgid "main_menu.quit_confirm" -msgstr "Are you sure you want to quit?" - -msgid "main_menu.checking_save.title" -msgstr "Checking for save data" - -msgid "main_menu.checking_save.message" -msgstr "Please wait..." - -msgid "main_menu.no_device.title" -msgstr "No Save Device Found" - -msgid "main_menu.no_device.message" -msgstr "Could not find a save device, ensure it is connected and try again. You can continue, but progress will not be saved." - -msgid "main_menu.no_device.retry" -msgstr "Try again" - -msgid "main_menu.no_device.continue" -msgstr "Continue without saving" - -msgid "main_menu.save_load_error.title" -msgstr "Error" - -msgid "main_menu.save_load_error.message" -msgstr "Failed to load save data. Please try again." - -msgid "main_menu.save_load_error.retry" -msgstr "Try Again" - - -# Select Save Screen -msgid "ui.select_save.title" -msgstr "Select Save" - -msgid "ui.select_save.empty" -msgstr "Empty Slot" - -msgid "ui.select_save.corrupt" -msgstr "Corrupted Save" - -msgid "ui.select_save.corrupt_warning" -msgstr "This save file is corrupted and could not be loaded. Please contact support if this issue persists." - -msgid "ui.select_save.slot_format" -msgstr "%s Lv.%d %s" - -msgid "ui.select_save.delete_mode" -msgstr "Delete a Save" - -msgid "ui.select_save.delete_confirm" -msgstr "Are you sure you want to delete this save?" - -msgid "ui.select_save.name_title" -msgstr "Enter game save file name" - -msgid "ui.save_slot.number_format" -msgstr "Slot %d" - - - - - - - - - - - -#: ui/menu.c:10 -msgid "ui.title" -msgstr "" -"Welcome" - -msgid "save.linux.mkdirp_failed" -msgstr "Failed to create save directory, check the disk is not full or write-protected." - -#: src/dusk/ui/frame/settings/uisettings.c -msgid "ui.settings.tabs.general" -msgstr "General" - -#: src/dusk/ui/frame/settings/uisettings.c -msgid "ui.settings.tabs.input" -msgstr "Input" - -#: src/dusk/ui/frame/settings/uisettings.c -msgid "ui.settings.tabs.display" -msgstr "Display" - -#: src/dusk/ui/frame/settings/uisettings.c -msgid "ui.settings.tabs.audio" -msgstr "Audio" - -msgid "ui.settings.input.deadzone" -msgstr "Deadzone" - -#: src/dusk/ui/frame/settings/uisettingsgeneral.c -msgid "ui.settings.general.language" -msgstr "Language" - -msgid "ui.settings.general.language_detail" -msgstr "Takes effect after restarting the application." - -#: src/dusk/ui/frame/settings/uisettings.c -msgid "ui.settings.apply" -msgstr "Apply" - -#: src/dusk/ui/frame/uiconfirm.c -msgid "ui.confirm.discard_changes" -msgstr "Discard unsaved changes?" - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.characters" -msgstr "Characters" - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.items" -msgstr "Items" - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.settings" -msgstr "Settings" - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.save" -msgstr "Save" - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.save_success" -msgstr "Game saved." - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.save_cancelled" -msgstr "Save cancelled." - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.save_unavailable" -msgstr "Can't save - no save device found." - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.save_temporary" -msgstr "This session is temporary - no save device was found, so saving is disabled." - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.save_create_confirm" -msgstr "No save data found. Create a new save?" - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.save_failed_format" -msgstr "Save failed: %s" - -#: src/dusk/ui/frame/game/uigamemenu.c -msgid "ui.game_menu.save_check_failed_format" -msgstr "Can't save: %s" - -#: src/dusk/ui/frame/initial/uiinitialnocard.c -msgid "ui.initial.no_card.message" -msgstr "No save device found. You can continue, but\nprogress will not be saved." - -#: src/dusk/ui/frame/initial/uiinitialnocard.c -msgid "ui.initial.no_card.retry" -msgstr "Retry" - -#: src/dusk/ui/frame/initial/uiinitialnocard.c -msgid "ui.initial.no_card.continue" -msgstr "Continue Anyway" - -#: src/dusk/ui/frame/initial/uiinitialcreatesave.c -msgid "ui.initial.create_save.message" -msgstr "No save data found. Create a new save?" - -#: src/dusk/ui/frame/initial/uiinitialcreatesave.c -msgid "ui.initial.create_save.yes" -msgstr "Yes" - -#: src/dusk/ui/frame/initial/uiinitialcreatesave.c -msgid "ui.initial.create_save.no" -msgstr "No" - -#: src/dusk/ui/frame/uiconfirm.c -msgid "ui.confirm.confirm" -msgstr "Confirm" - -#: src/dusk/ui/frame/uiconfirm.c -msgid "ui.confirm.cancel" -msgstr "Cancel" - -#: src/dusk/ui/frame/battle/uibattlemenu.c -msgid "ui.battle.menu.attack" -msgstr "Attack" - -#: src/dusk/ui/frame/battle/uibattlemenu.c -msgid "ui.battle.menu.flee" -msgstr "Flee" - -#: src/dusk/ui/frame/battle/uibattlemenu.c -msgid "ui.battle.menu.target_format" -msgstr "Enemy %u (%u/%u HP)" - -#: src/dusk/ui/frame/battle/uibattlehud.c -msgid "ui.battle.hud.hp_format" -msgstr "HP %u/%u" - -#: src/dusk/ui/frame/battle/uibattlehud.c -msgid "ui.battle.hud.mp_format" -msgstr "MP %u/%u" - -#: src/dusk/ui/frame/settings/uisettingsaudio.c -msgid "ui.settings.audio.placeholder" -msgstr "No audio settings yet" - -#: src/dusk/ui/frame/settings/uisettingsdisplay.c -msgid "ui.settings.display.placeholder" -msgstr "No display settings yet" - -#: src/dusk/ui/frame/settings/uisettingsinput.c -msgid "ui.settings.input.placeholder" -msgstr "No input settings yet" - -#: src/dusk/ui/frame/backpack/uibackpack.c -msgid "ui.backpack.category_format" -msgstr "Category %u" - -#: src/dusk/ui/overlay/uiloading.c -msgid "ui.loading.text" -msgstr "loading" - -#: src/dusk/ui/overlay/uiautosave.c -msgid "ui.autosave.saving" -msgstr "SAVING" - - -# -# Items -# -msgid "item.potion.name" -msgstr "Potion" - -msgid "item.potato.name" -msgstr "Potato" - -msgid "item.apple.name" -msgstr "Apple" - -# -# Moves -# -msgid "move.fire.name" -msgstr "Fire" - -msgid "move.slash.name" -msgstr "Slash" \ No newline at end of file diff --git a/assets/maps/overworld/chunks/chunk_-1_0_0.json b/assets/maps/overworld/chunks/chunk_-1_0_0.json deleted file mode 100644 index d0a994d6..00000000 --- a/assets/maps/overworld/chunks/chunk_-1_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"meshes":[{"file":"house_4_4.dmf","pos":[2,1,0]},{"file":"house_6_3.dmf","pos":[1,9,0]},{"file":"house_2_2.dmf","pos":[12,6,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]} \ No newline at end of file diff --git a/assets/maps/overworld/chunks/chunk_0_0_0.json b/assets/maps/overworld/chunks/chunk_0_0_0.json deleted file mode 100644 index ec0a47e0..00000000 --- a/assets/maps/overworld/chunks/chunk_0_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[6,0],[2,0],[2,0],[2,0],[2,0],[2,0],[2,0],[7,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[4,0],[12,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[9,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]],"meshes":[{"file":"house_5_3.dmf","pos":[1,1,0]}],"entities":[{"type":"global","globalId":3,"pos":[8,8,1]},{"type":"global","globalId":4,"pos":[12,2,0]}],"areas":[{"min":[11,3,0],"max":[16,9,10],"callbackId":1,"notify":3,"trigger":6}]} \ No newline at end of file diff --git a/assets/maps/overworld/chunks/chunk_0_1_0.json b/assets/maps/overworld/chunks/chunk_0_1_0.json deleted file mode 100644 index c22a5429..00000000 --- a/assets/maps/overworld/chunks/chunk_0_1_0.json +++ /dev/null @@ -1 +0,0 @@ -{"meshes":[{"file":"house_8_4.dmf","pos":[1,1,0]},{"file":"house_3_3.dmf","pos":[11,2,0]},{"file":"house_2_2.dmf","pos":[3,11,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]} \ No newline at end of file diff --git a/assets/maps/overworld/chunks/chunk_1_0_0.json b/assets/maps/overworld/chunks/chunk_1_0_0.json deleted file mode 100644 index 30644af6..00000000 --- a/assets/maps/overworld/chunks/chunk_1_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"meshes":[{"file":"house_3_2.dmf","pos":[1,1,0]},{"file":"house_2_3.dmf","pos":[7,2,0]},{"file":"house_4_2.dmf","pos":[1,8,0]},{"file":"house_1_1.dmf","pos":[11,10,0]},{"file":"house_5_2.dmf","pos":[13,6,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]} \ No newline at end of file diff --git a/assets/maps/overworld/chunks/chunk_1_1_0.json b/assets/maps/overworld/chunks/chunk_1_1_0.json deleted file mode 100644 index 003e8bc0..00000000 --- a/assets/maps/overworld/chunks/chunk_1_1_0.json +++ /dev/null @@ -1 +0,0 @@ -{"meshes":[{"file":"house_5_5.dmf","pos":[3,2,0]},{"file":"house_2_3.dmf","pos":[10,3,0]},{"file":"house_3_1.dmf","pos":[1,12,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]} \ No newline at end of file diff --git a/assets/maps/overworld/chunks/chunk_2_0_0.json b/assets/maps/overworld/chunks/chunk_2_0_0.json deleted file mode 100644 index 8b2d2e1b..00000000 --- a/assets/maps/overworld/chunks/chunk_2_0_0.json +++ /dev/null @@ -1 +0,0 @@ -{"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,3],[1,3],[1,3],[1,3],[1,3],[1,3],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,3],[1,3],[1,3],[1,3],[1,3],[1,3],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,3],[1,3],[1,3],[1,3],[1,3],[1,3],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[4,1],[4,1],[4,1],[4,1],[4,1],[4,1],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]],"meshes":[]} \ No newline at end of file diff --git a/assets/meshes/buildings/house_1_1.dmf b/assets/meshes/buildings/house_1_1.dmf deleted file mode 100644 index 0a409010..00000000 Binary files a/assets/meshes/buildings/house_1_1.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_1_2.dmf b/assets/meshes/buildings/house_1_2.dmf deleted file mode 100644 index c3081437..00000000 Binary files a/assets/meshes/buildings/house_1_2.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_1_3.dmf b/assets/meshes/buildings/house_1_3.dmf deleted file mode 100644 index 2cef82ef..00000000 Binary files a/assets/meshes/buildings/house_1_3.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_1_4.dmf b/assets/meshes/buildings/house_1_4.dmf deleted file mode 100644 index 4c6a9fb1..00000000 Binary files a/assets/meshes/buildings/house_1_4.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_1_5.dmf b/assets/meshes/buildings/house_1_5.dmf deleted file mode 100644 index 312a03cd..00000000 Binary files a/assets/meshes/buildings/house_1_5.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_1_6.dmf b/assets/meshes/buildings/house_1_6.dmf deleted file mode 100644 index 26730533..00000000 Binary files a/assets/meshes/buildings/house_1_6.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_1_7.dmf b/assets/meshes/buildings/house_1_7.dmf deleted file mode 100644 index 52ec8e45..00000000 Binary files a/assets/meshes/buildings/house_1_7.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_1_8.dmf b/assets/meshes/buildings/house_1_8.dmf deleted file mode 100644 index 9eea5b56..00000000 Binary files a/assets/meshes/buildings/house_1_8.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_2_1.dmf b/assets/meshes/buildings/house_2_1.dmf deleted file mode 100644 index 52187c10..00000000 Binary files a/assets/meshes/buildings/house_2_1.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_2_2.dmf b/assets/meshes/buildings/house_2_2.dmf deleted file mode 100644 index 0eeba1ba..00000000 Binary files a/assets/meshes/buildings/house_2_2.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_2_3.dmf b/assets/meshes/buildings/house_2_3.dmf deleted file mode 100644 index e232a5bc..00000000 Binary files a/assets/meshes/buildings/house_2_3.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_2_4.dmf b/assets/meshes/buildings/house_2_4.dmf deleted file mode 100644 index f1c9a1cc..00000000 Binary files a/assets/meshes/buildings/house_2_4.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_2_5.dmf b/assets/meshes/buildings/house_2_5.dmf deleted file mode 100644 index 76f6f453..00000000 Binary files a/assets/meshes/buildings/house_2_5.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_2_6.dmf b/assets/meshes/buildings/house_2_6.dmf deleted file mode 100644 index b200e0b6..00000000 Binary files a/assets/meshes/buildings/house_2_6.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_2_7.dmf b/assets/meshes/buildings/house_2_7.dmf deleted file mode 100644 index 5a5b5461..00000000 Binary files a/assets/meshes/buildings/house_2_7.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_2_8.dmf b/assets/meshes/buildings/house_2_8.dmf deleted file mode 100644 index 38b1707c..00000000 Binary files a/assets/meshes/buildings/house_2_8.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_3_1.dmf b/assets/meshes/buildings/house_3_1.dmf deleted file mode 100644 index a36cffe8..00000000 Binary files a/assets/meshes/buildings/house_3_1.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_3_2.dmf b/assets/meshes/buildings/house_3_2.dmf deleted file mode 100644 index b78ceb50..00000000 Binary files a/assets/meshes/buildings/house_3_2.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_3_3.dmf b/assets/meshes/buildings/house_3_3.dmf deleted file mode 100644 index d4e1e54f..00000000 Binary files a/assets/meshes/buildings/house_3_3.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_3_4.dmf b/assets/meshes/buildings/house_3_4.dmf deleted file mode 100644 index 61330418..00000000 Binary files a/assets/meshes/buildings/house_3_4.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_3_5.dmf b/assets/meshes/buildings/house_3_5.dmf deleted file mode 100644 index 80a2e82f..00000000 Binary files a/assets/meshes/buildings/house_3_5.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_3_6.dmf b/assets/meshes/buildings/house_3_6.dmf deleted file mode 100644 index bb150cf0..00000000 Binary files a/assets/meshes/buildings/house_3_6.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_3_7.dmf b/assets/meshes/buildings/house_3_7.dmf deleted file mode 100644 index b92dacd8..00000000 Binary files a/assets/meshes/buildings/house_3_7.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_3_8.dmf b/assets/meshes/buildings/house_3_8.dmf deleted file mode 100644 index 675c68de..00000000 Binary files a/assets/meshes/buildings/house_3_8.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_4_1.dmf b/assets/meshes/buildings/house_4_1.dmf deleted file mode 100644 index c3349b07..00000000 Binary files a/assets/meshes/buildings/house_4_1.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_4_2.dmf b/assets/meshes/buildings/house_4_2.dmf deleted file mode 100644 index f765e33c..00000000 Binary files a/assets/meshes/buildings/house_4_2.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_4_3.dmf b/assets/meshes/buildings/house_4_3.dmf deleted file mode 100644 index a2c0c241..00000000 Binary files a/assets/meshes/buildings/house_4_3.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_4_4.dmf b/assets/meshes/buildings/house_4_4.dmf deleted file mode 100644 index c4954890..00000000 Binary files a/assets/meshes/buildings/house_4_4.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_4_5.dmf b/assets/meshes/buildings/house_4_5.dmf deleted file mode 100644 index a6501df9..00000000 Binary files a/assets/meshes/buildings/house_4_5.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_4_6.dmf b/assets/meshes/buildings/house_4_6.dmf deleted file mode 100644 index 1f4c9390..00000000 Binary files a/assets/meshes/buildings/house_4_6.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_4_7.dmf b/assets/meshes/buildings/house_4_7.dmf deleted file mode 100644 index 160e5e1f..00000000 Binary files a/assets/meshes/buildings/house_4_7.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_4_8.dmf b/assets/meshes/buildings/house_4_8.dmf deleted file mode 100644 index 25edcb57..00000000 Binary files a/assets/meshes/buildings/house_4_8.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_5_1.dmf b/assets/meshes/buildings/house_5_1.dmf deleted file mode 100644 index e01e4b0f..00000000 Binary files a/assets/meshes/buildings/house_5_1.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_5_2.dmf b/assets/meshes/buildings/house_5_2.dmf deleted file mode 100644 index a90c62c6..00000000 Binary files a/assets/meshes/buildings/house_5_2.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_5_3.dmf b/assets/meshes/buildings/house_5_3.dmf deleted file mode 100644 index 5a1473e8..00000000 Binary files a/assets/meshes/buildings/house_5_3.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_5_4.dmf b/assets/meshes/buildings/house_5_4.dmf deleted file mode 100644 index 7eaf2865..00000000 Binary files a/assets/meshes/buildings/house_5_4.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_5_5.dmf b/assets/meshes/buildings/house_5_5.dmf deleted file mode 100644 index eff99b28..00000000 Binary files a/assets/meshes/buildings/house_5_5.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_5_6.dmf b/assets/meshes/buildings/house_5_6.dmf deleted file mode 100644 index 66cf5993..00000000 Binary files a/assets/meshes/buildings/house_5_6.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_5_7.dmf b/assets/meshes/buildings/house_5_7.dmf deleted file mode 100644 index d77782b7..00000000 Binary files a/assets/meshes/buildings/house_5_7.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_5_8.dmf b/assets/meshes/buildings/house_5_8.dmf deleted file mode 100644 index eabf0f66..00000000 Binary files a/assets/meshes/buildings/house_5_8.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_6_1.dmf b/assets/meshes/buildings/house_6_1.dmf deleted file mode 100644 index 9eb483a1..00000000 Binary files a/assets/meshes/buildings/house_6_1.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_6_2.dmf b/assets/meshes/buildings/house_6_2.dmf deleted file mode 100644 index 868abb7e..00000000 Binary files a/assets/meshes/buildings/house_6_2.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_6_3.dmf b/assets/meshes/buildings/house_6_3.dmf deleted file mode 100644 index 9241e0f8..00000000 Binary files a/assets/meshes/buildings/house_6_3.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_6_4.dmf b/assets/meshes/buildings/house_6_4.dmf deleted file mode 100644 index cda4f39b..00000000 Binary files a/assets/meshes/buildings/house_6_4.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_6_5.dmf b/assets/meshes/buildings/house_6_5.dmf deleted file mode 100644 index 94c60c01..00000000 Binary files a/assets/meshes/buildings/house_6_5.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_6_6.dmf b/assets/meshes/buildings/house_6_6.dmf deleted file mode 100644 index 75e4d9af..00000000 Binary files a/assets/meshes/buildings/house_6_6.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_6_7.dmf b/assets/meshes/buildings/house_6_7.dmf deleted file mode 100644 index b02e0515..00000000 Binary files a/assets/meshes/buildings/house_6_7.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_6_8.dmf b/assets/meshes/buildings/house_6_8.dmf deleted file mode 100644 index 637bfa21..00000000 Binary files a/assets/meshes/buildings/house_6_8.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_7_1.dmf b/assets/meshes/buildings/house_7_1.dmf deleted file mode 100644 index 0d26b2b4..00000000 Binary files a/assets/meshes/buildings/house_7_1.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_7_2.dmf b/assets/meshes/buildings/house_7_2.dmf deleted file mode 100644 index 9094d18c..00000000 Binary files a/assets/meshes/buildings/house_7_2.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_7_3.dmf b/assets/meshes/buildings/house_7_3.dmf deleted file mode 100644 index af0bd704..00000000 Binary files a/assets/meshes/buildings/house_7_3.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_7_4.dmf b/assets/meshes/buildings/house_7_4.dmf deleted file mode 100644 index 442b265f..00000000 Binary files a/assets/meshes/buildings/house_7_4.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_7_5.dmf b/assets/meshes/buildings/house_7_5.dmf deleted file mode 100644 index 275dc21d..00000000 Binary files a/assets/meshes/buildings/house_7_5.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_7_6.dmf b/assets/meshes/buildings/house_7_6.dmf deleted file mode 100644 index 64a40b77..00000000 Binary files a/assets/meshes/buildings/house_7_6.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_7_7.dmf b/assets/meshes/buildings/house_7_7.dmf deleted file mode 100644 index 0fac03bf..00000000 Binary files a/assets/meshes/buildings/house_7_7.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_7_8.dmf b/assets/meshes/buildings/house_7_8.dmf deleted file mode 100644 index 9eccf6f7..00000000 Binary files a/assets/meshes/buildings/house_7_8.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_8_1.dmf b/assets/meshes/buildings/house_8_1.dmf deleted file mode 100644 index 180481bf..00000000 Binary files a/assets/meshes/buildings/house_8_1.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_8_2.dmf b/assets/meshes/buildings/house_8_2.dmf deleted file mode 100644 index 611d63dd..00000000 Binary files a/assets/meshes/buildings/house_8_2.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_8_3.dmf b/assets/meshes/buildings/house_8_3.dmf deleted file mode 100644 index 6c57db41..00000000 Binary files a/assets/meshes/buildings/house_8_3.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_8_4.dmf b/assets/meshes/buildings/house_8_4.dmf deleted file mode 100644 index 6e31cd48..00000000 Binary files a/assets/meshes/buildings/house_8_4.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_8_5.dmf b/assets/meshes/buildings/house_8_5.dmf deleted file mode 100644 index 67ec1b55..00000000 Binary files a/assets/meshes/buildings/house_8_5.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_8_6.dmf b/assets/meshes/buildings/house_8_6.dmf deleted file mode 100644 index 00e36613..00000000 Binary files a/assets/meshes/buildings/house_8_6.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_8_7.dmf b/assets/meshes/buildings/house_8_7.dmf deleted file mode 100644 index fbffc4f7..00000000 Binary files a/assets/meshes/buildings/house_8_7.dmf and /dev/null differ diff --git a/assets/meshes/buildings/house_8_8.dmf b/assets/meshes/buildings/house_8_8.dmf deleted file mode 100644 index b29554d5..00000000 Binary files a/assets/meshes/buildings/house_8_8.dmf and /dev/null differ diff --git a/assets/models/buildings/house_1_1.json b/assets/models/buildings/house_1_1.json deleted file mode 100644 index f3231bce..00000000 --- a/assets/models/buildings/house_1_1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_1_1.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_1_2.json b/assets/models/buildings/house_1_2.json deleted file mode 100644 index 6962b659..00000000 --- a/assets/models/buildings/house_1_2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_1_2.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_1_3.json b/assets/models/buildings/house_1_3.json deleted file mode 100644 index 3c369e8e..00000000 --- a/assets/models/buildings/house_1_3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_1_3.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_1_4.json b/assets/models/buildings/house_1_4.json deleted file mode 100644 index 81b57861..00000000 --- a/assets/models/buildings/house_1_4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_1_4.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_1_5.json b/assets/models/buildings/house_1_5.json deleted file mode 100644 index 36d93d6c..00000000 --- a/assets/models/buildings/house_1_5.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_1_5.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_1_6.json b/assets/models/buildings/house_1_6.json deleted file mode 100644 index c544d562..00000000 --- a/assets/models/buildings/house_1_6.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_1_6.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_1_7.json b/assets/models/buildings/house_1_7.json deleted file mode 100644 index 7f419a27..00000000 --- a/assets/models/buildings/house_1_7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_1_7.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_1_8.json b/assets/models/buildings/house_1_8.json deleted file mode 100644 index b741c12f..00000000 --- a/assets/models/buildings/house_1_8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_1_8.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_2_1.json b/assets/models/buildings/house_2_1.json deleted file mode 100644 index 693ee13f..00000000 --- a/assets/models/buildings/house_2_1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_2_1.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_2_2.json b/assets/models/buildings/house_2_2.json deleted file mode 100644 index 83080cd5..00000000 --- a/assets/models/buildings/house_2_2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_2_2.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_2_3.json b/assets/models/buildings/house_2_3.json deleted file mode 100644 index 172b0784..00000000 --- a/assets/models/buildings/house_2_3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_2_3.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_2_4.json b/assets/models/buildings/house_2_4.json deleted file mode 100644 index c0b4a34b..00000000 --- a/assets/models/buildings/house_2_4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_2_4.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_2_5.json b/assets/models/buildings/house_2_5.json deleted file mode 100644 index a450c254..00000000 --- a/assets/models/buildings/house_2_5.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_2_5.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_2_6.json b/assets/models/buildings/house_2_6.json deleted file mode 100644 index 6b9e98ab..00000000 --- a/assets/models/buildings/house_2_6.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_2_6.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_2_7.json b/assets/models/buildings/house_2_7.json deleted file mode 100644 index b93f7400..00000000 --- a/assets/models/buildings/house_2_7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_2_7.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_2_8.json b/assets/models/buildings/house_2_8.json deleted file mode 100644 index a306084d..00000000 --- a/assets/models/buildings/house_2_8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_2_8.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_3_1.json b/assets/models/buildings/house_3_1.json deleted file mode 100644 index 8a51936f..00000000 --- a/assets/models/buildings/house_3_1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_3_1.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_3_2.json b/assets/models/buildings/house_3_2.json deleted file mode 100644 index 91a189a9..00000000 --- a/assets/models/buildings/house_3_2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_3_2.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_3_3.json b/assets/models/buildings/house_3_3.json deleted file mode 100644 index 98f13cca..00000000 --- a/assets/models/buildings/house_3_3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_3_3.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_3_4.json b/assets/models/buildings/house_3_4.json deleted file mode 100644 index f9413bde..00000000 --- a/assets/models/buildings/house_3_4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_3_4.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_3_5.json b/assets/models/buildings/house_3_5.json deleted file mode 100644 index 71507711..00000000 --- a/assets/models/buildings/house_3_5.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_3_5.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_3_6.json b/assets/models/buildings/house_3_6.json deleted file mode 100644 index 5eb7865d..00000000 --- a/assets/models/buildings/house_3_6.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_3_6.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_3_7.json b/assets/models/buildings/house_3_7.json deleted file mode 100644 index 7bcc8b5a..00000000 --- a/assets/models/buildings/house_3_7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_3_7.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_3_8.json b/assets/models/buildings/house_3_8.json deleted file mode 100644 index 0643ca47..00000000 --- a/assets/models/buildings/house_3_8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_3_8.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_4_1.json b/assets/models/buildings/house_4_1.json deleted file mode 100644 index 73a911cd..00000000 --- a/assets/models/buildings/house_4_1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_4_1.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_4_2.json b/assets/models/buildings/house_4_2.json deleted file mode 100644 index db055224..00000000 --- a/assets/models/buildings/house_4_2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_4_2.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_4_3.json b/assets/models/buildings/house_4_3.json deleted file mode 100644 index 64e39039..00000000 --- a/assets/models/buildings/house_4_3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_4_3.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_4_4.json b/assets/models/buildings/house_4_4.json deleted file mode 100644 index 8963ba12..00000000 --- a/assets/models/buildings/house_4_4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_4_4.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_4_5.json b/assets/models/buildings/house_4_5.json deleted file mode 100644 index c13ba9e3..00000000 --- a/assets/models/buildings/house_4_5.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_4_5.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_4_6.json b/assets/models/buildings/house_4_6.json deleted file mode 100644 index 9fc76393..00000000 --- a/assets/models/buildings/house_4_6.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_4_6.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_4_7.json b/assets/models/buildings/house_4_7.json deleted file mode 100644 index c7545cac..00000000 --- a/assets/models/buildings/house_4_7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_4_7.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_4_8.json b/assets/models/buildings/house_4_8.json deleted file mode 100644 index a470127f..00000000 --- a/assets/models/buildings/house_4_8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_4_8.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_5_1.json b/assets/models/buildings/house_5_1.json deleted file mode 100644 index 7966a3e8..00000000 --- a/assets/models/buildings/house_5_1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_5_1.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_5_2.json b/assets/models/buildings/house_5_2.json deleted file mode 100644 index cdbc0a7c..00000000 --- a/assets/models/buildings/house_5_2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_5_2.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_5_3.json b/assets/models/buildings/house_5_3.json deleted file mode 100644 index 657900a1..00000000 --- a/assets/models/buildings/house_5_3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_5_3.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_5_4.json b/assets/models/buildings/house_5_4.json deleted file mode 100644 index ce52eac1..00000000 --- a/assets/models/buildings/house_5_4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_5_4.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_5_5.json b/assets/models/buildings/house_5_5.json deleted file mode 100644 index 0223c690..00000000 --- a/assets/models/buildings/house_5_5.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_5_5.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_5_6.json b/assets/models/buildings/house_5_6.json deleted file mode 100644 index 769373be..00000000 --- a/assets/models/buildings/house_5_6.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_5_6.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_5_7.json b/assets/models/buildings/house_5_7.json deleted file mode 100644 index c428dbc2..00000000 --- a/assets/models/buildings/house_5_7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_5_7.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_5_8.json b/assets/models/buildings/house_5_8.json deleted file mode 100644 index 2efa8d5b..00000000 --- a/assets/models/buildings/house_5_8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_5_8.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_6_1.json b/assets/models/buildings/house_6_1.json deleted file mode 100644 index d2a4d5be..00000000 --- a/assets/models/buildings/house_6_1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_6_1.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_6_2.json b/assets/models/buildings/house_6_2.json deleted file mode 100644 index c3f0a02f..00000000 --- a/assets/models/buildings/house_6_2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_6_2.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_6_3.json b/assets/models/buildings/house_6_3.json deleted file mode 100644 index f145bf86..00000000 --- a/assets/models/buildings/house_6_3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_6_3.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_6_4.json b/assets/models/buildings/house_6_4.json deleted file mode 100644 index 6c37797c..00000000 --- a/assets/models/buildings/house_6_4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_6_4.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_6_5.json b/assets/models/buildings/house_6_5.json deleted file mode 100644 index b2d252ce..00000000 --- a/assets/models/buildings/house_6_5.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_6_5.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_6_6.json b/assets/models/buildings/house_6_6.json deleted file mode 100644 index e50bba0c..00000000 --- a/assets/models/buildings/house_6_6.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_6_6.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_6_7.json b/assets/models/buildings/house_6_7.json deleted file mode 100644 index 21ff16fd..00000000 --- a/assets/models/buildings/house_6_7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_6_7.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_6_8.json b/assets/models/buildings/house_6_8.json deleted file mode 100644 index a2c66829..00000000 --- a/assets/models/buildings/house_6_8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_6_8.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_7_1.json b/assets/models/buildings/house_7_1.json deleted file mode 100644 index 0017a2f4..00000000 --- a/assets/models/buildings/house_7_1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_7_1.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_7_2.json b/assets/models/buildings/house_7_2.json deleted file mode 100644 index d62cfc35..00000000 --- a/assets/models/buildings/house_7_2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_7_2.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_7_3.json b/assets/models/buildings/house_7_3.json deleted file mode 100644 index 112826b2..00000000 --- a/assets/models/buildings/house_7_3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_7_3.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_7_4.json b/assets/models/buildings/house_7_4.json deleted file mode 100644 index 3a68ee2e..00000000 --- a/assets/models/buildings/house_7_4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_7_4.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_7_5.json b/assets/models/buildings/house_7_5.json deleted file mode 100644 index 11a4aba9..00000000 --- a/assets/models/buildings/house_7_5.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_7_5.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_7_6.json b/assets/models/buildings/house_7_6.json deleted file mode 100644 index ab1f64e2..00000000 --- a/assets/models/buildings/house_7_6.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_7_6.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_7_7.json b/assets/models/buildings/house_7_7.json deleted file mode 100644 index 22262621..00000000 --- a/assets/models/buildings/house_7_7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_7_7.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_7_8.json b/assets/models/buildings/house_7_8.json deleted file mode 100644 index 8797c640..00000000 --- a/assets/models/buildings/house_7_8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_7_8.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_8_1.json b/assets/models/buildings/house_8_1.json deleted file mode 100644 index dc1860fa..00000000 --- a/assets/models/buildings/house_8_1.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_8_1.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_8_2.json b/assets/models/buildings/house_8_2.json deleted file mode 100644 index 4a32f105..00000000 --- a/assets/models/buildings/house_8_2.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_8_2.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_8_3.json b/assets/models/buildings/house_8_3.json deleted file mode 100644 index 2b7c73b7..00000000 --- a/assets/models/buildings/house_8_3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_8_3.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_8_4.json b/assets/models/buildings/house_8_4.json deleted file mode 100644 index 29a2a49c..00000000 --- a/assets/models/buildings/house_8_4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_8_4.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_8_5.json b/assets/models/buildings/house_8_5.json deleted file mode 100644 index 54788a80..00000000 --- a/assets/models/buildings/house_8_5.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_8_5.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_8_6.json b/assets/models/buildings/house_8_6.json deleted file mode 100644 index 6d2e35b6..00000000 --- a/assets/models/buildings/house_8_6.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_8_6.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_8_7.json b/assets/models/buildings/house_8_7.json deleted file mode 100644 index 41009c69..00000000 --- a/assets/models/buildings/house_8_7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_8_7.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/models/buildings/house_8_8.json b/assets/models/buildings/house_8_8.json deleted file mode 100644 index 800d2758..00000000 --- a/assets/models/buildings/house_8_8.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "mesh": "meshes/buildings/house_8_8.dmf", - "color": [255, 0, 255, 255] -} diff --git a/assets/party.jsonc b/assets/party.jsonc deleted file mode 100644 index 260acbc1..00000000 --- a/assets/party.jsonc +++ /dev/null @@ -1,22 +0,0 @@ -{ - "members": { - "hero": { - "attack": 10, - "defense": 5, - "magic": 0, - "speed": 10, - "luck": 0, - "healthMax": 30, - "mpMax": 10 - }, - "companion": { - "attack": 8, - "defense": 6, - "magic": 4, - "speed": 8, - "luck": 1, - "healthMax": 25, - "mpMax": 15 - } - } -} diff --git a/assets/story/storyflag.jsonc b/assets/story/storyflag.jsonc deleted file mode 100644 index f2738e1b..00000000 --- a/assets/story/storyflag.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "flags": { - "test": { - "initial": true - }, - "test2": { - "initial": 1 - } - } -} diff --git a/assets/tiles.png b/assets/tiles.png deleted file mode 100644 index bc170af1..00000000 Binary files a/assets/tiles.png and /dev/null differ diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index 743dbaa7..b170a90f 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -77,6 +77,7 @@ add_subdirectory(console) add_subdirectory(display) add_subdirectory(log) add_subdirectory(engine) +add_subdirectory(entity) add_subdirectory(error) add_subdirectory(input) add_subdirectory(locale) diff --git a/src/dusk/rpg/battle/fighter/CMakeLists.txt b/src/dusk/entity/CMakeLists.txt similarity index 68% rename from src/dusk/rpg/battle/fighter/CMakeLists.txt rename to src/dusk/entity/CMakeLists.txt index bc721bd2..e886dae7 100644 --- a/src/dusk/rpg/battle/fighter/CMakeLists.txt +++ b/src/dusk/entity/CMakeLists.txt @@ -6,5 +6,10 @@ # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC - battlefighter.c + entity.c + entitymanager.c + component.c ) + +# Subdirs +add_subdirectory(component) diff --git a/src/dusk/entity/component.c b/src/dusk/entity/component.c new file mode 100644 index 00000000..58512710 --- /dev/null +++ b/src/dusk/entity/component.c @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entitymanager.h" +#include "assert/assert.h" +#include "util/memory.h" + +componentdefinition_t COMPONENT_DEFINITIONS[] = { + [COMPONENT_TYPE_NULL] = { 0 }, + + #define X(enm, type, field, iMethod, dMethod, rMethod) \ + [COMPONENT_TYPE_##enm] = { \ + .enumName = #enm, \ + .name = #field, \ + .init = iMethod, \ + .dispose = dMethod, \ + .render = rMethod \ + }, + + #include "componentlist.h" + #undef X + + [COMPONENT_TYPE_COUNT] = { 0 } +}; + +void componentInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const componenttype_t type +) { + assertNotNull(mgr, "Entity manager cannot be null"); + assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); + assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); + assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB"); + assertTrue(type != COMPONENT_TYPE_NULL, "Cannot initialize null component"); + + componentindex_t index = componentGetIndex(entityId, componentId); + component_t *cmp = &mgr->components[index]; + memoryZero(cmp, sizeof(component_t)); + + cmp->type = type; + if(COMPONENT_DEFINITIONS[type].init) { + COMPONENT_DEFINITIONS[type].init(mgr, entityId, componentId); + } +} + +void * componentGetData( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const componenttype_t type +) { + assertNotNull(mgr, "Entity manager cannot be null"); + assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); + assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); + assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB"); + assertTrue(type != COMPONENT_TYPE_NULL, "Cannot get data of null component"); + + componentindex_t index = componentGetIndex(entityId, componentId); + component_t *cmp = &mgr->components[index]; + assertTrue(cmp->type == type, "Component type mismatch"); + + return &cmp->data; +} + +componentindex_t componentGetIndex( + const entityid_t entityId, + const componentid_t componentId +) { + assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); + assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); + return (entityId * ENTITY_COMPONENT_COUNT_MAX) + componentId; +} + +entityid_t componentGetEntitiesWithComponent( + entitymanager_t *mgr, + const componenttype_t type, + entityid_t outEntities[ENTITY_COUNT_MAX], + componentid_t outComponents[ENTITY_COUNT_MAX] +) { + assertNotNull(mgr, "Entity manager cannot be null"); + assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB"); + assertTrue(type != COMPONENT_TYPE_NULL, "Cannot check NULL type"); + assertNotNull(outEntities, "Output entities array cannot be null"); + assertNotNull(outComponents, "Output components array cannot be null"); + + entityid_t written = 0; + for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) { + componentid_t used = mgr->entitiesWithComponent[ + type * ENTITY_COUNT_MAX + i + ]; + if(used == COMPONENT_ID_INVALID) continue; + assertTrue( + mgr->components[componentGetIndex(i, used)].type == type, + "Component type mismatch in entitiesWithComponent lookup" + ); + assertTrue( + (mgr->entities[i].state & ENTITY_STATE_ACTIVE) != 0, + "Inactive entity in entitiesWithComponent lookup" + ); + assertTrue( + used < ENTITY_COMPONENT_COUNT_MAX, + "Component ID OOB in entitiesWithComponent lookup" + ); + assertTrue( + componentGetIndex(i,used) < ENTITY_COUNT_MAX*ENTITY_COMPONENT_COUNT_MAX, + "Component index OOB in entitiesWithComponent lookup" + ); + outComponents[written] = used; + outEntities[written++] = i; + } + return written; +} + +errorret_t componentRenderAll(entitymanager_t *mgr) { + assertNotNull(mgr, "Entity manager cannot be null"); + for(entityid_t eid = 0; eid < ENTITY_COUNT_MAX; eid++) { + if(!(mgr->entities[eid].state & ENTITY_STATE_ACTIVE)) continue; + for(componentid_t cid = 0; cid < ENTITY_COMPONENT_COUNT_MAX; cid++) { + component_t *cmp = &mgr->components[componentGetIndex(eid, cid)]; + if(cmp->type == COMPONENT_TYPE_NULL) continue; + if(!COMPONENT_DEFINITIONS[cmp->type].render) continue; + errorChain(COMPONENT_DEFINITIONS[cmp->type].render(mgr, eid, cid)); + } + } + errorOk(); +} + +void componentDispose( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + assertNotNull(mgr, "Entity manager cannot be null"); + assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); + assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); + + componentindex_t index = componentGetIndex(entityId, componentId); + component_t *cmp = &mgr->components[index]; + if(cmp->type == COMPONENT_TYPE_NULL) return; + + if(COMPONENT_DEFINITIONS[cmp->type].dispose) { + COMPONENT_DEFINITIONS[cmp->type].dispose(mgr, entityId, componentId); + } + + cmp->type = COMPONENT_TYPE_NULL; +} diff --git a/src/dusk/entity/component.h b/src/dusk/entity/component.h new file mode 100644 index 00000000..a1ee156a --- /dev/null +++ b/src/dusk/entity/component.h @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entitybase.h" +#include "error/error.h" + +#define X(enumName, type, field, init, dispose, render) \ + // do nothing +#include "componentlist.h" +#undef X + +typedef union { + #define X(enumName, type, field, init, dispose, render) type field; + #include "componentlist.h" + #undef X +} componentdata_t; + +/** + * Callback signature for a component's init/dispose hooks. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + */ +typedef void (*componentcallback_t)( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Callback signature for a component's render hook. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return Error state. + */ +typedef errorret_t (*componentcallbackerror_t)( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +typedef struct { + const char_t *enumName; + const char_t *name; + componentcallback_t init; + componentcallback_t dispose; + componentcallbackerror_t render; +} componentdefinition_t; + +typedef enum { + COMPONENT_TYPE_NULL, + + #define X(enumName, type, field, init, dispose, render) \ + COMPONENT_TYPE_##enumName, + #include "componentlist.h" + #undef X + + COMPONENT_TYPE_COUNT +} componenttype_t; + +typedef struct { + componenttype_t type; + componentdata_t data; +} component_t; + +extern componentdefinition_t COMPONENT_DEFINITIONS[]; + +/** + * Initializes a component of the given type for the entity with component ID. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param type The type of the component to initialize. + */ +void componentInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const componenttype_t type +); + +/** + * Gets the pointer to the data of a component for the entity with component ID. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param type The type of the component to get, only used for assertion. + * @return A pointer to the component data. + */ +void * componentGetData( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const componenttype_t type +); + +/** + * Gets the index of a component for the entity with component ID. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The index of the component in the component array. + */ +componentindex_t componentGetIndex( + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Gets the entity IDs of all entities with a component of the given type. + * + * @param mgr The entity manager to search. + * @param type The type of the component to get entities for. + * @param outEntities An array to write the entity IDs to, must be at least + * ENTITY_COUNT_MAX in size. + * @param outComponents An array to write the component IDs to. + * @return The number of entity IDs written to outEntities. + */ +entityid_t componentGetEntitiesWithComponent( + entitymanager_t *mgr, + const componenttype_t type, + entityid_t outEntities[ENTITY_COUNT_MAX], + componentid_t outComponents[ENTITY_COUNT_MAX] +); + +/** + * Disposes of a component for the entity with component ID. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void componentDispose( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Calls the render callback on every active component that defines one. + * Iterates all active entities and all their component slots. No-op for + * components whose definition has render == NULL. + * + * @param mgr The entity manager to render. + * @return Error state. + */ +errorret_t componentRenderAll(entitymanager_t *mgr); diff --git a/src/dusk/rpg/cutscene/item/item/CMakeLists.txt b/src/dusk/entity/component/CMakeLists.txt similarity index 52% rename from src/dusk/rpg/cutscene/item/item/CMakeLists.txt rename to src/dusk/entity/component/CMakeLists.txt index df5ebc21..c97bb6ee 100644 --- a/src/dusk/rpg/cutscene/item/item/CMakeLists.txt +++ b/src/dusk/entity/component/CMakeLists.txt @@ -3,7 +3,8 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutsceneitemgive.c -) +# Subdirs +add_subdirectory(display) +add_subdirectory(physics) +add_subdirectory(trigger) +add_subdirectory(animation) diff --git a/src/dusk/rpg/overworld/global/CMakeLists.txt b/src/dusk/entity/component/animation/CMakeLists.txt similarity index 89% rename from src/dusk/rpg/overworld/global/CMakeLists.txt rename to src/dusk/entity/component/animation/CMakeLists.txt index 27caaac2..f0d4d756 100644 --- a/src/dusk/rpg/overworld/global/CMakeLists.txt +++ b/src/dusk/entity/component/animation/CMakeLists.txt @@ -6,4 +6,5 @@ # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC + entityanimation.c ) diff --git a/src/dusk/entity/component/animation/entityanimation.c b/src/dusk/entity/component/animation/entityanimation.c new file mode 100644 index 00000000..88419e54 --- /dev/null +++ b/src/dusk/entity/component/animation/entityanimation.c @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entityanimation.h" +#include "entity/entitymanager.h" +#include "util/memory.h" +#include "time/time.h" + +void entityAnimationInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityanimation_t *animComp = entityAnimationGet(mgr, entityId, componentId); + + memoryZero(animComp, sizeof(entityanimation_t)); + animComp->speed = 1.0f; + + entityUpdateAdd(mgr, entityId, entityAnimationUpdate, componentId, NULL); +} + +entityanimation_t *entityAnimationGet( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + return componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_ANIMATION + ); +} + +void entityAnimationSetKeyframes( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + keyframe_t *keyframes, + uint16_t *keyframeCounts, + const uint16_t layerCount +) { + entityanimation_t *animComp = entityAnimationGet(mgr, entityId, componentId); + animationInit(&animComp->anim, keyframes, keyframeCounts, layerCount); + animComp->speed = 1.0f; + animComp->playing = false; +} + +void entityAnimationPlay( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityanimation_t *animComp = entityAnimationGet(mgr, entityId, componentId); + animComp->anim.time = 0.0f; + // Clear the internal completed latch so a previously-finished + // (non-looping) animation can actually advance again. + animComp->anim.flags &= ~ANIMATION_FLAG_INTERNAL_COMPLETED; + animComp->playing = true; +} + +void entityAnimationStop( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityanimation_t *animComp = entityAnimationGet(mgr, entityId, componentId); + animComp->playing = false; +} + +bool_t entityAnimationIsPlaying( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityanimation_t *animComp = entityAnimationGet(mgr, entityId, componentId); + return animComp->playing; +} + +void entityAnimationSetLoop( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const bool_t loop +) { + entityanimation_t *animComp = entityAnimationGet(mgr, entityId, componentId); + if(loop) animComp->anim.flags |= ANIMATION_FLAG_LOOP; + else animComp->anim.flags &= ~ANIMATION_FLAG_LOOP; +} + +void entityAnimationSetSpeed( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const float_t speed +) { + entityanimation_t *animComp = entityAnimationGet(mgr, entityId, componentId); + animComp->speed = speed; +} + +float_t entityAnimationGetValue( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint16_t channelIndex +) { + entityanimation_t *animComp = entityAnimationGet(mgr, entityId, componentId); + return animationGetLayerValue(&animComp->anim, channelIndex); +} + +void entityAnimationUpdate( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + void *user +) { + entityanimation_t *animComp = entityAnimationGet(mgr, entityId, componentId); + if(!animComp->playing) return; + animationUpdate(&animComp->anim, TIME.delta * animComp->speed); +} diff --git a/src/dusk/entity/component/animation/entityanimation.h b/src/dusk/entity/component/animation/entityanimation.h new file mode 100644 index 00000000..0fe2e3d3 --- /dev/null +++ b/src/dusk/entity/component/animation/entityanimation.h @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" +#include "animation/animation.h" + +typedef struct { + animation_t anim; + + /** + * Playback rate multiplier; 1.0 = normal speed. This branch's + * animation_t has no speed field of its own (animationUpdate() just + * takes a deltaTime), so the multiplier is tracked here and applied to + * TIME.delta before calling animationUpdate() each tick. + */ + float_t speed; + + /** + * True while entityAnimationUpdate() should advance anim's time each + * tick. This branch's animation_t has no playing field of its own -- + * advancing is purely a function of whether animationUpdate() is + * called -- so playback gating lives here instead. + */ + bool_t playing; +} entityanimation_t; + +/** + * Initializes the animation component: no keyframes set, and registers + * entityAnimationUpdate as an update callback. Call + * entityAnimationSetKeyframes() before entityAnimationPlay(). + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void entityAnimationInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Gets the underlying animation structure (temporarily) for the given + * entity. Prefer the dedicated getters/setters where possible. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The animation component data for the given entity and + * component ID. + */ +entityanimation_t *entityAnimationGet( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets the entity's keyframes, stopped, at speed 1.0, non-looping. See + * animationInit() -- keyframes/keyframeCounts and the keyframe_t data + * they point to are not copied, and must outlive this component (e.g. + * static/const arrays owned by the caller). + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param keyframes Flat array of keyframe_t, one contiguous run per layer + * (layer's run length is keyframeCounts[layer]) -- see animationInit(). + * @param keyframeCounts Array of layerCount keyframe counts, matching the + * per-layer runs in keyframes. + * @param layerCount The number of layers/channels. + */ +void entityAnimationSetKeyframes( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + keyframe_t *keyframes, + uint16_t *keyframeCounts, + const uint16_t layerCount +); + +/** + * Starts (or restarts) playback from time 0. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void entityAnimationPlay( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Stops playback without resetting the current time. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void entityAnimationStop( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Checks whether the animation is currently playing. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return True if playing. + */ +bool_t entityAnimationIsPlaying( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets whether the animation loops on reaching its final keyframe, rather + * than stopping there. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param loop True to loop, false to stop at the end. + */ +void entityAnimationSetLoop( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const bool_t loop +); + +/** + * Sets the animation's playback rate multiplier. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param speed The new playback rate; 1.0 = normal speed. + */ +void entityAnimationSetSpeed( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const float_t speed +); + +/** + * Evaluates one of the animation's channels at its current playback + * time, regardless of whether it's currently playing. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param channelIndex The channel to evaluate, in [0, channelCount) as + * passed to entityAnimationSetKeyframes(). + * @return That channel's value at the current time. + */ +float_t entityAnimationGetValue( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint16_t channelIndex +); + +/** + * Per-tick update for the animation component: calls animationUpdate() + * (a no-op if not playing). Registered automatically as an update + * callback by entityAnimationInit. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param user Unused. + */ +void entityAnimationUpdate( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + void *user +); diff --git a/src/dusk/rpg/entity/npc/CMakeLists.txt b/src/dusk/entity/component/display/CMakeLists.txt similarity index 75% rename from src/dusk/rpg/entity/npc/CMakeLists.txt rename to src/dusk/entity/component/display/CMakeLists.txt index 922a4e1d..c6b8538b 100644 --- a/src/dusk/rpg/entity/npc/CMakeLists.txt +++ b/src/dusk/entity/component/display/CMakeLists.txt @@ -6,8 +6,7 @@ # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC - npc.c - npcturn.c - npcwalk.c - npcpath.c + entityposition.c + entitycamera.c + entityrenderable.c ) diff --git a/src/dusk/entity/component/display/entitycamera.c b/src/dusk/entity/component/display/entitycamera.c new file mode 100644 index 00000000..06697054 --- /dev/null +++ b/src/dusk/entity/component/display/entitycamera.c @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entity/entitymanager.h" +#include "entity/entity.h" +#include "entity/component/display/entityposition.h" +#include "display/screen/screen.h" + +void entityCameraInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_CAMERA + ); + cam->nearClip = 0.1f; + cam->farClip = 5000.0f; + cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE; + cam->perspective.fov = glm_rad(45.0f); +} + +void entityCameraGetProjection( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + mat4 out +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_CAMERA + ); + + if( + cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE || + cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED + ) { + glm_mat4_identity(out); + glm_perspective( + cam->perspective.fov, + SCREEN.aspect, + cam->nearClip, + cam->farClip, + out + ); + + if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED) { + out[1][1] *= -1.0f; + } + } else if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { + glm_mat4_identity(out); + glm_ortho( + cam->orthographic.left, + cam->orthographic.right, + cam->orthographic.top, + cam->orthographic.bottom, + cam->nearClip, + cam->farClip, + out + ); + } +} + +entityid_t entityCameraGetCurrent(entitymanager_t *mgr) { + entityid_t camEnts[ENTITY_COUNT_MAX]; + componentid_t camComps[ENTITY_COUNT_MAX]; + entityid_t count = componentGetEntitiesWithComponent( + mgr, COMPONENT_TYPE_CAMERA, camEnts, camComps + ); + if(count == 0) return ENTITY_ID_INVALID; + return camEnts[0]; +} + +void entityCameraGetForward( + entitymanager_t *mgr, + const entityid_t entityId, + vec2 out +) { + componentid_t posComp = entityGetComponent( + mgr, entityId, COMPONENT_TYPE_POSITION + ); + mat4 transform; + entityPositionGetTransform(mgr, entityId, posComp, transform); + // transform is an object->world matrix; column 2 is the entity's local Z + // axis expressed in world space. Cameras look down their local -Z. + float_t fx = -transform[2][0]; + float_t fz = -transform[2][2]; + float_t len = sqrtf(fx * fx + fz * fz); + if(len > 1e-6f) { fx /= len; fz /= len; } + out[0] = fx; + out[1] = fz; +} + +void entityCameraGetRight( + entitymanager_t *mgr, + const entityid_t entityId, + vec2 out +) { + componentid_t posComp = entityGetComponent( + mgr, entityId, COMPONENT_TYPE_POSITION + ); + mat4 transform; + entityPositionGetTransform(mgr, entityId, posComp, transform); + // transform is an object->world matrix; column 0 is the entity's local X + // (right) axis expressed in world space. + float_t rx = transform[0][0]; + float_t rz = transform[0][2]; + float_t len = sqrtf(rx * rx + rz * rz); + if(len > 1e-6f) { rx /= len; rz /= len; } + out[0] = rx; + out[1] = rz; +} + +void entityCameraLookAtPixelPerfect( + entitymanager_t *mgr, + const entityid_t ent, + const componentid_t posComp, + const componentid_t camComp, + const vec3 point, + const vec3 eyeOffset, + const float_t scale +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + mgr, ent, camComp, COMPONENT_TYPE_CAMERA + ); + float_t dist = ( + (float_t)SCREEN.height / (2.0f * scale * tanf(cam->perspective.fov * 0.5f)) + ); + + vec3 eye = { + point[0] + eyeOffset[0], + point[1] + dist + eyeOffset[1], + point[2] + eyeOffset[2] + }; + vec3 up = { 0.0f, 0.0f, -1.0f }; + entityPositionLookAt(mgr, ent, posComp, eye, (float_t *)point, up); +} diff --git a/src/dusk/entity/component/display/entitycamera.h b/src/dusk/entity/component/display/entitycamera.h new file mode 100644 index 00000000..5a52c568 --- /dev/null +++ b/src/dusk/entity/component/display/entitycamera.h @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" +#include "error/error.h" + +typedef enum { + ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE, + ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED, + ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC +} entitycameraprojectiontype_t; + +typedef struct { + union { + struct { + float_t fov; + } perspective; + + struct { + float_t left; + float_t right; + float_t top; + float_t bottom; + } orthographic; + }; + + float_t nearClip; + float_t farClip; + entitycameraprojectiontype_t projType; +} entitycamera_t; + +/** + * Initializes an entity camera component. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void entityCameraInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Renders out the projection matrix for the given camera. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param out The output projection matrix. + */ +void entityCameraGetProjection( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + mat4 out +); + +/** + * Returns the entity ID of the first active camera, or ENTITY_ID_INVALID if + * none are active. + * + * @param mgr The entity manager to search. + */ +entityid_t entityCameraGetCurrent(entitymanager_t *mgr); + +/** + * Gets the camera's horizontal forward direction (XZ plane) from its + * position component. Automatically finds the position component on the + * entity. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The camera entity ID. + * @param out Output vec2: {forwardX, forwardZ} normalized. + */ +void entityCameraGetForward( + entitymanager_t *mgr, + const entityid_t entityId, + vec2 out +); + +/** + * Gets the camera's horizontal right direction (XZ plane) from its position + * component. Automatically finds the position component on the entity. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The camera entity ID. + * @param out Output vec2: {rightX, rightZ} normalized. + */ +void entityCameraGetRight( + entitymanager_t *mgr, + const entityid_t entityId, + vec2 out +); + +/** + * Positions the camera to look at a 3D point at a pixel-perfect distance + * derived from the camera's FOV and screen height. + * + * @param mgr The entity manager that owns the entity. + * @param ent The camera entity ID. + * @param posComp The position component ID. + * @param camComp The camera component ID. + * @param point World position to look at. + * @param eyeOffset Offset added to the eye position only (not the target). + * @param scale Pixels per world unit. 1.0 = pixel perfect, 2.0 = 2px per unit. + */ +void entityCameraLookAtPixelPerfect( + entitymanager_t *mgr, + const entityid_t ent, + const componentid_t posComp, + const componentid_t camComp, + const vec3 point, + const vec3 eyeOffset, + const float_t scale +); diff --git a/src/dusk/entity/component/display/entityposition.c b/src/dusk/entity/component/display/entityposition.c new file mode 100644 index 00000000..bddd61d6 --- /dev/null +++ b/src/dusk/entity/component/display/entityposition.c @@ -0,0 +1,669 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entity/entitymanager.h" +#include "assert/assert.h" + +void entityPositionInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + + pos->flags = 0; + pos->parentEntityId = ENTITY_ID_INVALID; + pos->parentComponentId = COMPONENT_ID_INVALID; + pos->childCount = 0; + glm_vec3_zero(pos->position); + glm_vec3_zero(pos->rotation); + glm_vec3_one(pos->scale); + glm_mat4_identity(pos->localTransform); + glm_mat4_identity(pos->worldTransform); +} + +void entityPositionLookAt( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 eye, + vec3 target, + vec3 up +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + // glm_lookat() produces a view matrix (world -> eye space). Every other + // setter treats localTransform as this entity's placement in world space + // (eye -> world), so invert it here to keep that meaning consistent -- + // callers that need a view matrix (e.g. scene rendering) invert it back. + mat4 view; + glm_lookat(eye, target, up, view); + glm_mat4_inv(view, pos->localTransform); + // localTransform is now authoritative; PRS cache is stale. + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_PRS_DIRTY) + & ~(ENTITY_POSITION_FLAG_ROTATION_DIRTY | + ENTITY_POSITION_FLAG_POSITION_DIRTY); + entityPositionMarkDirty(mgr, pos); +} + +void entityPositionGetTransform( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + mat4 dest +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + entityPositionEnsureWorld(mgr, pos); + glm_mat4_copy( + pos->parentEntityId == ENTITY_ID_INVALID + ? pos->localTransform : pos->worldTransform, + dest + ); +} + +void entityPositionGetLocalTransform( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + mat4 dest +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + entityPositionEnsureLocal(pos); + glm_mat4_copy(pos->localTransform, dest); +} + +void entityPositionGetLocalPosition( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + entityPositionEnsurePRS(pos); + glm_vec3_copy(pos->position, dest); +} + +void entityPositionGetWorldPosition( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + if(pos->parentEntityId == ENTITY_ID_INVALID) { + entityPositionEnsurePRS(pos); + glm_vec3_copy(pos->position, dest); + return; + } + entityPositionEnsureWorld(mgr, pos); + dest[0] = pos->worldTransform[3][0]; + dest[1] = pos->worldTransform[3][1]; + dest[2] = pos->worldTransform[3][2]; +} + +void entityPositionSetWorldPosition( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 position +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + if(pos->parentEntityId == ENTITY_ID_INVALID) { + glm_vec3_copy(position, pos->position); + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_POSITION_DIRTY) + & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); + return; + } + entityposition_t *parent = componentGetData( + mgr, pos->parentEntityId, pos->parentComponentId, COMPONENT_TYPE_POSITION + ); + entityPositionEnsureWorld(mgr, parent); + mat4 invParent; + glm_mat4_inv(parent->worldTransform, invParent); + vec3 localPos; + glm_mat4_mulv3(invParent, position, 1.0f, localPos); + glm_vec3_copy(localPos, pos->position); + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_POSITION_DIRTY) + & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); +} + +void entityPositionSetLocalPosition( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 position +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + glm_vec3_copy(position, pos->position); + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_POSITION_DIRTY) + & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); +} + +void entityPositionGetLocalRotation( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + entityPositionEnsurePRS(pos); + glm_vec3_copy(pos->rotation, dest); +} + +void entityPositionGetWorldRotation( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + if(pos->parentEntityId == ENTITY_ID_INVALID) { + entityPositionEnsurePRS(pos); + glm_vec3_copy(pos->rotation, dest); + return; + } + entityPositionEnsureWorld(mgr, pos); + const float_t (*wt)[4] = pos->worldTransform; + const float_t sx = sqrtf( + wt[0][0]*wt[0][0] + wt[0][1]*wt[0][1] + wt[0][2]*wt[0][2] + ); + const float_t sy = sqrtf( + wt[1][0]*wt[1][0] + wt[1][1]*wt[1][1] + wt[1][2]*wt[1][2] + ); + const float_t sz = sqrtf( + wt[2][0]*wt[2][0] + wt[2][1]*wt[2][1] + wt[2][2]*wt[2][2] + ); + const float_t r00 = sx > 0.0f ? wt[0][0]/sx : 0.0f; + const float_t r10 = sy > 0.0f ? wt[1][0]/sy : 0.0f; + const float_t r20 = sz > 0.0f ? wt[2][0]/sz : 0.0f; + const float_t r01 = sx > 0.0f ? wt[0][1]/sx : 0.0f; + const float_t r11 = sy > 0.0f ? wt[1][1]/sy : 0.0f; + const float_t r21 = sz > 0.0f ? wt[2][1]/sz : 0.0f; + const float_t r22 = sz > 0.0f ? wt[2][2]/sz : 0.0f; + const float_t sinBeta = glm_clamp(r20, -1.0f, 1.0f); + dest[1] = asinf(sinBeta); + const float_t cosBeta = cosf(dest[1]); + if(fabsf(cosBeta) > 1e-6f) { + dest[0] = atan2f(-r21, r22); + dest[2] = atan2f(-r10, r00); + } else { + dest[2] = 0.0f; + dest[0] = (sinBeta > 0.0f) ? atan2f(r01, r11) : -atan2f(r01, r11); + } +} + +void entityPositionSetLocalRotation( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 rotation +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + glm_vec3_copy(rotation, pos->rotation); + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_ROTATION_DIRTY) + & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); +} + +void entityPositionSetWorldRotation( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 rotation +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + if(pos->parentEntityId == ENTITY_ID_INVALID) { + glm_vec3_copy(rotation, pos->rotation); + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_ROTATION_DIRTY) + & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); + return; + } + entityposition_t *parent = componentGetData( + mgr, pos->parentEntityId, pos->parentComponentId, COMPONENT_TYPE_POSITION + ); + entityPositionEnsureWorld(mgr, parent); + + // Build target world rotation matrix (unit scale) from XYZ euler. + const float_t c0 = cosf(rotation[0]), s0 = sinf(rotation[0]); + const float_t c1 = cosf(rotation[1]), s1 = sinf(rotation[1]); + const float_t c2 = cosf(rotation[2]), s2 = sinf(rotation[2]); + const float_t s0s1 = s0*s1, c0s1 = c0*s1; + // Named wr[col_stored][row_stored] matching cglm column-major layout. + const float_t wr00 = c1*c2; + const float_t wr01 = c0*s2 + s0s1*c2; + const float_t wr02 = s0*s2 - c0s1*c2; + const float_t wr10 = -c1*s2; + const float_t wr11 = c0*c2 - s0s1*s2; + const float_t wr12 = s0*c2 + c0s1*s2; + const float_t wr20 = s1; + const float_t wr21 = -s0*c1; + const float_t wr22 = c0*c1; + + // Normalize parent world columns to extract pure rotation. + const float_t (*pt)[4] = parent->worldTransform; + const float_t psx = sqrtf( + pt[0][0]*pt[0][0] + pt[0][1]*pt[0][1] + pt[0][2]*pt[0][2] + ); + const float_t psy = sqrtf( + pt[1][0]*pt[1][0] + pt[1][1]*pt[1][1] + pt[1][2]*pt[1][2] + ); + const float_t psz = sqrtf( + pt[2][0]*pt[2][0] + pt[2][1]*pt[2][1] + pt[2][2]*pt[2][2] + ); + const float_t pr00 = psx > 0.f ? pt[0][0]/psx : 0.f; + const float_t pr01 = psx > 0.f ? pt[0][1]/psx : 0.f; + const float_t pr02 = psx > 0.f ? pt[0][2]/psx : 0.f; + const float_t pr10 = psy > 0.f ? pt[1][0]/psy : 0.f; + const float_t pr11 = psy > 0.f ? pt[1][1]/psy : 0.f; + const float_t pr12 = psy > 0.f ? pt[1][2]/psy : 0.f; + const float_t pr20 = psz > 0.f ? pt[2][0]/psz : 0.f; + const float_t pr21 = psz > 0.f ? pt[2][1]/psz : 0.f; + const float_t pr22 = psz > 0.f ? pt[2][2]/psz : 0.f; + + // local_R = parent_R^T * world_R (R^-1 == R^T for orthogonal matrices). + // Compute only the 7 entries of the local rotation matrix needed for XYZ + // euler extraction (stored column-major: [col][row] = math [row][col]). + // sinBeta = stored[2][0] = math[0][2] + // r21/r22 = stored[2][1..2] = math[1..2][2] + // r10/r00 = stored[1][0], stored[0][0] = math[0][1], math[0][0] + // gimbal = stored[0][1], stored[1][1] = math[1][0], math[1][1] + const float_t lr00 = pr00*wr00 + pr01*wr10 + pr02*wr20; // math[0][0] + const float_t lr10 = pr00*wr01 + pr01*wr11 + pr02*wr21; // math[0][1] + const float_t lr20 = pr00*wr02 + pr01*wr12 + pr02*wr22; // [0][2] -> sinBeta + const float_t lr01 = pr10*wr00 + pr11*wr10 + pr12*wr20; // math[1][0] + const float_t lr11 = pr10*wr01 + pr11*wr11 + pr12*wr21; // math[1][1] + const float_t lr21 = pr10*wr02 + pr11*wr12 + pr12*wr22; // [1][2] -> r21 + const float_t lr22 = pr20*wr02 + pr21*wr12 + pr22*wr22; // [2][2] -> r22 + + const float_t sinBeta = glm_clamp(lr20, -1.0f, 1.0f); + pos->rotation[1] = asinf(sinBeta); + const float_t cosBeta = cosf(pos->rotation[1]); + if(fabsf(cosBeta) > 1e-6f) { + pos->rotation[0] = atan2f(-lr21, lr22); + pos->rotation[2] = atan2f(-lr10, lr00); + } else { + pos->rotation[2] = 0.0f; + pos->rotation[0] = (sinBeta > 0.0f) + ? atan2f(lr01, lr11) : -atan2f(lr01, lr11); + } + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_ROTATION_DIRTY) + & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); +} + +void entityPositionGetLocalScale( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + entityPositionEnsurePRS(pos); + glm_vec3_copy(pos->scale, dest); +} + +void entityPositionGetWorldScale( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + if(pos->parentEntityId == ENTITY_ID_INVALID) { + entityPositionEnsurePRS(pos); + glm_vec3_copy(pos->scale, dest); + return; + } + entityPositionEnsureWorld(mgr, pos); + const float_t (*wt)[4] = pos->worldTransform; + dest[0] = sqrtf(wt[0][0]*wt[0][0] + wt[0][1]*wt[0][1] + wt[0][2]*wt[0][2]); + dest[1] = sqrtf(wt[1][0]*wt[1][0] + wt[1][1]*wt[1][1] + wt[1][2]*wt[1][2]); + dest[2] = sqrtf(wt[2][0]*wt[2][0] + wt[2][1]*wt[2][1] + wt[2][2]*wt[2][2]); +} + +void entityPositionSetLocalScale( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 scale +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + glm_vec3_copy(scale, pos->scale); + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_ROTATION_DIRTY) + & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); +} + +void entityPositionSetWorldScale( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 scale +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + if(pos->parentEntityId == ENTITY_ID_INVALID) { + glm_vec3_copy(scale, pos->scale); + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_ROTATION_DIRTY) + & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); + return; + } + entityposition_t *parent = componentGetData( + mgr, pos->parentEntityId, pos->parentComponentId, COMPONENT_TYPE_POSITION + ); + entityPositionEnsureWorld(mgr, parent); + const float_t (*pt)[4] = parent->worldTransform; + const float_t psx = sqrtf( + pt[0][0]*pt[0][0] + pt[0][1]*pt[0][1] + pt[0][2]*pt[0][2] + ); + const float_t psy = sqrtf( + pt[1][0]*pt[1][0] + pt[1][1]*pt[1][1] + pt[1][2]*pt[1][2] + ); + const float_t psz = sqrtf( + pt[2][0]*pt[2][0] + pt[2][1]*pt[2][1] + pt[2][2]*pt[2][2] + ); + pos->scale[0] = psx > 0.0f ? scale[0] / psx : scale[0]; + pos->scale[1] = psy > 0.0f ? scale[1] / psy : scale[1]; + pos->scale[2] = psz > 0.0f ? scale[2] / psz : scale[2]; + pos->flags = (pos->flags | ENTITY_POSITION_FLAG_ROTATION_DIRTY) + & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); +} + +void entityPositionSetParent( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const entityid_t parentEntityId, + const componentid_t parentComponentId +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + + // Remove from old parent's child list. + if(pos->parentEntityId != ENTITY_ID_INVALID) { + entityposition_t *oldParent = componentGetData( + mgr, pos->parentEntityId, pos->parentComponentId, + COMPONENT_TYPE_POSITION + ); + for(uint8_t i = 0; i < oldParent->childCount; i++) { + if( + oldParent->childEntityIds[i] == entityId && + oldParent->childComponentIds[i] == componentId + ) { + oldParent->childCount--; + for(uint8_t j = i; j < oldParent->childCount; j++) { + oldParent->childEntityIds[j] = oldParent->childEntityIds[j + 1]; + oldParent->childComponentIds[j] = oldParent->childComponentIds[j + 1]; + } + break; + } + } + } + + pos->parentEntityId = parentEntityId; + pos->parentComponentId = parentComponentId; + + // Register with new parent. + if(parentEntityId != ENTITY_ID_INVALID) { + entityposition_t *parent = componentGetData( + mgr, parentEntityId, parentComponentId, COMPONENT_TYPE_POSITION + ); + if(parent->childCount < ENTITY_POSITION_CHILDREN_MAX) { + parent->childEntityIds[parent->childCount] = entityId; + parent->childComponentIds[parent->childCount] = componentId; + parent->childCount++; + } + } + + entityPositionMarkDirty(mgr, pos); +} + +entityid_t entityPositionGetParent( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityposition_t *pos = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); + return pos->parentEntityId; +} + +entityposition_t *entityPositionGet( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + return componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_POSITION + ); +} + +void entityPositionRebuild(entitymanager_t *mgr, entityposition_t *pos) { + pos->flags = ( + pos->flags | + ENTITY_POSITION_FLAG_ROTATION_DIRTY | + ENTITY_POSITION_FLAG_POSITION_DIRTY + ) & ~ENTITY_POSITION_FLAG_PRS_DIRTY; + entityPositionMarkDirty(mgr, pos); +} + +void entityPositionMarkDirty(entitymanager_t *mgr, entityposition_t *pos) { + if(pos->flags & ENTITY_POSITION_FLAG_WORLD_DIRTY) return; + pos->flags |= ENTITY_POSITION_FLAG_WORLD_DIRTY; + for(uint8_t i = 0; i < pos->childCount; i++) { + entityposition_t *child = componentGetData( + mgr, pos->childEntityIds[i], pos->childComponentIds[i], + COMPONENT_TYPE_POSITION + ); + entityPositionMarkDirty(mgr, child); + } +} + +void entityPositionDisposeDeep( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityposition_t *pos = entityPositionGet(mgr, entityId, componentId); + + // Detach from parent so the parent's child list stays consistent. + if(pos->parentEntityId != ENTITY_ID_INVALID) { + entityPositionSetParent( + mgr, entityId, componentId, ENTITY_ID_INVALID, COMPONENT_ID_INVALID + ); + } + + // Copy the child list before disposing self (entityDispose invalidates + // pos). + uint8_t childCount = pos->childCount; + entityid_t childEntityIds[ENTITY_POSITION_CHILDREN_MAX]; + componentid_t childComponentIds[ENTITY_POSITION_CHILDREN_MAX]; + for(uint8_t i = 0; i < childCount; i++) { + childEntityIds[i] = pos->childEntityIds[i]; + childComponentIds[i] = pos->childComponentIds[i]; + // Sever child's parent link so it won't try to modify our disposed + // data. + entityposition_t *child = entityPositionGet( + mgr, childEntityIds[i], childComponentIds[i] + ); + child->parentEntityId = ENTITY_ID_INVALID; + child->parentComponentId = COMPONENT_ID_INVALID; + } + + entityDispose(mgr, entityId); + + for(uint8_t i = 0; i < childCount; i++) { + entityPositionDisposeDeep(mgr, childEntityIds[i], childComponentIds[i]); + } +} + +void entityPositionDecompose(entityposition_t *pos) { + // Translation: column 3 + pos->position[0] = pos->localTransform[3][0]; + pos->position[1] = pos->localTransform[3][1]; + pos->position[2] = pos->localTransform[3][2]; + + // Scale: length of each basis column (xyz only) + pos->scale[0] = sqrtf( + pos->localTransform[0][0] * pos->localTransform[0][0] + + pos->localTransform[0][1] * pos->localTransform[0][1] + + pos->localTransform[0][2] * pos->localTransform[0][2] + ); + pos->scale[1] = sqrtf( + pos->localTransform[1][0] * pos->localTransform[1][0] + + pos->localTransform[1][1] * pos->localTransform[1][1] + + pos->localTransform[1][2] * pos->localTransform[1][2] + ); + pos->scale[2] = sqrtf( + pos->localTransform[2][0] * pos->localTransform[2][0] + + pos->localTransform[2][1] * pos->localTransform[2][1] + + pos->localTransform[2][2] * pos->localTransform[2][2] + ); + + // Normalize columns to isolate the rotation matrix (no mat4 needed). + const float_t invS0 = pos->scale[0] > 0.0f ? 1.0f / pos->scale[0] : 0.0f; + const float_t invS1 = pos->scale[1] > 0.0f ? 1.0f / pos->scale[1] : 0.0f; + const float_t invS2 = pos->scale[2] > 0.0f ? 1.0f / pos->scale[2] : 0.0f; + + const float_t r00 = pos->localTransform[0][0] * invS0; + const float_t r01 = pos->localTransform[0][1] * invS0; + const float_t r02 = pos->localTransform[0][2] * invS0; + const float_t r10 = pos->localTransform[1][0] * invS1; + const float_t r11 = pos->localTransform[1][1] * invS1; + const float_t r20 = pos->localTransform[2][0] * invS2; + const float_t r21 = pos->localTransform[2][1] * invS2; + const float_t r22 = pos->localTransform[2][2] * invS2; + + // Extract XYZ euler angles (R = Rx * Ry * Rz, column-major) + const float_t sinBeta = glm_clamp(r20, -1.0f, 1.0f); + pos->rotation[1] = asinf(sinBeta); + const float_t cosBeta = cosf(pos->rotation[1]); + + if(fabsf(cosBeta) > 1e-6f) { + pos->rotation[0] = atan2f(-r21, r22); + pos->rotation[2] = atan2f(-r10, r00); + } else { + // Gimbal lock: pin Z to 0, recover X. + pos->rotation[2] = 0.0f; + pos->rotation[0] = (sinBeta > 0.0f) + ? atan2f(r01, r11) + : -atan2f(r01, r11); + } +} + +void entityPositionEnsurePRS(entityposition_t *pos) { + if(!(pos->flags & ENTITY_POSITION_FLAG_PRS_DIRTY)) return; + entityPositionDecompose(pos); + pos->flags &= ~ENTITY_POSITION_FLAG_PRS_DIRTY; +} + +void entityPositionEnsureLocal(entityposition_t *pos) { + const uint8_t dirty = pos->flags & ( + ENTITY_POSITION_FLAG_ROTATION_DIRTY | ENTITY_POSITION_FLAG_POSITION_DIRTY + ); + if(!dirty) return; + + if(dirty & ENTITY_POSITION_FLAG_ROTATION_DIRTY) { + // Rotation or scale changed: rebuild cols 0-2 analytically (XYZ euler). + const float_t c0 = cosf(pos->rotation[0]), s0 = sinf(pos->rotation[0]); + const float_t c1 = cosf(pos->rotation[1]), s1 = sinf(pos->rotation[1]); + const float_t c2 = cosf(pos->rotation[2]), s2 = sinf(pos->rotation[2]); + const float_t s0s1 = s0 * s1; + const float_t c0s1 = c0 * s1; + + pos->localTransform[0][0] = c1 * c2 * pos->scale[0]; + pos->localTransform[0][1] = (c0 * s2 + s0s1 * c2) * pos->scale[0]; + pos->localTransform[0][2] = (s0 * s2 - c0s1 * c2) * pos->scale[0]; + pos->localTransform[0][3] = 0.0f; + + pos->localTransform[1][0] = -c1 * s2 * pos->scale[1]; + pos->localTransform[1][1] = (c0 * c2 - s0s1 * s2) * pos->scale[1]; + pos->localTransform[1][2] = (s0 * c2 + c0s1 * s2) * pos->scale[1]; + pos->localTransform[1][3] = 0.0f; + + pos->localTransform[2][0] = s1 * pos->scale[2]; + pos->localTransform[2][1] = -s0 * c1 * pos->scale[2]; + pos->localTransform[2][2] = c0 * c1 * pos->scale[2]; + pos->localTransform[2][3] = 0.0f; + } + + if(dirty & ENTITY_POSITION_FLAG_POSITION_DIRTY) { + // Only position changed: update column 3 only (no trig needed). + pos->localTransform[3][0] = pos->position[0]; + pos->localTransform[3][1] = pos->position[1]; + pos->localTransform[3][2] = pos->position[2]; + pos->localTransform[3][3] = 1.0f; + } + + pos->flags &= ~( + ENTITY_POSITION_FLAG_ROTATION_DIRTY | ENTITY_POSITION_FLAG_POSITION_DIRTY + ); +} + +void entityPositionEnsureWorld(entitymanager_t *mgr, entityposition_t *pos) { + if(!(pos->flags & ENTITY_POSITION_FLAG_WORLD_DIRTY)) return; + entityPositionEnsureLocal(pos); + + if(pos->parentEntityId != ENTITY_ID_INVALID) { + // Parented: world = parent.world x local. worldTransform must be + // written because children (and this node's getters) read it. + entityposition_t *parent = componentGetData( + mgr, pos->parentEntityId, pos->parentComponentId, + COMPONENT_TYPE_POSITION + ); + entityPositionEnsureWorld(mgr, parent); + glm_mat4_mul( + parent->worldTransform, pos->localTransform, pos->worldTransform + ); + } else if(pos->childCount > 0) { + // Parentless root with children: children need a valid worldTransform + // to multiply against, but world == local, so just copy. + glm_mat4_copy(pos->localTransform, pos->worldTransform); + } + // Parentless leaf: world == local. Getters read localTransform directly; + // no copy needed. + + pos->flags &= ~ENTITY_POSITION_FLAG_WORLD_DIRTY; +} diff --git a/src/dusk/entity/component/display/entityposition.h b/src/dusk/entity/component/display/entityposition.h new file mode 100644 index 00000000..8cf763c1 --- /dev/null +++ b/src/dusk/entity/component/display/entityposition.h @@ -0,0 +1,460 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" +#include "error/error.h" + +/** Maximum number of child position components this node can track. */ +#define ENTITY_POSITION_CHILDREN_MAX 8 + +/** + * PRS cache is stale. localTransform was written directly (e.g. lookAt) and + * position/rotation/scale need to be decomposed before they can be read. + */ +#define ENTITY_POSITION_FLAG_PRS_DIRTY (1 << 0) + +/** + * Columns 0-2 of localTransform are stale. Rotation or scale changed; the + * basis vectors need to be rebuilt analytically before the matrix can be used. + * Does not imply column 3 (translation) is stale. + */ +#define ENTITY_POSITION_FLAG_ROTATION_DIRTY (1 << 1) + +/** + * Column 3 of localTransform is stale. Position changed; only the + * translation column needs to be written. Does not imply columns 0-2 are + * stale. + */ +#define ENTITY_POSITION_FLAG_POSITION_DIRTY (1 << 2) + +/** + * worldTransform is stale. Either the local matrix changed or an ancestor + * moved; worldTransform must be recomputed before world data can be read. + */ +#define ENTITY_POSITION_FLAG_WORLD_DIRTY (1 << 3) + +typedef struct { + /* + * Hot fields - flag checks, parent/child traversal (markDirty, ensureWorld) + * only touch these. Kept at the front so they share the first cache line. + */ + + /** ENTITY_POSITION_FLAG_* bitmask; describes which caches are stale. */ + uint8_t flags; + /** Entity ID of the parent node, or ENTITY_ID_INVALID if none. */ + entityid_t parentEntityId; + /** Component ID of the parent position, or COMPONENT_ID_INVALID if none. */ + componentid_t parentComponentId; + /** Number of currently registered children. */ + uint8_t childCount; + /** Entity IDs of child nodes. */ + entityid_t childEntityIds[ENTITY_POSITION_CHILDREN_MAX]; + /** Component IDs of child position components. */ + componentid_t childComponentIds[ENTITY_POSITION_CHILDREN_MAX]; + + /* + * Warm fields - read/written by PRS getters/setters. + * Accessed more often than the matrices but less often than flags. + */ + + /** Cached local position (XYZ). Stale when PRS_DIRTY is set. */ + vec3 position; + /** Cached local rotation (XYZ euler, radians). Stale when PRS_DIRTY. */ + vec3 rotation; + /** Cached local scale (XYZ). Stale when PRS_DIRTY is set. */ + vec3 scale; + + /* + * Cold fields - only touched when actually rebuilding transforms. + */ + + /** Local transform matrix, rebuilt lazily from position/rotation/scale. */ + mat4 localTransform; + /** World transform matrix, recomputed lazily from the parent chain. */ + mat4 worldTransform; +} entityposition_t; + +/** + * Initializes the entity position component, setting identity transforms and + * zeroing all parent/child state. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void entityPositionInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Positions and orients the entity at eye, facing target. Stores this as + * the entity's normal world-space placement (consistent with every other + * setter), not as a view matrix -- invert entityPositionGetTransform()'s + * result to get a view matrix for rendering. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param eye The eye/camera position. + * @param target The target point to look at. + * @param up The up vector. + */ +void entityPositionLookAt( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 eye, + vec3 target, + vec3 up +); + +/** + * Gets the world-space transform matrix, recomputing it lazily if dirty. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param dest Destination matrix. + */ +void entityPositionGetTransform( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + mat4 dest +); + +/** + * Gets the local transform matrix (does not include parent transforms). + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param dest Destination matrix. + */ +void entityPositionGetLocalTransform( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + mat4 dest +); + +/** + * Gets the cached local position (XYZ). Decomposes localTransform into PRS + * first if ENTITY_POSITION_FLAG_PRS_DIRTY is set; never triggers a matrix + * rebuild or world-transform update. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param dest Destination vector. + */ +void entityPositionGetLocalPosition( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +); + +/** + * Gets the world-space position. For parentless entities this is the same as + * the local position. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param dest Destination vector. + */ +void entityPositionGetWorldPosition( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +); + +/** + * Sets the world-space position. For parentless entities this is equivalent + * to entityPositionSetLocalPosition. For parented entities the position is + * converted to local space via the inverted parent world transform. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param position The desired world-space position. + */ +void entityPositionSetWorldPosition( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 position +); + +/** + * Sets the local position, marks localTransform and worldTransform (self + + * descendants) dirty. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param position The new local position. + */ +void entityPositionSetLocalPosition( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 position +); + +/** + * Gets the cached local euler rotation (XYZ, radians). Decomposes + * localTransform first if ENTITY_POSITION_FLAG_PRS_DIRTY is set. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param dest Destination vector. + */ +void entityPositionGetLocalRotation( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +); + +/** + * Gets the world-space euler rotation (XYZ, radians) by decomposing the + * world transform. For parentless entities this is the same as local + * rotation. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param dest Destination vector. + */ +void entityPositionGetWorldRotation( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +); + +/** + * Sets the local euler rotation (XYZ, radians) and marks transforms dirty. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param rotation The new local rotation. + */ +void entityPositionSetLocalRotation( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 rotation +); + +/** + * Sets the world-space euler rotation (XYZ, radians). For parentless + * entities this is equivalent to entityPositionSetLocalRotation. For + * parented entities the rotation is converted to local space by removing + * the parent world rotation. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param rotation The desired world-space euler rotation. + */ +void entityPositionSetWorldRotation( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 rotation +); + +/** + * Gets the cached local scale. Decomposes localTransform first if + * ENTITY_POSITION_FLAG_PRS_DIRTY is set. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param dest Destination vector. + */ +void entityPositionGetLocalScale( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +); + +/** + * Gets the world-space scale by extracting column lengths from the world + * transform. For parentless entities this is the same as local scale. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param dest Destination vector. + */ +void entityPositionGetWorldScale( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +); + +/** + * Sets the local scale and marks transforms dirty. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param scale The new local scale. + */ +void entityPositionSetLocalScale( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 scale +); + +/** + * Sets the world-space scale. For parentless entities this is equivalent to + * entityPositionSetLocalScale. For parented entities the scale is converted + * to local space by dividing by the parent world scale (assumes no shear). + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param scale The desired world-space scale. + */ +void entityPositionSetWorldScale( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 scale +); + +/** + * Sets the parent of this entity's position component. + * Pass ENTITY_ID_INVALID / COMPONENT_ID_INVALID to detach from any parent. + * + * @param mgr The entity manager that owns both entities. + * @param entityId The child entity ID. + * @param componentId The child component ID. + * @param parentEntityId The parent entity ID. + * @param parentComponentId The parent component ID. + */ +void entityPositionSetParent( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const entityid_t parentEntityId, + const componentid_t parentComponentId +); + +/** + * Gets the entity ID of this position component's parent. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The parent entity ID, or ENTITY_ID_INVALID if unparented. + */ +entityid_t entityPositionGetParent( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Returns a direct pointer to the entity position component data. + * After modifying localTransform directly, call entityPositionMarkDirty() to + * set ENTITY_POSITION_FLAG_WORLD_DIRTY on self and descendants. After + * modifying PRS directly, call entityPositionRebuild() instead. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return Pointer to the component data. + */ +entityposition_t *entityPositionGet( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Signals that the PRS cache was modified externally. Sets both + * ENTITY_POSITION_FLAG_ROTATION_DIRTY and ENTITY_POSITION_FLAG_POSITION_DIRTY + * so all of localTransform is rebuilt lazily on the next read, clears + * ENTITY_POSITION_FLAG_PRS_DIRTY, propagates ENTITY_POSITION_FLAG_WORLD_DIRTY + * to self and all descendants. + * + * @param mgr The entity manager that owns pos and its descendants. + * @param pos The position component whose PRS was modified. + */ +void entityPositionRebuild(entitymanager_t *mgr, entityposition_t *pos); + +/** + * Sets ENTITY_POSITION_FLAG_WORLD_DIRTY on this node and all descendants, + * indicating that worldTransform must be recomputed before it is read. + * Call this after modifying localTransform directly. + * + * @param mgr The entity manager that owns pos and its descendants. + * @param pos The position component to mark dirty. + */ +void entityPositionMarkDirty(entitymanager_t *mgr, entityposition_t *pos); + +/** + * Disposes this entity and all of its position-component descendants + * recursively. Detaches from any parent before destroying. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The root entity ID. + * @param componentId The root position component ID. + */ +void entityPositionDisposeDeep( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Decomposes the local transform matrix back into the position, rotation + * (XYZ euler, radians), and scale cache fields. + * + * @param pos The position component to decompose. + */ +void entityPositionDecompose(entityposition_t *pos); + +/** + * Internal. Decomposes localTransform into the PRS cache if + * ENTITY_POSITION_FLAG_PRS_DIRTY is set. + * + * @param pos The position component to update. + */ +void entityPositionEnsurePRS(entityposition_t *pos); + +/** + * Internal. Rebuilds localTransform from the PRS cache, touching only the + * columns flagged as stale (ROTATION_DIRTY and/or POSITION_DIRTY). + * + * @param pos The position component to update. + */ +void entityPositionEnsureLocal(entityposition_t *pos); + +/** + * Internal. Recomputes worldTransform from the parent chain if + * ENTITY_POSITION_FLAG_WORLD_DIRTY is set. + * + * @param mgr The entity manager that owns pos and its ancestors. + * @param pos The position component to update. + */ +void entityPositionEnsureWorld(entitymanager_t *mgr, entityposition_t *pos); diff --git a/src/dusk/entity/component/display/entityrenderable.c b/src/dusk/entity/component/display/entityrenderable.c new file mode 100644 index 00000000..0f510d2f --- /dev/null +++ b/src/dusk/entity/component/display/entityrenderable.c @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entityrenderable.h" +#include "entity/entitymanager.h" +#include "display/shader/shadermaterial.h" +#include "display/shader/shaderunlit.h" +#include "display/display.h" +#include "display/mesh/cube.h" +#include "util/memory.h" +#include "assert/assert.h" + +void entityRenderableInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityrenderable_t *r = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE + ); + memoryZero(r, sizeof(entityrenderable_t)); + r->type = ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL; + r->data.material.shaderType = SHADER_LIST_SHADER_UNLIT; + r->data.material.material.unlit.color = COLOR_WHITE; + r->data.material.meshes[0] = &CUBE_MESH_SIMPLE; + r->data.material.meshOffsets[0] = 0; + r->data.material.meshCounts[0] = -1; + r->data.material.meshCount = 1; + r->data.material.state.flags = DISPLAY_STATE_FLAG_DEPTH_TEST; +} + +void entityRenderableDispose( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { +} + +void entityRenderableSetType( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const entityrenderabletype_t type +) { + entityrenderable_t *r = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE + ); + r->type = type; +} + +void entityRenderableSetPriority( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const int8_t priority +) { + entityrenderable_t *r = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE + ); + r->priority = priority; +} + +void entityRenderableSetColor( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const color_t color +) { + entityrenderable_t *r = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE + ); + assertTrue( + r->type == ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL, + "Renderable must be ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL to set color" + ); + r->data.material.material.unlit.color = color; +} + +void entityRenderableSetMesh( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint8_t slot, + mesh_t *mesh +) { + assertNotNull(mesh, "Mesh cannot be null"); + assertTrue(slot < ENTITY_RENDERABLE_MESHES_MAX, "Mesh slot out of bounds"); + + entityrenderable_t *r = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE + ); + assertTrue( + r->type == ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL, + "Renderable must be ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL to set a mesh" + ); + + r->data.material.meshes[slot] = mesh; + r->data.material.meshOffsets[slot] = 0; + r->data.material.meshCounts[slot] = -1; + if(slot >= r->data.material.meshCount) { + r->data.material.meshCount = slot + 1; + } +} + +void entityRenderableSetDraw( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + errorret_t (*draw)( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + void *user + ), + void *user +) { + assertNotNull(draw, "Draw callback cannot be null"); + entityrenderable_t *r = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE + ); + r->type = ENTITY_RENDERABLE_TYPE_CUSTOM; + r->data.custom.draw = draw; + r->data.custom.drawUser = user; +} + +errorret_t entityRenderableDraw( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityrenderable_t *r = componentGetData( + mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE + ); + switch(r->type) { + case ENTITY_RENDERABLE_TYPE_SPRITEBATCH: + return entityRenderableDrawSpritebatch(&r->data.spritebatch); + case ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL: + return entityRenderableDrawMaterial(&r->data.material); + case ENTITY_RENDERABLE_TYPE_CUSTOM: + return entityRenderableDrawCustom( + mgr, entityId, componentId, &r->data.custom + ); + default: + assertUnreachable("Invalid renderable type"); + } +} + +errorret_t entityRenderableDrawSpritebatch( + const entityrenderablespritebatch_t *sb +) { + if(sb->spriteCount == 0) errorOk(); + + errorChain(displaySetState((displaystate_t){ + .flags = DISPLAY_STATE_FLAG_BLEND + })); + + spriteBatchClear(); + shadermaterial_t mat; + memoryZero(&mat, sizeof(shadermaterial_t)); + mat.unlit.texture = sb->texture; + mat.unlit.color = COLOR_WHITE; + errorChain(spriteBatchBuffer( + sb->sprites, sb->spriteCount, + SHADER_LIST_DEFS[SHADER_LIST_SHADER_UNLIT].shader, mat + )); + return spriteBatchFlush(); +} + +errorret_t entityRenderableDrawMaterial(const entityrenderablematerial_t *m) { + errorChain(displaySetState(m->state)); + shader_t *shader = SHADER_LIST_DEFS[m->shaderType].shader; + assertNotNull(shader, "Shader cannot be null for material type"); + errorChain(shaderBind(shader)); + errorChain(shaderSetMaterial(shader, &m->material)); + for(uint8_t i = 0; i < m->meshCount; i++) { + errorChain(meshDraw(m->meshes[i], m->meshOffsets[i], m->meshCounts[i])); + } + errorOk(); +} + +errorret_t entityRenderableDrawCustom( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const entityrenderablecustom_t *custom +) { + return custom->draw(mgr, entityId, componentId, custom->drawUser); +} diff --git a/src/dusk/entity/component/display/entityrenderable.h b/src/dusk/entity/component/display/entityrenderable.h new file mode 100644 index 00000000..a4acee32 --- /dev/null +++ b/src/dusk/entity/component/display/entityrenderable.h @@ -0,0 +1,234 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" +#include "display/mesh/mesh.h" +#include "display/shader/shadermaterial.h" +#include "display/spritebatch/spritebatch.h" +#include "display/displaystate.h" +#include "error/error.h" + +#define ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX 64 +#define ENTITY_RENDERABLE_MESHES_MAX 8 + +typedef enum { + ENTITY_RENDERABLE_TYPE_CUSTOM = 0, + ENTITY_RENDERABLE_TYPE_SPRITEBATCH, + ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL, +} entityrenderabletype_t; + +typedef struct { + spritebatchsprite_t sprites[ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX]; + uint32_t spriteCount; + texture_t *texture; +} entityrenderablespritebatch_t; + +typedef struct { + mesh_t *meshes[ENTITY_RENDERABLE_MESHES_MAX]; + int32_t meshOffsets[ENTITY_RENDERABLE_MESHES_MAX]; + int32_t meshCounts[ENTITY_RENDERABLE_MESHES_MAX]; + uint8_t meshCount; + shaderlistshader_t shaderType; + shadermaterial_t material; + displaystate_t state; +} entityrenderablematerial_t; + +typedef struct { + errorret_t (*draw)( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + void *user + ); + void *drawUser; +} entityrenderablecustom_t; + +typedef union entityrenderabledata_u { + entityrenderablespritebatch_t spritebatch; + entityrenderablematerial_t material; + entityrenderablecustom_t custom; +} entityrenderabledata_t; + +typedef struct { + entityrenderabletype_t type; + entityrenderabledata_t data; + + /** + * Render priority. 0 = auto (derived from type/flags). Higher values + * render later (on top of lower values). Range: [-128..127] with 0 auto. + */ + int8_t priority; +} entityrenderable_t; + +/** + * Initializes the entity renderable component. Defaults to + * ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL with the unlit shader, a white + * cube, and depth-test enabled. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to initialize the component for. + * @param componentId The renderable component of the entity. + */ +void entityRenderableInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Disposes the entity renderable component. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to dispose the component for. + * @param componentId The renderable component of the entity. + */ +void entityRenderableDispose( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets the rendering type for the renderable component. Resets + * type-specific data to zero. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to configure. + * @param componentId The renderable component. + * @param type The rendering type to use. + */ +void entityRenderableSetType( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const entityrenderabletype_t type +); + +/** + * Sets the render priority. 0 = auto (derived from type/flags). Higher + * values render later (on top). Use non-zero to force ordering. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to configure. + * @param componentId The renderable component. + * @param priority The priority value, or 0 for auto. + */ +void entityRenderableSetPriority( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const int8_t priority +); + +/** + * Sets the unlit material color. Only meaningful when the renderable is + * (or defaults to) ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL -- asserts + * otherwise. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to configure. + * @param componentId The renderable component. + * @param color The color to tint the material with. + */ +void entityRenderableSetColor( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const color_t color +); + +/** + * Sets one of the material renderable's meshes, drawn in full (no offset/ + * count override). Only meaningful when the renderable is (or defaults + * to) ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL -- asserts otherwise. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to configure. + * @param componentId The renderable component. + * @param slot Index into the material's meshes array (0 to + * ENTITY_RENDERABLE_MESHES_MAX - 1). + * @param mesh The mesh to draw in that slot. + */ +void entityRenderableSetMesh( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint8_t slot, + mesh_t *mesh +); + +/** + * Sets the draw callback, switching the type to + * ENTITY_RENDERABLE_TYPE_CUSTOM. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to configure. + * @param componentId The renderable component of the entity. + * @param draw The draw callback to assign. + * @param user Userdata passed to the callback. + */ +void entityRenderableSetDraw( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + errorret_t (*draw)( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + void *user + ), + void *user +); + +/** + * Draws the entity using its renderable component data. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to draw. + * @param componentId The renderable component of the entity. + * @return Any error state that happened. + */ +errorret_t entityRenderableDraw( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Internal. Draws a spritebatch-type renderable. + * + * @param sb The spritebatch data to draw. + * @return Error state. + */ +errorret_t entityRenderableDrawSpritebatch( + const entityrenderablespritebatch_t *sb +); + +/** + * Internal. Draws a shader-material-type renderable. + * + * @param m The material data to draw. + * @return Error state. + */ +errorret_t entityRenderableDrawMaterial(const entityrenderablematerial_t *m); + +/** + * Internal. Invokes a custom-type renderable's draw callback. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity being drawn. + * @param componentId The renderable component of the entity. + * @param custom The custom draw data. + * @return Error state. + */ +errorret_t entityRenderableDrawCustom( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const entityrenderablecustom_t *custom +); diff --git a/src/dusk/rpg/entity/item/CMakeLists.txt b/src/dusk/entity/component/physics/CMakeLists.txt similarity index 90% rename from src/dusk/rpg/entity/item/CMakeLists.txt rename to src/dusk/entity/component/physics/CMakeLists.txt index 6834d19b..88588512 100644 --- a/src/dusk/rpg/entity/item/CMakeLists.txt +++ b/src/dusk/entity/component/physics/CMakeLists.txt @@ -6,5 +6,5 @@ # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC - entityitem.c + entityphysics.c ) diff --git a/src/dusk/entity/component/physics/entityphysics.c b/src/dusk/entity/component/physics/entityphysics.c new file mode 100644 index 00000000..e5909d01 --- /dev/null +++ b/src/dusk/entity/component/physics/entityphysics.c @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entityphysics.h" +#include "entity/entitymanager.h" +#include "util/memory.h" + +void entityPhysicsInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + + memoryZero(phys, sizeof(entityphysics_t)); + + // Default to cube + phys->type = PHYSICS_BODY_DYNAMIC; + phys->shape.type = PHYSICS_SHAPE_CUBE; + phys->shape.data.cube.halfExtents[0] = 0.5f; + phys->shape.data.cube.halfExtents[1] = 0.5f; + phys->shape.data.cube.halfExtents[2] = 0.5f; + phys->gravityScale = 1.0f; + phys->onGround = false; + phys->collideMask = 0x1; +} + +entityphysics_t *entityPhysicsGet( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + return componentGetData(mgr, entityId, componentId, COMPONENT_TYPE_PHYSICS); +} + +void entityPhysicsSetShape( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const physicsshape_t shape +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + phys->shape = shape; +} + +physicsshape_t entityPhysicsGetShape( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + return phys->shape; +} + +void entityPhysicsGetVelocity( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + glm_vec3_copy(phys->velocity, dest); +} + +void entityPhysicsSetVelocity( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 velocity +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + glm_vec3_copy(velocity, phys->velocity); +} + +void entityPhysicsApplyImpulse( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 impulse +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + if(phys->type == PHYSICS_BODY_STATIC) return; + glm_vec3_add(phys->velocity, impulse, phys->velocity); +} + +bool_t entityPhysicsIsOnGround( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + return phys->onGround; +} + +void entityPhysicsSetBodyType( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const physicsbodytype_t type +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + phys->type = type; +} + +physicsbodytype_t entityPhysicsGetBodyType( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + return phys->type; +} + +void entityPhysicsSetCollideMask( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint32_t mask +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + phys->collideMask = mask; +} + +uint32_t entityPhysicsGetCollideMask( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entityphysics_t *phys = entityPhysicsGet(mgr, entityId, componentId); + return phys->collideMask; +} diff --git a/src/dusk/entity/component/physics/entityphysics.h b/src/dusk/entity/component/physics/entityphysics.h new file mode 100644 index 00000000..7bd28c22 --- /dev/null +++ b/src/dusk/entity/component/physics/entityphysics.h @@ -0,0 +1,211 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" +#include "physics/physicsshape.h" +#include "physics/physicsbodytype.h" +#include "error/error.h" + +typedef struct { + physicsbodytype_t type; + physicsshape_t shape; + vec3 velocity; + float_t gravityScale; + bool_t onGround; + + /** + * Bitmask of collision layers/categories this body belongs to. Two + * bodies only collide with each other if (a.collideMask & b.collideMask) + * is non-zero -- i.e. they share at least one set bit. Defaults to 0x1 + * (only bit 0 set). + */ + uint32_t collideMask; +} entityphysics_t; + +/** + * Initializes the physics component: defaults to a dynamic 1x1x1 cube + * body, zero velocity, unit gravity scale, and onGround false. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void entityPhysicsInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Gets the underlying physics structure (temporarily) for the given entity. + * This is really just intended for doing operations faster than using the + * getters and setters, but it is preferred that you use those. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The physics component data for the given entity and component ID. + */ +entityphysics_t *entityPhysicsGet( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets the shape of the entity's physics body. This will not reset the body + * state, so if you change from a cube to a sphere, it will keep the same + * velocity and onGround state. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param shape The new shape to set on the physics body. + */ +void entityPhysicsSetShape( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const physicsshape_t shape +); + +/** + * Gets the shape of the entity's physics body. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The shape of the physics body. + */ +physicsshape_t entityPhysicsGetShape( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Gets the velocity of the entity's physics body. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param dest The destination vec3 to write the velocity to. + */ +void entityPhysicsGetVelocity( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 dest +); + +/** + * Sets the velocity of the entity's physics body. This is not an impulse, so + * it will be affected by mass and drag. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param velocity The new velocity to set on the physics body. + */ +void entityPhysicsSetVelocity( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 velocity +); + +/** + * Applies an impulse to the entity's physics body. This is an immediate + * velocity change that is not affected by mass or drag. No-op on STATIC + * bodies. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param impulse The impulse to apply to the physics body. + */ +void entityPhysicsApplyImpulse( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + vec3 impulse +); + +/** + * Returns true if the entity's physics body rested on a surface during the + * last physicsWorldStep. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return True if the body is on the ground, false otherwise. + */ +bool_t entityPhysicsIsOnGround( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets the body type of the entity's physics body. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param type The body type to set. + */ +void entityPhysicsSetBodyType( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const physicsbodytype_t type +); + +/** + * Gets the body type of the entity's physics body. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The body type of the physics body. + */ +physicsbodytype_t entityPhysicsGetBodyType( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets the collision mask of the entity's physics body. Two bodies only + * collide if their masks share at least one set bit. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param mask The new collision mask to set on the physics body. + */ +void entityPhysicsSetCollideMask( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint32_t mask +); + +/** + * Gets the collision mask of the entity's physics body. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The collision mask of the physics body. + */ +uint32_t entityPhysicsGetCollideMask( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); diff --git a/src/dusk/rpg/battle/move/CMakeLists.txt b/src/dusk/entity/component/trigger/CMakeLists.txt similarity index 80% rename from src/dusk/rpg/battle/move/CMakeLists.txt rename to src/dusk/entity/component/trigger/CMakeLists.txt index cdbe40d2..83974422 100644 --- a/src/dusk/rpg/battle/move/CMakeLists.txt +++ b/src/dusk/entity/component/trigger/CMakeLists.txt @@ -6,5 +6,6 @@ # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC - battlemove.c + entitytrigger.c + entitytriggerevents.c ) diff --git a/src/dusk/entity/component/trigger/entitytrigger.c b/src/dusk/entity/component/trigger/entitytrigger.c new file mode 100644 index 00000000..40edf1b9 --- /dev/null +++ b/src/dusk/entity/component/trigger/entitytrigger.c @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entitytrigger.h" +#include "entity/entitymanager.h" +#include "util/memory.h" + +void entityTriggerInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + + memoryZero(trig, sizeof(entitytrigger_t)); + + trig->shape.type = PHYSICS_SHAPE_CUBE; + trig->shape.data.cube.halfExtents[0] = 0.5f; + trig->shape.data.cube.halfExtents[1] = 0.5f; + trig->shape.data.cube.halfExtents[2] = 0.5f; + trig->collideMask = 0x1; +} + +entitytrigger_t *entityTriggerGet( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + return componentGetData(mgr, entityId, componentId, COMPONENT_TYPE_TRIGGER); +} + +void entityTriggerSetShape( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const physicsshape_t shape +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + trig->shape = shape; +} + +physicsshape_t entityTriggerGetShape( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + return trig->shape; +} + +void entityTriggerSetCollideMask( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint32_t mask +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + trig->collideMask = mask; +} + +uint32_t entityTriggerGetCollideMask( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + return trig->collideMask; +} + +uint8_t entityTriggerGetOccupantCount( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + return trig->occupantCount; +} + +entityid_t entityTriggerGetOccupantEntityId( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint8_t index +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + if(index >= trig->occupantCount) return ENTITY_ID_INVALID; + return trig->occupants[index].entityId; +} + +bool_t entityTriggerIsOccupyingEntity( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const entityid_t otherEntityId +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + for(uint8_t i = 0; i < trig->occupantCount; i++) { + if(trig->occupants[i].entityId == otherEntityId) return true; + } + return false; +} diff --git a/src/dusk/entity/component/trigger/entitytrigger.h b/src/dusk/entity/component/trigger/entitytrigger.h new file mode 100644 index 00000000..d4d37f9e --- /dev/null +++ b/src/dusk/entity/component/trigger/entitytrigger.h @@ -0,0 +1,226 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" +#include "physics/physicsshape.h" +#include "error/error.h" + +/** Maximum number of entities a single trigger can track at once. */ +#define ENTITY_TRIGGER_OCCUPANTS_MAX 8 + +/** Maximum number of subscribers per trigger event (onEnter/onActive/...). */ +#define ENTITY_TRIGGER_CALLBACK_COUNT_MAX 4 + +/** + * Callback invoked for a trigger's onEnter/onActive/onMove/onLeave events. + * See entityTriggerOnEnterAdd() and its onActive/onMove/onLeave siblings. + * + * @param mgr The entity manager that owns both entities. + * @param triggerEntityId The entity ID of the trigger volume. + * @param triggerComponentId The TRIGGER component ID. + * @param otherEntityId The entity ID that entered/is inside/left the + * trigger volume. + * @param otherComponentId The other entity's POSITION component ID. + * @param user The user pointer passed to the matching *Add() call. + */ +typedef void (*triggercallback_t)( + entitymanager_t *mgr, + const entityid_t triggerEntityId, + const componentid_t triggerComponentId, + const entityid_t otherEntityId, + const componentid_t otherComponentId, + void *user +); + +/** Tracks one entity currently overlapping a trigger volume. */ +typedef struct { + /** The occupying entity's ID. */ + entityid_t entityId; + /** The occupying entity's POSITION component ID. */ + componentid_t componentId; + /** The occupying entity's local position as of the last step, used to + * detect movement (see entityTriggerOnMoveAdd()). */ + vec3 lastPosition; +} entitytriggeroccupant_t; + +typedef struct { + /** The trigger volume's shape, in the owning entity's local space. */ + physicsshape_t shape; + + /** + * Bitmask of collision layers/categories this trigger detects. An + * entity's PHYSICS body is only tested against this trigger if + * (trigger.collideMask & body.collideMask) is non-zero -- i.e. they + * share at least one set bit. Defaults to 0x1 (only bit 0 set). See + * entityPhysicsSetCollideMask(). + */ + uint32_t collideMask; + + /** Number of entities currently tracked in occupants. */ + uint8_t occupantCount; + /** Entities currently overlapping this trigger. */ + entitytriggeroccupant_t occupants[ENTITY_TRIGGER_OCCUPANTS_MAX]; + + /** Fired the step an entity begins overlapping this trigger. */ + uint8_t onEnterCount; + triggercallback_t onEnter[ENTITY_TRIGGER_CALLBACK_COUNT_MAX]; + void *onEnterUser[ENTITY_TRIGGER_CALLBACK_COUNT_MAX]; + + /** Fired every step an entity continues overlapping this trigger, + * starting the step after onEnter fired. */ + uint8_t onActiveCount; + triggercallback_t onActive[ENTITY_TRIGGER_CALLBACK_COUNT_MAX]; + void *onActiveUser[ENTITY_TRIGGER_CALLBACK_COUNT_MAX]; + + /** Fired alongside onActive on any step where the entity's position + * changed since the previous step. */ + uint8_t onMoveCount; + triggercallback_t onMove[ENTITY_TRIGGER_CALLBACK_COUNT_MAX]; + void *onMoveUser[ENTITY_TRIGGER_CALLBACK_COUNT_MAX]; + + /** Fired the step an entity stops overlapping this trigger. */ + uint8_t onLeaveCount; + triggercallback_t onLeave[ENTITY_TRIGGER_CALLBACK_COUNT_MAX]; + void *onLeaveUser[ENTITY_TRIGGER_CALLBACK_COUNT_MAX]; +} entitytrigger_t; + +/** + * Initializes the trigger component: defaults to a 1x1x1 cube volume, no + * tracked occupants, and no event subscribers. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + */ +void entityTriggerInit( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Gets the underlying trigger structure (temporarily) for the given + * entity. Prefer the dedicated getters/setters where possible. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The trigger component data for the given entity and component ID. + */ +entitytrigger_t *entityTriggerGet( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets the shape of the trigger volume. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param shape The new shape to set on the trigger volume. + */ +void entityTriggerSetShape( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const physicsshape_t shape +); + +/** + * Gets the shape of the trigger volume. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The shape of the trigger volume. + */ +physicsshape_t entityTriggerGetShape( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Sets the collision mask of the trigger volume. An entity's PHYSICS body + * is only detected if their masks share at least one set bit. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param mask The new collision mask to set on the trigger volume. + */ +void entityTriggerSetCollideMask( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint32_t mask +); + +/** + * Gets the collision mask of the trigger volume. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The collision mask of the trigger volume. + */ +uint32_t entityTriggerGetCollideMask( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Gets the number of entities currently overlapping this trigger. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @return The number of currently tracked occupants. + */ +uint8_t entityTriggerGetOccupantCount( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId +); + +/** + * Gets the entity ID of the occupant at the given index, in [0, + * entityTriggerGetOccupantCount()). Order is not stable across steps. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param index The occupant index. + * @return The occupying entity's ID, or ENTITY_ID_INVALID if index is out + * of range. + */ +entityid_t entityTriggerGetOccupantEntityId( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const uint8_t index +); + +/** + * Checks whether a specific entity is currently overlapping this trigger. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param otherEntityId The entity ID to check for. + * @return True if otherEntityId is a currently tracked occupant. + */ +bool_t entityTriggerIsOccupyingEntity( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const entityid_t otherEntityId +); diff --git a/src/dusk/entity/component/trigger/entitytriggerevents.c b/src/dusk/entity/component/trigger/entitytriggerevents.c new file mode 100644 index 00000000..753f6e25 --- /dev/null +++ b/src/dusk/entity/component/trigger/entitytriggerevents.c @@ -0,0 +1,221 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entitytriggerevents.h" +#include "assert/assert.h" + +void entityTriggerOnEnterAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback, + void *user +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + assertTrue( + trig->onEnterCount < ENTITY_TRIGGER_CALLBACK_COUNT_MAX, + "Trigger onEnter callback slots full" + ); + trig->onEnter[trig->onEnterCount] = callback; + trig->onEnterUser[trig->onEnterCount] = user; + trig->onEnterCount++; +} + +void entityTriggerOnEnterRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + for(uint8_t i = 0; i < trig->onEnterCount; i++) { + if(trig->onEnter[i] != callback) continue; + trig->onEnterCount--; + for(uint8_t j = i; j < trig->onEnterCount; j++) { + trig->onEnter[j] = trig->onEnter[j + 1]; + trig->onEnterUser[j] = trig->onEnterUser[j + 1]; + } + return; + } +} + +void entityTriggerOnActiveAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback, + void *user +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + assertTrue( + trig->onActiveCount < ENTITY_TRIGGER_CALLBACK_COUNT_MAX, + "Trigger onActive callback slots full" + ); + trig->onActive[trig->onActiveCount] = callback; + trig->onActiveUser[trig->onActiveCount] = user; + trig->onActiveCount++; +} + +void entityTriggerOnActiveRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + for(uint8_t i = 0; i < trig->onActiveCount; i++) { + if(trig->onActive[i] != callback) continue; + trig->onActiveCount--; + for(uint8_t j = i; j < trig->onActiveCount; j++) { + trig->onActive[j] = trig->onActive[j + 1]; + trig->onActiveUser[j] = trig->onActiveUser[j + 1]; + } + return; + } +} + +void entityTriggerOnMoveAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback, + void *user +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + assertTrue( + trig->onMoveCount < ENTITY_TRIGGER_CALLBACK_COUNT_MAX, + "Trigger onMove callback slots full" + ); + trig->onMove[trig->onMoveCount] = callback; + trig->onMoveUser[trig->onMoveCount] = user; + trig->onMoveCount++; +} + +void entityTriggerOnMoveRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + for(uint8_t i = 0; i < trig->onMoveCount; i++) { + if(trig->onMove[i] != callback) continue; + trig->onMoveCount--; + for(uint8_t j = i; j < trig->onMoveCount; j++) { + trig->onMove[j] = trig->onMove[j + 1]; + trig->onMoveUser[j] = trig->onMoveUser[j + 1]; + } + return; + } +} + +void entityTriggerOnLeaveAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback, + void *user +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + assertTrue( + trig->onLeaveCount < ENTITY_TRIGGER_CALLBACK_COUNT_MAX, + "Trigger onLeave callback slots full" + ); + trig->onLeave[trig->onLeaveCount] = callback; + trig->onLeaveUser[trig->onLeaveCount] = user; + trig->onLeaveCount++; +} + +void entityTriggerOnLeaveRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback +) { + entitytrigger_t *trig = entityTriggerGet(mgr, entityId, componentId); + for(uint8_t i = 0; i < trig->onLeaveCount; i++) { + if(trig->onLeave[i] != callback) continue; + trig->onLeaveCount--; + for(uint8_t j = i; j < trig->onLeaveCount; j++) { + trig->onLeave[j] = trig->onLeave[j + 1]; + trig->onLeaveUser[j] = trig->onLeaveUser[j + 1]; + } + return; + } +} + +void entityTriggerFireEnter( + entitymanager_t *mgr, + const entityid_t triggerEntityId, + const componentid_t triggerComponentId, + const entityid_t otherEntityId, + const componentid_t otherComponentId +) { + entitytrigger_t *trig = entityTriggerGet( + mgr, triggerEntityId, triggerComponentId + ); + for(uint8_t i = 0; i < trig->onEnterCount; i++) { + trig->onEnter[i]( + mgr, triggerEntityId, triggerComponentId, + otherEntityId, otherComponentId, trig->onEnterUser[i] + ); + } +} + +void entityTriggerFireActive( + entitymanager_t *mgr, + const entityid_t triggerEntityId, + const componentid_t triggerComponentId, + const entityid_t otherEntityId, + const componentid_t otherComponentId +) { + entitytrigger_t *trig = entityTriggerGet( + mgr, triggerEntityId, triggerComponentId + ); + for(uint8_t i = 0; i < trig->onActiveCount; i++) { + trig->onActive[i]( + mgr, triggerEntityId, triggerComponentId, + otherEntityId, otherComponentId, trig->onActiveUser[i] + ); + } +} + +void entityTriggerFireMove( + entitymanager_t *mgr, + const entityid_t triggerEntityId, + const componentid_t triggerComponentId, + const entityid_t otherEntityId, + const componentid_t otherComponentId +) { + entitytrigger_t *trig = entityTriggerGet( + mgr, triggerEntityId, triggerComponentId + ); + for(uint8_t i = 0; i < trig->onMoveCount; i++) { + trig->onMove[i]( + mgr, triggerEntityId, triggerComponentId, + otherEntityId, otherComponentId, trig->onMoveUser[i] + ); + } +} + +void entityTriggerFireLeave( + entitymanager_t *mgr, + const entityid_t triggerEntityId, + const componentid_t triggerComponentId, + const entityid_t otherEntityId, + const componentid_t otherComponentId +) { + entitytrigger_t *trig = entityTriggerGet( + mgr, triggerEntityId, triggerComponentId + ); + for(uint8_t i = 0; i < trig->onLeaveCount; i++) { + trig->onLeave[i]( + mgr, triggerEntityId, triggerComponentId, + otherEntityId, otherComponentId, trig->onLeaveUser[i] + ); + } +} diff --git a/src/dusk/entity/component/trigger/entitytriggerevents.h b/src/dusk/entity/component/trigger/entitytriggerevents.h new file mode 100644 index 00000000..d71ba5e4 --- /dev/null +++ b/src/dusk/entity/component/trigger/entitytriggerevents.h @@ -0,0 +1,220 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entitytrigger.h" + +/** + * Subscribes a callback to a trigger's onEnter event, fired the step an + * entity begins overlapping the trigger volume. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param callback The callback to invoke. + * @param user Arbitrary pointer forwarded to the callback unchanged. + */ +void entityTriggerOnEnterAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback, + void *user +); + +/** + * Unsubscribes a previously added onEnter callback. No-op if not + * currently subscribed. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param callback The callback that was passed to entityTriggerOnEnterAdd(). + */ +void entityTriggerOnEnterRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback +); + +/** + * Subscribes a callback to a trigger's onActive event, fired every step an + * entity continues overlapping the trigger volume (starting the step + * after onEnter fired). + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param callback The callback to invoke. + * @param user Arbitrary pointer forwarded to the callback unchanged. + */ +void entityTriggerOnActiveAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback, + void *user +); + +/** + * Unsubscribes a previously added onActive callback. No-op if not + * currently subscribed. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param callback The callback that was passed to entityTriggerOnActiveAdd(). + */ +void entityTriggerOnActiveRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback +); + +/** + * Subscribes a callback to a trigger's onMove event, fired alongside + * onActive on any step where the overlapping entity's position changed + * since the previous step. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param callback The callback to invoke. + * @param user Arbitrary pointer forwarded to the callback unchanged. + */ +void entityTriggerOnMoveAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback, + void *user +); + +/** + * Unsubscribes a previously added onMove callback. No-op if not currently + * subscribed. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param callback The callback that was passed to entityTriggerOnMoveAdd(). + */ +void entityTriggerOnMoveRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback +); + +/** + * Subscribes a callback to a trigger's onLeave event, fired the step an + * entity stops overlapping the trigger volume. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param callback The callback to invoke. + * @param user Arbitrary pointer forwarded to the callback unchanged. + */ +void entityTriggerOnLeaveAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback, + void *user +); + +/** + * Unsubscribes a previously added onLeave callback. No-op if not + * currently subscribed. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity ID. + * @param componentId The component ID. + * @param callback The callback that was passed to entityTriggerOnLeaveAdd(). + */ +void entityTriggerOnLeaveRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + const triggercallback_t callback +); + +/** + * Internal. Invokes every subscribed onEnter callback for a trigger. + * Called by triggerSystemStep() when an entity begins overlapping. + * + * @param mgr The entity manager that owns both entities. + * @param triggerEntityId The entity ID of the trigger volume. + * @param triggerComponentId The TRIGGER component ID. + * @param otherEntityId The entity ID that entered the trigger volume. + * @param otherComponentId The other entity's POSITION component ID. + */ +void entityTriggerFireEnter( + entitymanager_t *mgr, + const entityid_t triggerEntityId, + const componentid_t triggerComponentId, + const entityid_t otherEntityId, + const componentid_t otherComponentId +); + +/** + * Internal. Invokes every subscribed onActive callback for a trigger. + * Called by triggerSystemStep() for every entity still overlapping. + * + * @param mgr The entity manager that owns both entities. + * @param triggerEntityId The entity ID of the trigger volume. + * @param triggerComponentId The TRIGGER component ID. + * @param otherEntityId The entity ID still inside the trigger volume. + * @param otherComponentId The other entity's POSITION component ID. + */ +void entityTriggerFireActive( + entitymanager_t *mgr, + const entityid_t triggerEntityId, + const componentid_t triggerComponentId, + const entityid_t otherEntityId, + const componentid_t otherComponentId +); + +/** + * Internal. Invokes every subscribed onMove callback for a trigger. + * Called by triggerSystemStep() when an overlapping entity's position + * changed since the previous step. + * + * @param mgr The entity manager that owns both entities. + * @param triggerEntityId The entity ID of the trigger volume. + * @param triggerComponentId The TRIGGER component ID. + * @param otherEntityId The entity ID that moved within the trigger volume. + * @param otherComponentId The other entity's POSITION component ID. + */ +void entityTriggerFireMove( + entitymanager_t *mgr, + const entityid_t triggerEntityId, + const componentid_t triggerComponentId, + const entityid_t otherEntityId, + const componentid_t otherComponentId +); + +/** + * Internal. Invokes every subscribed onLeave callback for a trigger. + * Called by triggerSystemStep() when an entity stops overlapping. + * + * @param mgr The entity manager that owns both entities. + * @param triggerEntityId The entity ID of the trigger volume. + * @param triggerComponentId The TRIGGER component ID. + * @param otherEntityId The entity ID that left the trigger volume. + * @param otherComponentId The other entity's POSITION component ID. + */ +void entityTriggerFireLeave( + entitymanager_t *mgr, + const entityid_t triggerEntityId, + const componentid_t triggerComponentId, + const entityid_t otherEntityId, + const componentid_t otherComponentId +); diff --git a/src/dusk/entity/componentlist.h b/src/dusk/entity/componentlist.h new file mode 100644 index 00000000..07eb6eb1 --- /dev/null +++ b/src/dusk/entity/componentlist.h @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entity/component/display/entityposition.h" +#include "entity/component/display/entitycamera.h" +#include "entity/component/display/entityrenderable.h" +#include "entity/component/physics/entityphysics.h" +#include "entity/component/trigger/entitytrigger.h" +#include "entity/component/animation/entityanimation.h" + +// Name (Uppercase) +// Structure +// Field name (lowercase) +// Init function (optional) +// Dispose function (optional) +// Render function (optional) + +X(POSITION, entityposition_t, position, entityPositionInit, NULL, NULL) +X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL, NULL) +X(RENDERABLE, entityrenderable_t, renderable, + entityRenderableInit, entityRenderableDispose, NULL) +X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, NULL, NULL) +X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL, NULL) +X(ANIMATION, entityanimation_t, animation, entityAnimationInit, NULL, NULL) + +// Game-specific components +#include "entity/gamecomponentlist.h" diff --git a/src/dusk/entity/entity.c b/src/dusk/entity/entity.c new file mode 100644 index 00000000..9bf3f740 --- /dev/null +++ b/src/dusk/entity/entity.c @@ -0,0 +1,215 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entitymanager.h" +#include "component/display/entityposition.h" +#include "util/memory.h" +#include "util/string.h" +#include "assert/assert.h" + +void entityInit(entitymanager_t *mgr, const entityid_t entityId) { + entity_t *ent = &mgr->entities[entityId]; + + memoryZero(ent, sizeof(entity_t)); + + // Mark all component types not using this entity. + for( + componenttype_t compType = 0; + compType < COMPONENT_TYPE_COUNT; + compType++ + ) { + mgr->entitiesWithComponent[ + compType * ENTITY_COUNT_MAX + entityId + ] = COMPONENT_ID_INVALID; + } + + ent->state |= ENTITY_STATE_ACTIVE; +} + +componentid_t entityAddComponent( + entitymanager_t *mgr, + const entityid_t entityId, + const componenttype_t type +) { + componentindex_t compInd; + + for(componentid_t i = 0; i < ENTITY_COMPONENT_COUNT_MAX; i++) { + compInd = componentGetIndex(entityId, i); + if(mgr->components[compInd].type != COMPONENT_TYPE_NULL) { + assertTrue( + mgr->components[compInd].type != type, + "Entity already has component of this type" + ); + continue; + } + componentInit(mgr, entityId, i, type); + mgr->entitiesWithComponent[ + type * ENTITY_COUNT_MAX + entityId + ] = i; + return i; + } + + assertUnreachable("Entity has no more component slots available"); + return COMPONENT_ID_INVALID; +} + +componentid_t entityGetComponent( + entitymanager_t *mgr, + const entityid_t entityId, + const componenttype_t type +) { + componentid_t compId = mgr->entitiesWithComponent[ + type * ENTITY_COUNT_MAX + entityId + ]; + if(compId == COMPONENT_ID_INVALID) return compId; + assertTrue( + mgr->components[componentGetIndex(entityId, compId)].type == type, + "Component type mismatch" + ); + return compId; +} + +void entitySetName( + entitymanager_t *mgr, + const entityid_t entityId, + const char_t *name +) { + stringCopy(mgr->entities[entityId].name, name, ENTITY_NAME_MAX); +} + +const char_t *entityGetName( + entitymanager_t *mgr, + const entityid_t entityId +) { + return mgr->entities[entityId].name; +} + +entityid_t entityFindByName( + entitymanager_t *mgr, + const char_t *name +) { + for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) { + if(!(mgr->entities[i].state & ENTITY_STATE_ACTIVE)) continue; + if(stringEquals(mgr->entities[i].name, name)) return i; + } + return ENTITY_ID_INVALID; +} + +void entityDisposeDeep(entitymanager_t *mgr, const entityid_t entityId) { + componentid_t posComp = entityGetComponent( + mgr, entityId, COMPONENT_TYPE_POSITION + ); + if(posComp != COMPONENT_ID_INVALID) { + entityPositionDisposeDeep(mgr, entityId, posComp); + } else { + entityDispose(mgr, entityId); + } +} + +void entityUpdate(entitymanager_t *mgr, const entityid_t entityId) { + entity_t *ent = &mgr->entities[entityId]; + for(uint8_t i = 0; i < ent->updateCount; i++) { + ent->onUpdate[i]( + mgr, entityId, ent->updateComponentId[i], ent->updateUser[i] + ); + } +} + +void entityDispose(entitymanager_t *mgr, const entityid_t entityId) { + componentindex_t compInd; + entity_t *ent = &mgr->entities[entityId]; + + for(uint8_t i = 0; i < ent->disposeCount; i++) { + ent->onDispose[i]( + mgr, entityId, ent->disposeComponentId[i], ent->disposeUser[i] + ); + } + + for(componentid_t i = 0; i < ENTITY_COMPONENT_COUNT_MAX; i++) { + compInd = componentGetIndex(entityId, i); + componenttype_t type = mgr->components[compInd].type; + if(type == COMPONENT_TYPE_NULL) continue; + mgr->entitiesWithComponent[ + type * ENTITY_COUNT_MAX + entityId + ] = COMPONENT_ID_INVALID; + componentDispose(mgr, entityId, i); + } + + ent->state = 0; +} + +void entityUpdateAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const entitycallback_t callback, + const componentid_t componentId, + void *user +) { + entity_t *ent = &mgr->entities[entityId]; + assertTrue( + ent->updateCount < ENTITY_UPDATE_CALLBACK_COUNT_MAX, + "Entity update callback slots full" + ); + ent->onUpdate[ent->updateCount] = callback; + ent->updateComponentId[ent->updateCount] = componentId; + ent->updateUser[ent->updateCount] = user; + ent->updateCount++; +} + +void entityUpdateRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const entitycallback_t callback +) { + entity_t *ent = &mgr->entities[entityId]; + for(uint8_t i = 0; i < ent->updateCount; i++) { + if(ent->onUpdate[i] != callback) continue; + ent->updateCount--; + for(uint8_t j = i; j < ent->updateCount; j++) { + ent->onUpdate[j] = ent->onUpdate[j + 1]; + ent->updateComponentId[j] = ent->updateComponentId[j + 1]; + ent->updateUser[j] = ent->updateUser[j + 1]; + } + return; + } +} + +void entityDisposeAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const entitycallback_t callback, + const componentid_t componentId, + void *user +) { + entity_t *ent = &mgr->entities[entityId]; + assertTrue( + ent->disposeCount < ENTITY_DISPOSE_CALLBACK_COUNT_MAX, + "Entity dispose callback slots full" + ); + ent->onDispose[ent->disposeCount] = callback; + ent->disposeComponentId[ent->disposeCount] = componentId; + ent->disposeUser[ent->disposeCount] = user; + ent->disposeCount++; +} + +void entityDisposeRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const entitycallback_t callback +) { + entity_t *ent = &mgr->entities[entityId]; + for(uint8_t i = 0; i < ent->disposeCount; i++) { + if(ent->onDispose[i] != callback) continue; + ent->disposeCount--; + for(uint8_t j = i; j < ent->disposeCount; j++) { + ent->onDispose[j] = ent->onDispose[j + 1]; + ent->disposeComponentId[j] = ent->disposeComponentId[j + 1]; + ent->disposeUser[j] = ent->disposeUser[j + 1]; + } + return; + } +} diff --git a/src/dusk/entity/entity.h b/src/dusk/entity/entity.h new file mode 100644 index 00000000..adb6c41e --- /dev/null +++ b/src/dusk/entity/entity.h @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "component.h" +#include "error/error.h" + +#define ENTITY_STATE_ACTIVE (1 << 0) + +#define ENTITY_UPDATE_CALLBACK_COUNT_MAX 5 +#define ENTITY_DISPOSE_CALLBACK_COUNT_MAX 5 +#define ENTITY_NAME_MAX 32 + +typedef void (*entitycallback_t)( + entitymanager_t *mgr, + const entityid_t entityId, + const componentid_t componentId, + void *user +); + +typedef struct { + char_t name[ENTITY_NAME_MAX]; + uint8_t state; + uint8_t updateCount; + uint8_t disposeCount; + entitycallback_t onUpdate[ENTITY_UPDATE_CALLBACK_COUNT_MAX]; + componentid_t updateComponentId[ENTITY_UPDATE_CALLBACK_COUNT_MAX]; + void *updateUser[ENTITY_UPDATE_CALLBACK_COUNT_MAX]; + entitycallback_t onDispose[ENTITY_DISPOSE_CALLBACK_COUNT_MAX]; + componentid_t disposeComponentId[ENTITY_DISPOSE_CALLBACK_COUNT_MAX]; + void *disposeUser[ENTITY_DISPOSE_CALLBACK_COUNT_MAX]; +} entity_t; + +/** + * Initializes an entity with the given ID. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The ID of the entity to initialize. + */ +void entityInit(entitymanager_t *mgr, const entityid_t entityId); + +/** + * Adds a component of the given type to the entity with the given ID. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The ID of the entity to add the component to. + * @param type The type of the component to add. + * @return The ID of the entity with component. + */ +componentid_t entityAddComponent( + entitymanager_t *mgr, + const entityid_t entityId, + const componenttype_t type +); + +/** + * Gets the ID of the component of the given type on the entity with the + * given ID, or COMPONENT_ID_INVALID if the entity lacks the component. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The ID of the entity to get the component from. + * @param type The type of the component to get. + * @return The ID of the component. + */ +componentid_t entityGetComponent( + entitymanager_t *mgr, + const entityid_t entityId, + const componenttype_t type +); + +/** + * Sets an entity's name. Names are optional -- an entity's name is empty + * by default (entityInit zeroes it) -- but are required to make the + * entity referenceable by name, e.g. as a parent via + * entityPositionSetParent(). + * + * @param mgr The entity manager that owns the entity. + * @param entityId The ID of the entity to name. + * @param name The name to give the entity. Must be shorter than + * ENTITY_NAME_MAX. + */ +void entitySetName( + entitymanager_t *mgr, + const entityid_t entityId, + const char_t *name +); + +/** + * Gets an entity's name, or an empty string if it has none. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The ID of the entity to get the name of. + * @return The entity's name. + */ +const char_t *entityGetName( + entitymanager_t *mgr, + const entityid_t entityId +); + +/** + * Finds the first active entity with the given name. + * + * @param mgr The entity manager to search. + * @param name The name to search for. + * @return The entity ID, or ENTITY_ID_INVALID if no active entity matches. + */ +entityid_t entityFindByName( + entitymanager_t *mgr, + const char_t *name +); + +/** + * Runs all registered update callbacks for the entity. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The ID of the entity to update. + */ +void entityUpdate(entitymanager_t *mgr, const entityid_t entityId); + +/** + * Disposes of an entity with the given ID. Fires all dispose callbacks + * before cleaning up components and state. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The ID of the entity to dispose of. + */ +void entityDispose(entitymanager_t *mgr, const entityid_t entityId); + +/** + * Disposes of an entity and all of its position-component descendants + * recursively. If the entity has no position component, behaves like + * entityDispose. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The root entity ID. + */ +void entityDisposeDeep(entitymanager_t *mgr, const entityid_t entityId); + +/** + * Registers an update callback, invoked each time entityUpdate is called. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to register on. + * @param callback The function to call. + * @param componentId The component ID passed back to the callback. + * @param user Userdata passed back to the callback. + */ +void entityUpdateAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const entitycallback_t callback, + const componentid_t componentId, + void *user +); + +/** + * Removes a previously registered update callback. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to remove from. + * @param callback The function to remove. + */ +void entityUpdateRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const entitycallback_t callback +); + +/** + * Registers a dispose callback, invoked at the start of entityDispose + * before any component or state cleanup. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to register on. + * @param callback The function to call. + * @param componentId The component ID passed back to the callback. + * @param user Userdata passed back to the callback. + */ +void entityDisposeAdd( + entitymanager_t *mgr, + const entityid_t entityId, + const entitycallback_t callback, + const componentid_t componentId, + void *user +); + +/** + * Removes a previously registered dispose callback. + * + * @param mgr The entity manager that owns the entity. + * @param entityId The entity to remove from. + * @param callback The function to remove. + */ +void entityDisposeRemove( + entitymanager_t *mgr, + const entityid_t entityId, + const entitycallback_t callback +); diff --git a/src/dusk/entity/entitybase.h b/src/dusk/entity/entitybase.h new file mode 100644 index 00000000..649c647c --- /dev/null +++ b/src/dusk/entity/entitybase.h @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +#define ENTITY_COUNT_MAX 64 +#define ENTITY_COMPONENT_COUNT_MAX 16 + +#define ENTITY_ID_INVALID 0xFF +#define COMPONENT_ID_INVALID 0xFF + +typedef uint8_t entityid_t; +typedef uint8_t componentid_t; +typedef uint16_t componentindex_t; + +// Forward declared here (rather than in component.h) so every entity/ +// component header can reference entitymanager_t* regardless of include +// order, without needing a full entitymanager.h include. +typedef struct entitymanager_t entitymanager_t; diff --git a/src/dusk/entity/entitymanager.c b/src/dusk/entity/entitymanager.c new file mode 100644 index 00000000..c661d338 --- /dev/null +++ b/src/dusk/entity/entitymanager.c @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "entitymanager.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "console/console.h" + +void entityManagerInit(entitymanager_t *mgr) { + assertNotNull(mgr, "Entity manager cannot be null"); + memoryZero(mgr, sizeof(entitymanager_t)); + memorySet( + mgr->entitiesWithComponent, COMPONENT_ID_INVALID, + sizeof(componentid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX + ); + + consolePrint( + "Entity manager size: %zu bytes (%.2f KB)", + sizeof(entitymanager_t), + sizeof(entitymanager_t) / 1024.0f + ); +} + +entityid_t entityManagerAdd(entitymanager_t *mgr) { + assertNotNull(mgr, "Entity manager cannot be null"); + for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) { + if((mgr->entities[i].state & ENTITY_STATE_ACTIVE) != 0) continue; + entityInit(mgr, i); + return i; + } + assertUnreachable("No more entity IDs available"); + return ENTITY_ID_INVALID; +} + +void entityManagerUpdate(entitymanager_t *mgr) { + assertNotNull(mgr, "Entity manager cannot be null"); + entityid_t i = 0; + while(i < ENTITY_COUNT_MAX) { + if((mgr->entities[i].state & ENTITY_STATE_ACTIVE) != 0) { + entityUpdate(mgr, i); + } + i++; + } +} + +void entityManagerDispose(entitymanager_t *mgr) { + assertNotNull(mgr, "Entity manager cannot be null"); + for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) { + if((mgr->entities[i].state & ENTITY_STATE_ACTIVE) == 0) continue; + entityDispose(mgr, i); + } +} diff --git a/src/dusk/entity/entitymanager.h b/src/dusk/entity/entitymanager.h new file mode 100644 index 00000000..7872a0b9 --- /dev/null +++ b/src/dusk/entity/entitymanager.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity.h" + +typedef struct entitymanager_t { + entity_t entities[ENTITY_COUNT_MAX]; + component_t components[ENTITY_COUNT_MAX * ENTITY_COMPONENT_COUNT_MAX]; + componentid_t entitiesWithComponent[COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX]; +} entitymanager_t; + +/** + * Initializes an entity manager, marking all entities and components as + * unused. + * + * @param mgr The entity manager to initialize. + */ +void entityManagerInit(entitymanager_t *mgr); + +/** + * Adds / Reserves a new entity ID in the given manager. + * + * @param mgr The entity manager to allocate the entity in. + * @return The new entity ID. + */ +entityid_t entityManagerAdd(entitymanager_t *mgr); + +/** + * Updates all active entities in the given manager. + * + * @param mgr The entity manager to update. + */ +void entityManagerUpdate(entitymanager_t *mgr); + +/** + * Disposes of the entity manager, in turn freeing all entities and + * components. + * + * @param mgr The entity manager to dispose. + */ +void entityManagerDispose(entitymanager_t *mgr); diff --git a/src/dusk/ui/uielementlist.h b/src/dusk/entity/gamecomponentlist.h similarity index 65% rename from src/dusk/ui/uielementlist.h rename to src/dusk/entity/gamecomponentlist.h index 11bbca33..d7d10633 100644 --- a/src/dusk/ui/uielementlist.h +++ b/src/dusk/entity/gamecomponentlist.h @@ -5,7 +5,4 @@ * https://opensource.org/licenses/MIT */ -#pragma once -#include "ui/uielement.h" - -extern uielement_t UI_ELEMENTS[]; +// Game-specific components are appended here. diff --git a/src/dusk/physics/physicsbodytype.h b/src/dusk/physics/physicsbodytype.h new file mode 100644 index 00000000..b4aff902 --- /dev/null +++ b/src/dusk/physics/physicsbodytype.h @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef enum { + /** + * Never moves. Acts as an immovable collision surface. + */ + PHYSICS_BODY_STATIC, + + /** + * Simulated by the world step: gravity, forces, and collision response. + */ + PHYSICS_BODY_DYNAMIC, + + /** + * Moved programmatically via the owning entity's position component; + * collides but is not driven by the simulation. Typical use: player + * character controller. + */ + PHYSICS_BODY_KINEMATIC +} physicsbodytype_t; diff --git a/src/dusk/physics/physicsshape.h b/src/dusk/physics/physicsshape.h new file mode 100644 index 00000000..52f56f0f --- /dev/null +++ b/src/dusk/physics/physicsshape.h @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "error/error.h" + +typedef enum { + PHYSICS_SHAPE_CUBE, + PHYSICS_SHAPE_SPHERE, + PHYSICS_SHAPE_CAPSULE, + PHYSICS_SHAPE_PLANE, + + /** + * Arbitrary shape of any complexity (heightfield, mesh, etc), tested via + * a caller-supplied callback rather than a built-in primitive routine. + * See physicsshapecustom_t. + */ + PHYSICS_SHAPE_CUSTOM +} physicshapetype_t; + +typedef struct { + vec3 halfExtents; +} physicsshapecube_t; + +typedef struct { + float_t radius; +} physicsshapesphere_t; + +typedef struct { + float_t radius; + float_t halfHeight; +} physicsshapecapsule_t; + +typedef struct { + vec3 normal; + float_t distance; +} physicsshapeplane_t; + +typedef struct physicsshape_t physicsshape_t; + +/** + * Tests a custom shape (positioned at selfPos, describing itself via + * selfData) against another shape (positioned at otherPos). Return true + * and fill outNormal/outDepth if the shapes overlap. + * + * outNormal points from self toward other -- the direction to push other + * away from self, e.g. straight up for a body resting on flat ground. + * This is the opposite convention from physicsTestShapeVsShape's + * A-vs-B/outNormal (which points from B toward A); physicsWorldStep + * always queries a custom shape as the static/kinematic side, so this + * convention lets an implementation delegate directly to a primitive test + * like physicsTestSphereVsPlane (self=ground, other=body) and return its + * result unmodified. + * + * @param selfPos The custom shape's position. + * @param selfData The custom shape's own describing data (physicsshapecustom_t.userData). + * @param otherPos The other shape's position. + * @param otherShape The other shape's full descriptor. May itself be any + * type except PHYSICS_SHAPE_CUSTOM -- custom-vs-custom is not supported. + * @param outNormal Push-out normal, pointing from self toward other. + * @param outDepth Penetration depth (positive when overlapping). + * @return true if the shapes overlap, false otherwise. + */ +typedef bool_t (*physicsshapecustomtest_t)( + const vec3 selfPos, + const void *selfData, + const vec3 otherPos, + const physicsshape_t *otherShape, + vec3 outNormal, + float_t *outDepth +); + +/** + * A shape of arbitrary complexity, e.g. landscape/terrain geometry, that + * the built-in primitive tests (cube/sphere/capsule/plane) cannot describe. + * Rather than embedding the shape's data (which may be large -- a + * heightfield, a triangle mesh, a BVH) inline, this just holds an opaque + * pointer plus a callback that the physics system invokes to test overlap + * against it. + * + * Ownership: userData is not owned or freed by the physics system. The + * struct/asset that actually describes the shape (e.g. a chunk's + * heightfield) must outlive this physicsshape_t -- typically it's owned by + * whatever entity/asset also owns the entityphysics_t component that holds + * this shape. + */ +typedef struct { + /** Opaque pointer to the owner's shape-describing data. */ + void *userData; + /** Callback invoked to test this shape against another. */ + physicsshapecustomtest_t test; +} physicsshapecustom_t; + +typedef union { + physicsshapecube_t cube; + physicsshapesphere_t sphere; + physicsshapecapsule_t capsule; + physicsshapeplane_t plane; + physicsshapecustom_t custom; +} physicsshapedata_t; + +typedef struct physicsshape_t { + physicshapetype_t type; + physicsshapedata_t data; +} physicsshape_t; diff --git a/src/dusk/rpg/CMakeLists.txt b/src/dusk/rpg/CMakeLists.txt deleted file mode 100644 index 51db8064..00000000 --- a/src/dusk/rpg/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - rpg.c - rpgcamera.c -) - -# Subdirs -add_subdirectory(battle) -add_subdirectory(cutscene) -add_subdirectory(entity) -add_subdirectory(overworld) - -add_subdirectory(story) -add_subdirectory(item) \ No newline at end of file diff --git a/src/dusk/rpg/battle/CMakeLists.txt b/src/dusk/rpg/battle/CMakeLists.txt deleted file mode 100644 index 49363600..00000000 --- a/src/dusk/rpg/battle/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - battle.c - party.c -) - -add_subdirectory(fighter) -add_subdirectory(move) -add_subdirectory(state) diff --git a/src/dusk/rpg/battle/battle.c b/src/dusk/rpg/battle/battle.c deleted file mode 100644 index f9e2d47a..00000000 --- a/src/dusk/rpg/battle/battle.c +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battle.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "scene/scene.h" - -battle_t BATTLE; - -void battleInit(void) { - memoryZero(&BATTLE, sizeof(battle_t)); -} - -void battleStart( - battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT] -) { - assertNotNull(fighters, "Fighters cannot be NULL"); - - #if DUSK_ASSERTIONS - // Must be at least two non null fighter present, may need to ensure they - // are from seperate teams also. - uint8_t nonNullCount = 0; - for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) { - if(fighters[i] != NULL) nonNullCount++; - } - assertTrue(nonNullCount >= 2, "Must have at least two fighters"); - #endif - - - // TODO: I may not need to zero entire battle. - memoryZero(&BATTLE, sizeof(battle_t)); - memoryCopy( - BATTLE.fighters, - fighters, - sizeof(battlefighter_t *) * BATTLE_FIGHTER_POS_COUNT - ); - - // Enter the opening state. - battleSetState(BATTLE_STATE_OPENING); - - // Change scene - sceneSet(SCENE_TYPE_BATTLE); -} - -void battleSetState(const battlestate_t next) { - assertTrue(next < BATTLE_STATE_COUNT, "Invalid battle state"); - assertNotNull(BATTLE_STATE_FUNCTIONS[next].init, "Missing state init function"); - assertNotNull(BATTLE_STATE_FUNCTIONS[next].update, "Missing state update function"); - assertNotNull(BATTLE_STATE_FUNCTIONS[next].dispose, "Missing state dispose function"); - - if(BATTLE.state != BATTLE_STATE_NULL) { - BATTLE_STATE_FUNCTIONS[BATTLE.state].dispose(); - } - BATTLE.state = next; - BATTLE_STATE_FUNCTIONS[BATTLE.state].init(); -} - -void battleUpdate(void) { - if(BATTLE.state == BATTLE_STATE_NULL) return; - assertTrue(BATTLE.state < BATTLE_STATE_COUNT, "Invalid battle state"); - assertNotNull(BATTLE_STATE_FUNCTIONS[BATTLE.state].update, "Missing state update function"); - - BATTLE_STATE_FUNCTIONS[BATTLE.state].update(); -} - -void battleDispose(void) { - if(BATTLE.state == BATTLE_STATE_NULL) return; - assertTrue(BATTLE.state < BATTLE_STATE_COUNT, "Invalid battle state"); - assertNotNull(BATTLE_STATE_FUNCTIONS[BATTLE.state].dispose, "Missing state dispose function"); - - BATTLE_STATE_FUNCTIONS[BATTLE.state].dispose(); - memoryZero(&BATTLE, sizeof(battle_t)); -} \ No newline at end of file diff --git a/src/dusk/rpg/battle/battle.h b/src/dusk/rpg/battle/battle.h deleted file mode 100644 index 108a0742..00000000 --- a/src/dusk/rpg/battle/battle.h +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/battle/fighter/battlefighter.h" -#include "rpg/battle/move/battlemove.h" -#include "rpg/battle/move/battlemoveaction.h" -#include "rpg/battle/fighter/battlefighterpos.h" -#include "rpg/battle/state/battlestate.h" - -typedef enum { - BATTLE_RESULT_NONE, - BATTLE_RESULT_WIN, - BATTLE_RESULT_LOSS, - BATTLE_RESULT_FLED, - BATTLE_RESULT_COUNT -} battleresult_t; - -typedef struct { - battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT]; - - // Order of who gets to act. - uint8_t fighterOrder[BATTLE_FIGHTER_POS_COUNT]; - uint8_t fighterOrderCount; - - // Actions to be executed. - battlemoveaction_t actionsPending[BATTLE_FIGHTER_POS_COUNT]; - uint8_t actionsPendingCount; - - battlestate_t state; - battlestatedata_t stateData; - - // Set once a side has been wiped out (see cutsceneBattlePostMoveStart) - // or the player has fled (see battleStateSelectionPlayerFlee). Stays - // BATTLE_RESULT_NONE for the rest of the battle otherwise. - battleresult_t result; -} battle_t; - -extern battle_t BATTLE; - -/** - * Initializes the battle system. Marks it as inactive with no fighters. - */ -void battleInit(void); - -/** - * Starts a battle. - * - * @param fighters Fighters in the battle. - */ -void battleStart( - battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT] -); - -/** - * Transitions the battle to a new state: disposes the current state (if - * any) and initializes next. - * - * @param next The state to transition to. - */ -void battleSetState(const battlestate_t next); - -/** - * Updates the battle system. - */ -void battleUpdate(void); - -/** - * Disposes of the battle, clearing all fighters and marking it inactive. - */ -void battleDispose(void); \ No newline at end of file diff --git a/src/dusk/rpg/battle/fighter/battlefighter.c b/src/dusk/rpg/battle/fighter/battlefighter.c deleted file mode 100644 index 27ebe06f..00000000 --- a/src/dusk/rpg/battle/fighter/battlefighter.c +++ /dev/null @@ -1,230 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlefighter.h" -#include "assert/assert.h" -#include "util/memory.h" - -errorret_t battleFighterParseStatField( - yyjson_val *obj, - const char_t *key, - uint16_t *out -) { - yyjson_val *val = yyjson_obj_get(obj, key); - if(!yyjson_is_int(val)) { - errorThrow("Fighter: missing/invalid '%s'", key); - } - - const int64_t num = yyjson_get_sint(val); - if(num < 0 || num > UINT16_MAX) { - errorThrow("Fighter: '%s' must be between 0 and %u", key, UINT16_MAX); - } - - *out = (uint16_t)num; - errorOk(); -} - -errorret_t battleFighterParseOptionalIntField( - yyjson_val *obj, - const char_t *key, - const int32_t defaultValue, - int32_t *out -) { - yyjson_val *val = yyjson_obj_get(obj, key); - if(val == NULL) { - *out = defaultValue; - errorOk(); - } - - if(!yyjson_is_int(val)) errorThrow("Fighter: invalid '%s'", key); - *out = (int32_t)yyjson_get_sint(val); - errorOk(); -} - -errorret_t battleFighterParse(battlefighter_t *fighter, yyjson_val *obj) { - assertNotNull(fighter, "Fighter cannot be NULL"); - assertNotNull(obj, "JSON object cannot be NULL"); - - int32_t teamNum; - errorChain(battleFighterParseOptionalIntField( - obj, "team", BATTLE_FIGHTER_TEAM_ALLY, &teamNum - )); - if(teamNum < 0 || teamNum >= BATTLE_FIGHTER_TEAM_COUNT) { - errorThrow("Fighter: invalid 'team'"); - } - - int32_t controllerNum; - errorChain(battleFighterParseOptionalIntField( - obj, "controller", BATTLE_FIGHTER_CONTROLLER_PLAYER, &controllerNum - )); - if(controllerNum < 0 || controllerNum >= BATTLE_FIGHTER_CONTROLLER_COUNT) { - errorThrow("Fighter: invalid 'controller'"); - } - - battlefighterstats_t stats; - errorChain(battleFighterParseStatField(obj, "attack", &stats.attack)); - errorChain(battleFighterParseStatField(obj, "defense", &stats.defense)); - errorChain(battleFighterParseStatField(obj, "magic", &stats.magic)); - errorChain(battleFighterParseStatField(obj, "speed", &stats.speed)); - errorChain(battleFighterParseStatField(obj, "luck", &stats.luck)); - errorChain(battleFighterParseStatField(obj, "healthMax", &stats.healthMax)); - errorChain(battleFighterParseStatField(obj, "mpMax", &stats.mpMax)); - - battleFighterInit( - fighter, (battlefighterteam_t)teamNum, - (battlefightercontroller_t)controllerNum, stats - ); - - int32_t statusNum; - errorChain(battleFighterParseOptionalIntField( - obj, "status", BATTLE_FIGHTER_STATUS_NORMAL, &statusNum - )); - if(statusNum < 0 || statusNum >= BATTLE_FIGHTER_STATUS_COUNT) { - errorThrow("Fighter: invalid 'status'"); - } - fighter->status = (battlefighterstatus_t)statusNum; - - int32_t health; - errorChain( - battleFighterParseOptionalIntField(obj, "health", stats.healthMax, &health) - ); - if(health < 0 || health > stats.healthMax) { - errorThrow("Fighter: 'health' out of range"); - } - fighter->stats.health = (uint16_t)health; - - int32_t mp; - errorChain(battleFighterParseOptionalIntField(obj, "mp", stats.mpMax, &mp)); - if(mp < 0 || mp > stats.mpMax) { - errorThrow("Fighter: 'mp' out of range"); - } - fighter->stats.mp = (uint16_t)mp; - - yyjson_val *abilities = yyjson_obj_get(obj, "abilities"); - if(abilities != NULL) { - if( - !yyjson_is_arr(abilities) || - yyjson_arr_size(abilities) > BATTLE_FIGHTER_ABILITY_COUNT_MAX - ) { - errorThrow( - "Fighter: 'abilities' must have 0-%u entries", - BATTLE_FIGHTER_ABILITY_COUNT_MAX - ); - } - - yyjson_arr_iter iter = yyjson_arr_iter_with(abilities); - yyjson_val *abilityVal; - while((abilityVal = yyjson_arr_iter_next(&iter)) != NULL) { - const char_t *name = yyjson_get_str(abilityVal); - battlemoveid_t ability = - name == NULL ? BATTLE_MOVE_ID_NULL : battleMoveGetIdByName(name); - if(ability == BATTLE_MOVE_ID_NULL) { - errorThrow("Fighter: invalid entry in 'abilities'"); - } - battleFighterAbilityAdd(fighter, ability); - } - } - - errorOk(); -} - -yyjson_mut_val *battleFighterSerialize( - const battlefighter_t *fighter, - yyjson_mut_doc *doc -) { - assertNotNull(fighter, "Fighter cannot be NULL"); - assertNotNull(doc, "Doc cannot be NULL"); - - yyjson_mut_val *obj = yyjson_mut_obj(doc); - - yyjson_mut_obj_add_uint(doc, obj, "team", fighter->team); - yyjson_mut_obj_add_uint(doc, obj, "controller", fighter->controller); - yyjson_mut_obj_add_uint(doc, obj, "status", fighter->status); - - yyjson_mut_obj_add_uint(doc, obj, "attack", fighter->stats.attack); - yyjson_mut_obj_add_uint(doc, obj, "defense", fighter->stats.defense); - yyjson_mut_obj_add_uint(doc, obj, "magic", fighter->stats.magic); - yyjson_mut_obj_add_uint(doc, obj, "speed", fighter->stats.speed); - yyjson_mut_obj_add_uint(doc, obj, "luck", fighter->stats.luck); - yyjson_mut_obj_add_uint(doc, obj, "healthMax", fighter->stats.healthMax); - yyjson_mut_obj_add_uint(doc, obj, "mpMax", fighter->stats.mpMax); - yyjson_mut_obj_add_uint(doc, obj, "health", fighter->stats.health); - yyjson_mut_obj_add_uint(doc, obj, "mp", fighter->stats.mp); - - yyjson_mut_val *abilities = yyjson_mut_arr(doc); - for(uint8_t i = 0; i < fighter->abilityCount; i++) { - yyjson_mut_arr_add_str(doc, abilities, BATTLE_MOVES[fighter->abilities[i]].name); - } - yyjson_mut_obj_add_val(doc, obj, "abilities", abilities); - - return obj; -} - -void battleFighterInit( - battlefighter_t *fighter, - const battlefighterteam_t team, - const battlefightercontroller_t controller, - const battlefighterstats_t stats -) { - assertNotNull(fighter, "Fighter pointer cannot be NULL"); - assertTrue(team < BATTLE_FIGHTER_TEAM_COUNT, "Invalid fighter team"); - assertTrue( - controller < BATTLE_FIGHTER_CONTROLLER_COUNT, - "Invalid fighter controller" - ); - - const uint8_t id = fighter->id; - memoryZero(fighter, sizeof(battlefighter_t)); - fighter->id = id; - fighter->status = BATTLE_FIGHTER_STATUS_NORMAL; - fighter->team = team; - fighter->controller = controller; - fighter->stats = stats; - fighter->stats.health = stats.healthMax; - fighter->stats.mp = stats.mpMax; -} - -bool_t battleFighterIsAlive(const battlefighter_t *fighter) { - assertNotNull(fighter, "Fighter pointer cannot be NULL"); - return fighter->status == BATTLE_FIGHTER_STATUS_NORMAL; -} - -void battleFighterAbilityAdd( - battlefighter_t *fighter, - const battlemoveid_t ability -) { - assertNotNull(fighter, "Fighter pointer cannot be NULL"); - assertTrue( - fighter->abilityCount < BATTLE_FIGHTER_ABILITY_COUNT_MAX, - "Fighter already has the maximum number of abilities" - ); - - fighter->abilities[fighter->abilityCount++] = ability; -} - -void battleFighterDamage( - battlefighter_t *fighter, - const uint16_t amount -) { - assertNotNull(fighter, "Fighter pointer cannot be NULL"); - - fighter->stats.health = - amount >= fighter->stats.health ? 0 : fighter->stats.health - amount; - if(fighter->stats.health == 0) fighter->status = BATTLE_FIGHTER_STATUS_DEAD; -} - -void battleFighterHeal( - battlefighter_t *fighter, - const uint16_t amount -) { - assertNotNull(fighter, "Fighter pointer cannot be NULL"); - if(!battleFighterIsAlive(fighter)) return; - - const uint32_t healed = (uint32_t)fighter->stats.health + amount; - fighter->stats.health = healed > fighter->stats.healthMax - ? fighter->stats.healthMax : (uint16_t)healed; -} diff --git a/src/dusk/rpg/battle/fighter/battlefighter.h b/src/dusk/rpg/battle/fighter/battlefighter.h deleted file mode 100644 index 447f9772..00000000 --- a/src/dusk/rpg/battle/fighter/battlefighter.h +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "yyjson.h" -#include "rpg/battle/move/battlemove.h" -#include "rpg/battle/fighter/battlefighterstatus.h" -#include "rpg/battle/fighter/battlefighterteam.h" -#include "rpg/battle/fighter/battlefightercontroller.h" -#include "rpg/battle/fighter/battlefighterstats.h" -#include "rpg/battle/fighter/battlefighterpos.h" - -#define BATTLE_FIGHTER_ABILITY_COUNT_MAX 8 - -typedef struct battlefighter_s { - uint8_t id; - battlefighterstatus_t status; - battlefighterteam_t team; - battlefightercontroller_t controller; - - - battlefighterstats_t stats; - - battlemoveid_t abilities[BATTLE_FIGHTER_ABILITY_COUNT_MAX]; - battlemoveid_t abilityCount; -} battlefighter_t; - -/** - * Initializes a battle fighter in place, filling health/mp to the maximum - * values carried on stats and setting its status to normal. - * - * @param fighter Pointer to the fighter to initialize. - * @param team The team the fighter belongs to. - * @param controller Who makes decisions for the fighter. - * @param stats The fighter's base combat stats, including healthMax/mpMax. - */ -void battleFighterInit( - battlefighter_t *fighter, - const battlefighterteam_t team, - const battlefightercontroller_t controller, - const battlefighterstats_t stats -); - -/** - * Generic battlefighter_t parser, shared by every place that builds a - * fighter from JSON (party.jsonc roster members, BATTLE_START enemy - * definitions, save-file party members) - see battleFighterSerialize for - * the inverse. Reads 'team'/'controller'/'status' as ints (each optional, - * defaulting to ALLY/PLAYER/NORMAL respectively - a caller that needs a - * different team/controller, e.g. BATTLE_START's enemies, sets those - * fields on fighter after this returns), 'attack'/'defense'/'magic'/ - * 'speed'/'luck'/'healthMax'/'mpMax' as required ints in [0, UINT16_MAX], - * 'health'/'mp' as optional ints defaulting to healthMax/mpMax (a fresh - * fighter), and an optional 'abilities' array of move name strings. Does - * not touch fighter->id - set it (or leave it) before calling, same as - * battleFighterInit. - * - * @param fighter Pointer to the fighter to fill in. - * @param obj The JSON object to read fields from. - * @return Error if a required field is missing/invalid, or an abilities - * entry doesn't name a real move. - */ -errorret_t battleFighterParse(battlefighter_t *fighter, yyjson_val *obj); - -/** - * Inverse of battleFighterParse: builds a new JSON object (not yet - * attached anywhere - the caller attaches it, e.g. via - * yyjson_mut_arr_add_val/yyjson_mut_obj_add_val) with every field - * battleFighterParse reads except id (see its doc comment for why). - * - * @param fighter The fighter to serialize. - * @param doc The mutable document to allocate the object in. - * @return The newly built object. - */ -yyjson_mut_val *battleFighterSerialize( - const battlefighter_t *fighter, - yyjson_mut_doc *doc -); - -/** - * Returns true if the fighter is in a state where it can still act (i.e. - * is not dead). - * - * @param fighter Pointer to the fighter to check. - * @returns True if the fighter can act. - */ -bool_t battleFighterIsAlive(const battlefighter_t *fighter); - -/** - * Appends an ability to the fighter's ability list. - * - * @param fighter Pointer to the fighter to add the ability to. - * @param ability The ability to add. - */ -void battleFighterAbilityAdd( - battlefighter_t *fighter, - const battlemoveid_t ability -); - -/** - * Subtracts amount from the fighter's health (floored at 0), marking it - * dead once health reaches 0. - * - * @param fighter Pointer to the fighter to damage. - * @param amount The amount of damage to apply. - */ -void battleFighterDamage( - battlefighter_t *fighter, - const uint16_t amount -); - -/** - * Adds amount to the fighter's health (capped at healthMax). No-op if the - * fighter isn't alive -- healing can't revive a dead fighter. - * - * @param fighter Pointer to the fighter to heal. - * @param amount The amount to heal. - */ -void battleFighterHeal( - battlefighter_t *fighter, - const uint16_t amount -); diff --git a/src/dusk/rpg/battle/fighter/battlefightercontroller.h b/src/dusk/rpg/battle/fighter/battlefightercontroller.h deleted file mode 100644 index 5e0389d0..00000000 --- a/src/dusk/rpg/battle/fighter/battlefightercontroller.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef enum { - BATTLE_FIGHTER_CONTROLLER_NONE, - - BATTLE_FIGHTER_CONTROLLER_PLAYER, - BATTLE_FIGHTER_CONTROLLER_AI, - - BATTLE_FIGHTER_CONTROLLER_COUNT -} battlefightercontroller_t; diff --git a/src/dusk/rpg/battle/fighter/battlefighterpos.h b/src/dusk/rpg/battle/fighter/battlefighterpos.h deleted file mode 100644 index e7f237b0..00000000 --- a/src/dusk/rpg/battle/fighter/battlefighterpos.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef uint8_t battlefighterpos_t; - -#define BATTLE_FIGHTER_POS(x) ((battlefighterpos_t)(x)) - -// Left (Enemy side) -#define BATTLE_FIGHTER_POS_0 BATTLE_FIGHTER_POS(0) -#define BATTLE_FIGHTER_POS_1 BATTLE_FIGHTER_POS(1) -#define BATTLE_FIGHTER_POS_2 BATTLE_FIGHTER_POS(2) -#define BATTLE_FIGHTER_POS_3 BATTLE_FIGHTER_POS(3) - -// Right (Ally side) -#define BATTLE_FIGHTER_POS_4 BATTLE_FIGHTER_POS(4) -#define BATTLE_FIGHTER_POS_5 BATTLE_FIGHTER_POS(5) -#define BATTLE_FIGHTER_POS_6 BATTLE_FIGHTER_POS(6) -#define BATTLE_FIGHTER_POS_7 BATTLE_FIGHTER_POS(7) - -#define BATTLE_FIGHTER_POS_COUNT (BATTLE_FIGHTER_POS_7 + 1) - -// Sentinels resolved by battleStateExecutingInit against the currently -// resolving action, for cutscene items authored inside a move's own -// cutscene (e.g. cutscenes/battle/moves/slash.jsonc) that need to refer -// to "whoever is being hit"/"whoever is attacking" without knowing their -// concrete BATTLE.fighters index ahead of time. -#define BATTLE_FIGHTER_POS_TARGET BATTLE_FIGHTER_POS(0xFE) -#define BATTLE_FIGHTER_POS_USER BATTLE_FIGHTER_POS(0xFD) \ No newline at end of file diff --git a/src/dusk/rpg/battle/fighter/battlefighterspecies.h b/src/dusk/rpg/battle/fighter/battlefighterspecies.h deleted file mode 100644 index 92162b98..00000000 --- a/src/dusk/rpg/battle/fighter/battlefighterspecies.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "rpg/battle/fighter/battlefighterstats.h" - -#define BATTLE_FIGHTER_SPECIES_NAME_MAX 16 -#define BATTLE_FIGHTER_SPECIES_COUNT_MAX 250 - -typedef uint8_t battlespeciesid_t; - -#define BATTLE_SPECIES_ID_NULL ((battlespeciesid_t)0) - -typedef struct { - char_t name[BATTLE_FIGHTER_SPECIES_NAME_MAX]; - battlefighterstats_t baseStats; -} battlefighterspecies_t; diff --git a/src/dusk/rpg/battle/fighter/battlefighterstats.h b/src/dusk/rpg/battle/fighter/battlefighterstats.h deleted file mode 100644 index f4b731bb..00000000 --- a/src/dusk/rpg/battle/fighter/battlefighterstats.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - uint16_t health; - uint16_t mp; - - uint16_t healthMax; - uint16_t mpMax; - uint16_t attack; - uint16_t defense; - uint16_t magic; - uint16_t speed; - uint16_t luck; -} battlefighterstats_t; diff --git a/src/dusk/rpg/battle/fighter/battlefighterstatus.h b/src/dusk/rpg/battle/fighter/battlefighterstatus.h deleted file mode 100644 index 3c73af2d..00000000 --- a/src/dusk/rpg/battle/fighter/battlefighterstatus.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef enum { - BATTLE_FIGHTER_STATUS_NULL, - - BATTLE_FIGHTER_STATUS_NORMAL, - BATTLE_FIGHTER_STATUS_DEAD, - - // Poisoned, burned, confused, etc. - - BATTLE_FIGHTER_STATUS_COUNT -} battlefighterstatus_t; diff --git a/src/dusk/rpg/battle/fighter/battlefighterteam.h b/src/dusk/rpg/battle/fighter/battlefighterteam.h deleted file mode 100644 index 85abda07..00000000 --- a/src/dusk/rpg/battle/fighter/battlefighterteam.h +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef enum { - BATTLE_FIGHTER_TEAM_ALLY, - BATTLE_FIGHTER_TEAM_ENEMY, - - BATTLE_FIGHTER_TEAM_COUNT -} battlefighterteam_t; diff --git a/src/dusk/rpg/battle/move/battlemove.c b/src/dusk/rpg/battle/move/battlemove.c deleted file mode 100644 index 389c5ee0..00000000 --- a/src/dusk/rpg/battle/move/battlemove.c +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlemove.h" -#include "asset/asset.h" -#include "asset/loader/assetloader.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include - -battleability_t BATTLE_MOVES[BATTLE_MOVE_COUNT_MAX]; -uint8_t BATTLE_MOVE_COUNT; - -errorret_t battleMoveInit(void) { - memoryZero(BATTLE_MOVES, sizeof(BATTLE_MOVES)); - BATTLE_MOVE_COUNT = BATTLE_MOVE_ID_NULL + 1;// Reserve NULL. - - // Get moves JSON. - assetentry_t *entry = assetLock( - "battle/moves.jsonc", ASSET_LOADER_TYPE_JSON, NULL - ); - errorret_t ret = assetRequireLoaded(entry); - if(errorIsNotOk(ret)) { - assetUnlockEntry(entry); - errorChain(ret); - } - - yyjson_val *root = yyjson_doc_get_root(entry->data.json); - yyjson_val *moves = yyjson_obj_get(root, "moves"); - yyjson_obj_iter iter = yyjson_obj_iter_with(moves); - yyjson_val *key; - while((key = yyjson_obj_iter_next(&iter))) { - if(BATTLE_MOVE_COUNT >= BATTLE_MOVE_COUNT_MAX) { - assetUnlockEntry(entry); - errorThrow( - "Battle move: too many moves defined (max %u)", - (uint32_t)BATTLE_MOVE_COUNT_MAX - ); - } - - battlemoveid_t id = BATTLE_MOVE_COUNT++; - battleability_t *move = &BATTLE_MOVES[id]; - - // Name - if(!yyjson_is_str(key)) { - assetUnlockEntry(entry); - errorThrow("Battle move: keys in 'moves' must be strings"); - } - const char_t *name = yyjson_get_str(key); - stringCopy(move->name, name, BATTLE_MOVE_NAME_MAX); - - if(strlen(name) >= BATTLE_MOVE_NAME_MAX) { - assetUnlockEntry(entry); - errorThrow( - "Battle move '%s': name must be less than %u characters", - name, (uint32_t)BATTLE_MOVE_NAME_MAX - ); - } - - // Must me object - yyjson_val *val = yyjson_obj_iter_get_val(key); - if(!yyjson_is_obj(val)) { - assetUnlockEntry(entry); - errorThrow("Battle move '%s': value must be an object", name); - } - - // Valid targets - yyjson_val *enemies = yyjson_obj_get(val, "enemies"); - if(enemies != NULL && !yyjson_is_bool(enemies)) { - assetUnlockEntry(entry); - errorThrow("Battle move '%s': 'enemies' must be a boolean", name); - } - yyjson_val *allies = yyjson_obj_get(val, "allies"); - if(allies != NULL && !yyjson_is_bool(allies)) { - assetUnlockEntry(entry); - errorThrow("Battle move '%s': 'allies' must be a boolean", name); - } - yyjson_val *multiple = yyjson_obj_get(val, "multiple"); - if(multiple != NULL && !yyjson_is_bool(multiple)) { - assetUnlockEntry(entry); - errorThrow("Battle move '%s': 'multiple' must be a boolean", name); - } - yyjson_val *self = yyjson_obj_get(val, "self"); - if(self != NULL && !yyjson_is_bool(self)) { - assetUnlockEntry(entry); - errorThrow("Battle move '%s': 'self' must be a boolean", name); - } - - battletarget_t target = 0; - if(yyjson_get_bool(enemies)) target |= BATTLE_TARGET_ENEMIES; - if(yyjson_get_bool(allies)) target |= BATTLE_TARGET_ALLIES; - if(yyjson_get_bool(multiple)) target |= BATTLE_TARGET_MULTIPLE; - if(yyjson_get_bool(self)) target |= BATTLE_TARGET_SELF; - move->target = target; - if(move->target == 0) { - assetUnlockEntry(entry); - errorThrow("Battle move '%s': missing target type", name); - } - - // Default target (optional) - yyjson_val *defaultVal = yyjson_obj_get(val, "default"); - if(defaultVal != NULL && !yyjson_is_str(defaultVal)) { - assetUnlockEntry(entry); - errorThrow("Battle move '%s': 'default' must be a string", name); - } - - const char_t *defaultTarget = yyjson_get_str(defaultVal); - if(defaultTarget == NULL) { - move->targetDefault = 0; - } else if(stringCompareInsensitive(defaultTarget, "enemy") == 0) { - move->targetDefault = BATTLE_TARGET_ENEMIES; - } else if(stringCompareInsensitive(defaultTarget, "ally") == 0) { - move->targetDefault = BATTLE_TARGET_ALLIES; - } else if(stringCompareInsensitive(defaultTarget, "self") == 0) { - move->targetDefault = BATTLE_TARGET_SELF; - } else { - assetUnlockEntry(entry); - errorThrow("Battle move %s has invalid default target.", name); - } - } - - assetUnlockEntry(entry); - errorOk(); -} - -battlemoveid_t battleMoveGetIdByName(const char_t *name) { - assertNotNull(name, "name must not be NULL"); - - for(battlemoveid_t id = BATTLE_MOVE_ID_NULL + 1; id < BATTLE_MOVE_COUNT; id++) { - if(stringEquals(BATTLE_MOVES[id].name, name)) return id; - } - - return BATTLE_MOVE_ID_NULL; -} diff --git a/src/dusk/rpg/battle/move/battlemove.h b/src/dusk/rpg/battle/move/battlemove.h deleted file mode 100644 index 3c11b2d2..00000000 --- a/src/dusk/rpg/battle/move/battlemove.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "asset/asset.h" -#include "battletarget.h" - -typedef uint8_t battlemoveid_t; - -#define BATTLE_MOVE_NAME_MAX 16 -#define BATTLE_MOVE_COUNT_MAX 250 - -#define BATTLE_MOVE_ID_NULL ((battlemoveid_t)0) - -extern uint8_t BATTLE_MOVE_COUNT; - -typedef struct { - char_t name[BATTLE_MOVE_NAME_MAX]; - battletarget_t target; - battletarget_t targetDefault; -} battleability_t; - -extern battleability_t BATTLE_MOVES[BATTLE_MOVE_COUNT_MAX]; - -/** - * Initializes the battle move subsystem, loading assets/battle/moves.jsonc. - * - * @return Any error that occurs. - */ -errorret_t battleMoveInit(void); - -/** - * Finds a move's id by name (exact match). - * - * @param name The move name to search for. - * @return The matching move's id, or BATTLE_MOVE_ID_NULL if not found. - */ -battlemoveid_t battleMoveGetIdByName(const char_t *name); diff --git a/src/dusk/rpg/battle/move/battlemoveaction.h b/src/dusk/rpg/battle/move/battlemoveaction.h deleted file mode 100644 index f39f8852..00000000 --- a/src/dusk/rpg/battle/move/battlemoveaction.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/battle/move/battlemove.h" -#include "rpg/battle/fighter/battlefighterpos.h" - -typedef struct battlefighter_s battlefighter_t; - -typedef struct { - battlemoveid_t move; - battlefighterpos_t targets[BATTLE_FIGHTER_POS_COUNT]; - battlefighter_t *user; -} battlemoveaction_t; diff --git a/src/dusk/rpg/battle/move/battletarget.h b/src/dusk/rpg/battle/move/battletarget.h deleted file mode 100644 index 73abfb38..00000000 --- a/src/dusk/rpg/battle/move/battletarget.h +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef uint8_t battletarget_t; - -#define BATTLE_TARGET_ENEMIES (1 << 0) -#define BATTLE_TARGET_ALLIES (1 << 1) -#define BATTLE_TARGET_MULTIPLE (1 << 2) -#define BATTLE_TARGET_SELF (1 << 3) \ No newline at end of file diff --git a/src/dusk/rpg/battle/party.c b/src/dusk/rpg/battle/party.c deleted file mode 100644 index 016e4ca3..00000000 --- a/src/dusk/rpg/battle/party.c +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "party.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "save/save.h" -#include "asset/asset.h" -#include "asset/loader/assetloader.h" - -errorret_t partyInit(party_t *party) { - assertNotNull(party, "Party cannot be NULL"); - - memoryZero(party, sizeof(party_t)); - for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) { - party->order[i] = PARTY_ORDER_EMPTY; - } - - assetentry_t *entry = assetLock("party.jsonc", ASSET_LOADER_TYPE_JSON, NULL); - errorret_t result = assetRequireLoaded(entry); - if(errorIsNotOk(result)) { - assetUnlockEntry(entry); - errorChain(result); - } - - yyjson_val *root = yyjson_doc_get_root(entry->data.json); - yyjson_val *members = yyjson_obj_get(root, "members"); - if(!yyjson_is_obj(members)) { - assetUnlockEntry(entry); - errorThrow("party.jsonc: missing/invalid 'members' object"); - } - - const size_t memberCount = yyjson_obj_size(members); - if(memberCount > PARTY_MEMBER_COUNT_MAX) { - assetUnlockEntry(entry); - errorThrow( - "party.jsonc: defines more than PARTY_MEMBER_COUNT_MAX (%u) members", - PARTY_MEMBER_COUNT_MAX - ); - } - - uint8_t idx = 0; - yyjson_val *key; - yyjson_val *memberObj; - yyjson_obj_iter iter = yyjson_obj_iter_with(members); - while((key = yyjson_obj_iter_next(&iter))) { - memberObj = yyjson_obj_iter_get_val(key); - - if(yyjson_get_len(key) >= PARTY_KEY_LENGTH_MAX) { - assetUnlockEntry(entry); - errorThrow( - "party.jsonc: member key '%s' exceeds PARTY_KEY_LENGTH_MAX (%u)", - yyjson_get_str(key), PARTY_KEY_LENGTH_MAX - ); - } - - stringCopy(party->keys[idx], yyjson_get_str(key), PARTY_KEY_LENGTH_MAX - 1); - - battlefighter_t *member = &party->members[idx]; - member->id = idx; - errorret_t parseResult = battleFighterParse(member, memberObj); - if(errorIsNotOk(parseResult)) { - assetUnlockEntry(entry); - errorChain(parseResult); - } - - idx++; - } - - assetUnlockEntry(entry); - - // TEST: only the first defined member starts active, in the first party - // position - replace once there's a real starting-party/character- - // creation flow. Deliberately separate from the roster seeding above: - // order (not a member's presence in the roster) is what determines who's - // currently in the party. - if(memberCount > 0) party->order[0] = 0; - - errorOk(); -} - -uint8_t partyGetMemberIndexByKey(const char_t *key) { - assertNotNull(key, "Key cannot be NULL"); - - const party_t *party = &SAVE.slot.party; - for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) { - if(stringEquals(party->keys[i], key)) return i; - } - - return PARTY_MEMBER_NOT_FOUND; -} - -const char_t *partyGetMemberKey(const uint8_t memberIndex) { - assertTrue(memberIndex < PARTY_MEMBER_COUNT_MAX, "Invalid party member index"); - return SAVE.slot.party.keys[memberIndex]; -} - -bool_t partyIsMemberActive(const uint8_t memberIndex) { - assertTrue(memberIndex < PARTY_MEMBER_COUNT_MAX, "Invalid party member index"); - - const party_t *party = &SAVE.slot.party; - for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) { - if(party->order[i] == memberIndex) return true; - } - - return false; -} - -void partyAddMember(const uint8_t memberIndex) { - assertTrue(memberIndex < PARTY_MEMBER_COUNT_MAX, "Invalid party member index"); - - party_t *party = &SAVE.slot.party; - assertTrue( - party->members[memberIndex].status != BATTLE_FIGHTER_STATUS_NULL, - "Member does not exist in the roster" - ); - - if(partyIsMemberActive(memberIndex)) return; - - for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) { - if(party->order[i] != PARTY_ORDER_EMPTY) continue; - party->order[i] = memberIndex; - return; - } -} - -battlefighter_t *partyGetOrderMember(const uint8_t slot) { - assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot"); - - party_t *party = &SAVE.slot.party; - const uint8_t index = party->order[slot]; - if(index == PARTY_ORDER_EMPTY) return NULL; - return &party->members[index]; -} - -void partySetOrder(const uint8_t slot, const uint8_t memberIndex) { - assertTrue(slot < PARTY_ACTIVE_SIZE_MAX, "Invalid party order slot"); - assertTrue( - memberIndex == PARTY_ORDER_EMPTY || memberIndex < PARTY_MEMBER_COUNT_MAX, - "Invalid party member index" - ); - - SAVE.slot.party.order[slot] = memberIndex; -} - -void partyRemoveMember(const uint8_t memberIndex) { - assertTrue(memberIndex < PARTY_MEMBER_COUNT_MAX, "Invalid party member index"); - - party_t *party = &SAVE.slot.party; - - uint8_t removedSlot = PARTY_ORDER_EMPTY; - for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) { - if(party->order[i] != memberIndex) continue; - removedSlot = i; - break; - } - if(removedSlot == PARTY_ORDER_EMPTY) return; - - for(uint8_t i = removedSlot; i < PARTY_ACTIVE_SIZE_MAX - 1; i++) { - party->order[i] = party->order[i + 1]; - } - party->order[PARTY_ACTIVE_SIZE_MAX - 1] = PARTY_ORDER_EMPTY; -} - -void partyHealMember(const uint8_t memberIndex) { - assertTrue(memberIndex < PARTY_MEMBER_COUNT_MAX, "Invalid party member index"); - - battlefighter_t *member = &SAVE.slot.party.members[memberIndex]; - if(member->status == BATTLE_FIGHTER_STATUS_NULL) return; - - member->status = BATTLE_FIGHTER_STATUS_NORMAL; - member->stats.health = member->stats.healthMax; - member->stats.mp = member->stats.mpMax; -} - -void partyHealAll(void) { - const party_t *party = &SAVE.slot.party; - - for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) { - if(party->members[i].status == BATTLE_FIGHTER_STATUS_NULL) continue; - partyHealMember(i); - } -} diff --git a/src/dusk/rpg/battle/party.h b/src/dusk/rpg/battle/party.h deleted file mode 100644 index 8feedaef..00000000 --- a/src/dusk/rpg/battle/party.h +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/battle/fighter/battlefighter.h" -#include "yyjson.h" - -#define PARTY_MEMBER_COUNT_MAX 10 -#define PARTY_ACTIVE_SIZE_MAX 4 -#define PARTY_KEY_LENGTH_MAX 32 -#define PARTY_ORDER_EMPTY 0xFF -#define PARTY_MEMBER_NOT_FOUND 0xFF - -typedef struct { - battlefighter_t members[PARTY_MEMBER_COUNT_MAX]; - char_t keys[PARTY_MEMBER_COUNT_MAX][PARTY_KEY_LENGTH_MAX]; - uint8_t order[PARTY_ACTIVE_SIZE_MAX]; -} party_t; - -/** - * Initializes party with an empty order, then seeds every roster slot - * directly from party.jsonc's "members" object (each key/value pair fills - * the next members[]/keys[] slot in the object's iteration order, key - * copied into keys[] and value parsed via battleFighterParse) - unlike - * partyAddMember, this never touches order itself except for one - * placeholder default: if party.jsonc defines at least one member, - * order[0] is set to 0 (the first member starts active in the first party - * position). Replace this default once there's a real starting-party/ - * character-creation flow. - * - * @param party The party to initialize. - * @return Error if party.jsonc fails to load/parse, defines more members - * than PARTY_MEMBER_COUNT_MAX has room for, or a key exceeds - * PARTY_KEY_LENGTH_MAX. - */ -errorret_t partyInit(party_t *party); - -/** - * Looks up a roster member's index by its party.jsonc key (see - * party_t.keys). - * - * @param key The member's key to search for. - * @return The matching roster index, or PARTY_MEMBER_NOT_FOUND if no - * member has that key. - */ -uint8_t partyGetMemberIndexByKey(const char_t *key); - -/** - * Gets a roster member's party.jsonc key by index (see party_t.keys). - * - * @param memberIndex The roster member index to query. - * @return The member's key - "" if memberIndex is past however many - * members party.jsonc actually defines. - */ -const char_t *partyGetMemberKey(const uint8_t memberIndex); - -/** - * Checks whether memberIndex currently occupies a slot in the current save - * slot's party order (i.e. is actively in the party right now). - * - * @param memberIndex The roster member index to check. - * @return True if memberIndex appears anywhere in order. - */ -bool_t partyIsMemberActive(const uint8_t memberIndex); - -/** - * Adds an existing roster member (see partyInit - every roster slot - * always exists once initialized) to the current save slot's active party - * order, in the first free order slot. No-op if memberIndex is already - * active, or if there is no free order slot. - * - * @param memberIndex The roster member index to activate. - */ -void partyAddMember(const uint8_t memberIndex); - -/** - * Gets the roster member currently occupying an active order slot in the - * current save slot's party. - * - * @param slot The active order slot to query. - * @return Pointer to the member in that slot, or NULL if the slot is - * empty. - */ -battlefighter_t *partyGetOrderMember(const uint8_t slot); - -/** - * Assigns a roster member to an active order slot in the current save - * slot's party, replacing whatever was there. Use PARTY_ORDER_EMPTY to - * clear a slot. - * - * @param slot The active order slot to assign. - * @param memberIndex The roster member index to place there, or - * PARTY_ORDER_EMPTY to clear the slot. - */ -void partySetOrder(const uint8_t slot, const uint8_t memberIndex); - -/** - * Removes memberIndex from the current save slot's active party order - - * the roster member itself is untouched (it still exists, per partyInit; - * it's simply no longer active) - shifting every later order entry down - * one slot to close the gap, so order stays packed with no holes between - * active members. No-op if memberIndex isn't currently active. - * - * @param memberIndex The roster member index to deactivate. - */ -void partyRemoveMember(const uint8_t memberIndex); - -/** - * Fully restores a roster member's health/mp to their max and clears a - * dead status back to normal - unlike battleFighterHeal, this can revive - * a dead member (there's no combat context here to keep them down). No-op - * if the slot is empty. - * - * @param memberIndex The roster member index to heal. - */ -void partyHealMember(const uint8_t memberIndex); - -/** - * Calls partyHealMember for every occupied slot in the current save - * slot's party roster. - */ -void partyHealAll(void); diff --git a/src/dusk/rpg/battle/state/CMakeLists.txt b/src/dusk/rpg/battle/state/CMakeLists.txt deleted file mode 100644 index 603227d0..00000000 --- a/src/dusk/rpg/battle/state/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - battlestate.c - battlestateopening.c - battlestatepreround.c - battlestateselection.c - battlestateexecuting.c - battlestatepostround.c - battlestatelost.c - battlestatewon.c - battlestatefled.c - battlestateending.c -) diff --git a/src/dusk/rpg/battle/state/battlestate.c b/src/dusk/rpg/battle/state/battlestate.c deleted file mode 100644 index d5387658..00000000 --- a/src/dusk/rpg/battle/state/battlestate.c +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestate.h" - -battlestatefunctions_t BATTLE_STATE_FUNCTIONS[BATTLE_STATE_COUNT] = { - [BATTLE_STATE_NULL] = { 0 }, - - [BATTLE_STATE_OPENING] = { - .init = battleStateOpeningInit, - .update = battleStateOpeningUpdate, - .dispose = battleStateOpeningDispose - }, - - [BATTLE_STATE_PRE_ROUND] = { - .init = battleStatePreRoundInit, - .update = battleStatePreRoundUpdate, - .dispose = battleStatePreRoundDispose - }, - - [BATTLE_STATE_SELECTION] = { - .init = battleStateSelectionInit, - .update = battleStateSelectionUpdate, - .dispose = battleStateSelectionDispose - }, - - [BATTLE_STATE_EXECUTING] = { - .init = battleStateExecutingInit, - .update = battleStateExecutingUpdate, - .dispose = battleStateExecutingDispose - }, - - [BATTLE_STATE_POST_ROUND] = { - .init = battleStatePostRoundInit, - .update = battleStatePostRoundUpdate, - .dispose = battleStatePostRoundDispose - }, - - [BATTLE_STATE_LOST] = { - .init = battleStateLostInit, - .update = battleStateLostUpdate, - .dispose = battleStateLostDispose - }, - - [BATTLE_STATE_WON] = { - .init = battleStateWonInit, - .update = battleStateWonUpdate, - .dispose = battleStateWonDispose - }, - - [BATTLE_STATE_FLED] = { - .init = battleStateFledInit, - .update = battleStateFledUpdate, - .dispose = battleStateFledDispose - }, - - [BATTLE_STATE_ENDING] = { - .init = battleStateEndingInit, - .update = battleStateEndingUpdate, - .dispose = battleStateEndingDispose - } -}; diff --git a/src/dusk/rpg/battle/state/battlestate.h b/src/dusk/rpg/battle/state/battlestate.h deleted file mode 100644 index 00eddd5e..00000000 --- a/src/dusk/rpg/battle/state/battlestate.h +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "battlestateopening.h" -#include "battlestatepreround.h" -#include "battlestateselection.h" -#include "battlestateexecuting.h" -#include "battlestatepostround.h" -#include "battlestatelost.h" -#include "battlestatewon.h" -#include "battlestatefled.h" -#include "battlestateending.h" - -typedef enum { - // No active battle. - BATTLE_STATE_NULL, - - // Battle hasn't actually started yet, equiv to trainer intro in Pokemon. - BATTLE_STATE_OPENING, - // Pre round, anything that happens before AI or Player can act. - BATTLE_STATE_PRE_ROUND, - // Player is deciding what to do, waiting for their input - BATTLE_STATE_SELECTION, - // Currently executing the moves. - BATTLE_STATE_EXECUTING, - // Post round, anything that happens after AI or Player has acted, loops back - // after this point - BATTLE_STATE_POST_ROUND, - // Battle has ended with a player loss - BATTLE_STATE_LOST, - // Battle has ended with a player win - BATTLE_STATE_WON, - // Battle has ended with a player fleeing - BATTLE_STATE_FLED, - // Battle state is done, this is fade out or whatever. - BATTLE_STATE_ENDING, - - BATTLE_STATE_COUNT -} battlestate_t; - -typedef union { - battlestateopening_t opening; - battlestatepreround_t preRound; - battlestateselection_t selection; - battlestateexecuting_t executing; - battlestatepostround_t postRound; - battlestatelost_t lost; - battlestatewon_t won; - battlestatefled_t fled; - battlestateending_t ending; -} battlestatedata_t; - -typedef struct { - void (*init)(); - void (*update)(); - void (*dispose)(); -} battlestatefunctions_t; - -extern battlestatefunctions_t BATTLE_STATE_FUNCTIONS[BATTLE_STATE_COUNT]; \ No newline at end of file diff --git a/src/dusk/rpg/battle/state/battlestateending.c b/src/dusk/rpg/battle/state/battlestateending.c deleted file mode 100644 index 2a195cbc..00000000 --- a/src/dusk/rpg/battle/state/battlestateending.c +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestateending.h" -#include "rpg/battle/battle.h" -#include "scene/scene.h" - -void battleStateEndingInit(void) { - // Tear down the battle and hand control back to the overworld. The - // overworld scene keeps no state of its own in scenedata_t (player/map - // state lives in the separate global entity/map systems, untouched by - // switching to/from SCENE_TYPE_BATTLE), so there's nothing else to - // restore here. - battleDispose(); - sceneSet(SCENE_TYPE_OVERWORLD); -} - -void battleStateEndingUpdate(void) { - -} - -void battleStateEndingDispose(void) { - -} diff --git a/src/dusk/rpg/battle/state/battlestateending.h b/src/dusk/rpg/battle/state/battlestateending.h deleted file mode 100644 index 7b4fc581..00000000 --- a/src/dusk/rpg/battle/state/battlestateending.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - -} battlestateending_t; - -/** - * Called when the battle enters BATTLE_STATE_ENDING. Disposes the battle - * (see battleDispose) and switches back to SCENE_TYPE_OVERWORLD. - */ -void battleStateEndingInit(void); - -/** - * Updates BATTLE_STATE_ENDING for one frame. - */ -void battleStateEndingUpdate(void); - -/** - * Called when the battle leaves BATTLE_STATE_ENDING. - */ -void battleStateEndingDispose(void); diff --git a/src/dusk/rpg/battle/state/battlestateexecuting.c b/src/dusk/rpg/battle/state/battlestateexecuting.c deleted file mode 100644 index d0045a38..00000000 --- a/src/dusk/rpg/battle/state/battlestateexecuting.c +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestateexecuting.h" -#include "rpg/battle/battle.h" -#include "rpg/cutscene/item/cutsceneitem.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "rpg/cutscene/cutscene.h" -#include "asset/assetfile.h" -#include "ui/overlay/uifatalerror.h" -#include "assert/assert.h" -#include "util/string.h" - - -void battleStateExecutingResolveFighterIndex( - battlefighterpos_t *fighterIndex, - const uint8_t userIndex, - const battlemoveaction_t *action -) { - if(*fighterIndex == BATTLE_FIGHTER_POS_TARGET) { - *fighterIndex = action->targets[0]; - } else if(*fighterIndex == BATTLE_FIGHTER_POS_USER) { - *fighterIndex = userIndex; - } -} - -void battleStateExecutingInit(void) { - cutsceneitem_t items[BATTLE_STATE_EXECUTING_ITEMS_MAX]; - uint8_t itemsCount = 0; - - // For each action (just queued during selection). actionsPending is - // indexed by fighter position, not sequentially, so scan every position - // and stop early once every pending action has been processed. - uint8_t processed = 0; - for( - uint8_t i = 0; - i < BATTLE_FIGHTER_POS_COUNT && processed < BATTLE.actionsPendingCount; - i++ - ) { - // Get action. - battlemoveaction_t *action = &BATTLE.actionsPending[i]; - if(action->move == BATTLE_MOVE_ID_NULL) continue; - processed++; - - assertTrue(action->move < BATTLE_MOVE_COUNT, "Invalid move id"); - - // Get ability. - battleability_t *ability = &BATTLE_MOVES[action->move]; - assertStrLenMin(ability->name, 1, "Invalid move name"); - - // Determine cutscene path - char_t path[ASSET_FILE_NAME_MAX]; - stringFormat( - path, ASSET_FILE_NAME_MAX - 1, - "cutscenes/battle/moves/%s.jsonc", ability->name - ); - - // Load and parse the move's cutscene straight into the tail of items. - const uint8_t moveItemsStart = itemsCount; - cutscene_t scene; - errorret_t result = cutsceneLoadParse( - path, &scene, &items[itemsCount], - BATTLE_STATE_EXECUTING_ITEMS_MAX - itemsCount - ); - if(errorIsNotOk(result)) { - errorCatch(errorPrint(result)); - uiFatalErrorOpen(NULL); - continue; - } - itemsCount += scene.itemCount; - - // Resolve BATTLE_FIGHTER_POS_TARGET/USER sentinels against this - // action, now that its user/target fighters are known. - for(uint8_t j = moveItemsStart; j < itemsCount; j++) { - cutsceneitem_t *moveItem = &items[j]; - if(moveItem->type == CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_DAMAGE) { - battleStateExecutingResolveFighterIndex( - &moveItem->battleFighterDamage.fighterIndex, i, action - ); - } else if(moveItem->type == CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_HEAL) { - battleStateExecutingResolveFighterIndex( - &moveItem->battleFighterHeal.fighterIndex, i, action - ); - } - } - - // Insert end of attack cutscene item. - items[itemsCount++] = (cutsceneitem_t){ - .type = CUTSCENE_ITEM_TYPE_BATTLE_POST_MOVE - }; - - // This action has now been resolved - clear it so it isn't - // re-executed (and doesn't block re-selection) next round. - action->move = BATTLE_MOVE_ID_NULL; - } - BATTLE.actionsPendingCount = 0; - - // After all moves completed, transition to pre-round for next round. - items[itemsCount++] = (cutsceneitem_t){ - .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, - .battleSetState = { .state = BATTLE_STATE_PRE_ROUND } - }; - cutsceneSystemInsertItemsNext(items, itemsCount); -} - -void battleStateExecutingUpdate(void) { - -} - -void battleStateExecutingDispose(void) { - -} diff --git a/src/dusk/rpg/battle/state/battlestateexecuting.h b/src/dusk/rpg/battle/state/battlestateexecuting.h deleted file mode 100644 index 8e51d3e9..00000000 --- a/src/dusk/rpg/battle/state/battlestateexecuting.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "rpg/battle/fighter/battlefighterpos.h" -#include "rpg/battle/move/battlemoveaction.h" - -typedef struct { - -} battlestateexecuting_t; - -#define BATTLE_STATE_EXECUTING_ITEMS_MAX 64 - -/** - * Called when the battle enters BATTLE_STATE_EXECUTING. - */ -void battleStateExecutingInit(void); - -/** - * Resolves a BATTLE_FIGHTER_POS_TARGET/BATTLE_FIGHTER_POS_USER sentinel - * (as parsed onto a BATTLE_FIGHTER_DAMAGE/BATTLE_FIGHTER_HEAL item inside - * a move's own cutscene) against the action currently being resolved - * into cutscene items. Leaves fighterIndex untouched if it's already a - * concrete BATTLE.fighters index. - * - * @param fighterIndex The field to resolve in place. - * @param userIndex Index into BATTLE.fighters of the fighter performing - * the action. - * @param action The action being resolved. - */ -void battleStateExecutingResolveFighterIndex( - battlefighterpos_t *fighterIndex, - const uint8_t userIndex, - const battlemoveaction_t *action -); - -/** - * Updates BATTLE_STATE_EXECUTING for one frame. - */ -void battleStateExecutingUpdate(void); - -/** - * Called when the battle leaves BATTLE_STATE_EXECUTING. - */ -void battleStateExecutingDispose(void); diff --git a/src/dusk/rpg/battle/state/battlestatefled.c b/src/dusk/rpg/battle/state/battlestatefled.c deleted file mode 100644 index 441072a2..00000000 --- a/src/dusk/rpg/battle/state/battlestatefled.c +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestatefled.h" -#include "rpg/cutscene/item/cutsceneitem.h" -#include "rpg/cutscene/cutscenesystem.h" - -void battleStateFledInit(void) { - const cutsceneitem_t items[] = { - { - .type = CUTSCENE_ITEM_TYPE_TEXT, - .text = { .text = "Got away safely!" } - }, - { - .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, - .battleSetState = { .state = BATTLE_STATE_ENDING } - } - }; - cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); -} - -void battleStateFledUpdate(void) { - -} - -void battleStateFledDispose(void) { - -} diff --git a/src/dusk/rpg/battle/state/battlestatefled.h b/src/dusk/rpg/battle/state/battlestatefled.h deleted file mode 100644 index f482b6be..00000000 --- a/src/dusk/rpg/battle/state/battlestatefled.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - -} battlestatefled_t; - -/** - * Called when the battle enters BATTLE_STATE_FLED. Queues a textbox - * announcing the escape, then transitions to BATTLE_STATE_ENDING once - * dismissed. - */ -void battleStateFledInit(void); - -/** - * Updates BATTLE_STATE_FLED for one frame. - */ -void battleStateFledUpdate(void); - -/** - * Called when the battle leaves BATTLE_STATE_FLED. - */ -void battleStateFledDispose(void); diff --git a/src/dusk/rpg/battle/state/battlestatelost.c b/src/dusk/rpg/battle/state/battlestatelost.c deleted file mode 100644 index 334d8d87..00000000 --- a/src/dusk/rpg/battle/state/battlestatelost.c +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestatelost.h" -#include "rpg/cutscene/item/cutsceneitem.h" -#include "rpg/cutscene/cutscenesystem.h" - -void battleStateLostInit(void) { - const cutsceneitem_t items[] = { - { - .type = CUTSCENE_ITEM_TYPE_TEXT, - .text = { .text = "You were defeated..." } - }, - { - .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, - .battleSetState = { .state = BATTLE_STATE_ENDING } - } - }; - cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); -} - -void battleStateLostUpdate(void) { - -} - -void battleStateLostDispose(void) { - -} diff --git a/src/dusk/rpg/battle/state/battlestatelost.h b/src/dusk/rpg/battle/state/battlestatelost.h deleted file mode 100644 index 7a20dca7..00000000 --- a/src/dusk/rpg/battle/state/battlestatelost.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - -} battlestatelost_t; - -/** - * Called when the battle enters BATTLE_STATE_LOST. Queues a textbox - * announcing the loss, then transitions to BATTLE_STATE_ENDING once - * dismissed. - */ -void battleStateLostInit(void); - -/** - * Updates BATTLE_STATE_LOST for one frame. - */ -void battleStateLostUpdate(void); - -/** - * Called when the battle leaves BATTLE_STATE_LOST. - */ -void battleStateLostDispose(void); diff --git a/src/dusk/rpg/battle/state/battlestateopening.c b/src/dusk/rpg/battle/state/battlestateopening.c deleted file mode 100644 index eb2a2752..00000000 --- a/src/dusk/rpg/battle/state/battlestateopening.c +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestateopening.h" -#include "rpg/cutscene/item/cutsceneitem.h" -#include "rpg/cutscene/cutscenesystem.h" - -void battleStateOpeningInit(void) { - const cutsceneitem_t items[] = { - { - .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, - .battleSetState = { .state = BATTLE_STATE_PRE_ROUND } - } - }; - cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); -} - -void battleStateOpeningUpdate(void) { - -} - -void battleStateOpeningDispose(void) { - -} diff --git a/src/dusk/rpg/battle/state/battlestateopening.h b/src/dusk/rpg/battle/state/battlestateopening.h deleted file mode 100644 index 946c4d06..00000000 --- a/src/dusk/rpg/battle/state/battlestateopening.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - -} battlestateopening_t; - -/** - * Called when the battle enters BATTLE_STATE_OPENING. - */ -void battleStateOpeningInit(void); - -/** - * Updates BATTLE_STATE_OPENING for one frame. - */ -void battleStateOpeningUpdate(void); - -/** - * Called when the battle leaves BATTLE_STATE_OPENING. - */ -void battleStateOpeningDispose(void); diff --git a/src/dusk/rpg/battle/state/battlestatepostround.c b/src/dusk/rpg/battle/state/battlestatepostround.c deleted file mode 100644 index 2e713610..00000000 --- a/src/dusk/rpg/battle/state/battlestatepostround.c +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestatepostround.h" - -void battleStatePostRoundInit(void) { - -} - -void battleStatePostRoundUpdate(void) { - -} - -void battleStatePostRoundDispose(void) { - -} diff --git a/src/dusk/rpg/battle/state/battlestatepostround.h b/src/dusk/rpg/battle/state/battlestatepostround.h deleted file mode 100644 index cba3a38f..00000000 --- a/src/dusk/rpg/battle/state/battlestatepostround.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - -} battlestatepostround_t; - -/** - * Called when the battle enters BATTLE_STATE_POST_ROUND. - */ -void battleStatePostRoundInit(void); - -/** - * Updates BATTLE_STATE_POST_ROUND for one frame. - */ -void battleStatePostRoundUpdate(void); - -/** - * Called when the battle leaves BATTLE_STATE_POST_ROUND. - */ -void battleStatePostRoundDispose(void); diff --git a/src/dusk/rpg/battle/state/battlestatepreround.c b/src/dusk/rpg/battle/state/battlestatepreround.c deleted file mode 100644 index 7b85ece7..00000000 --- a/src/dusk/rpg/battle/state/battlestatepreround.c +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestatepreround.h" -#include "rpg/battle/battle.h" -#include "rpg/cutscene/item/cutsceneitem.h" -#include "rpg/cutscene/cutscenesystem.h" - -void battleStatePreRoundInit(void) { - // TODO: Determine selection order and who gets to act. - BATTLE.fighterOrderCount = 0; - for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) { - if(BATTLE.fighters[i] == NULL) continue; - if(!battleFighterIsAlive(BATTLE.fighters[i])) continue; - BATTLE.fighterOrder[BATTLE.fighterOrderCount++] = i; - } - - // Queue up the set state and wait for selection to stop cutscene ending. - cutsceneitem_t items[] = { - { - .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, - .battleSetState = { .state = BATTLE_STATE_SELECTION } - }, - { - .type = CUTSCENE_ITEM_TYPE_BATTLE_WAIT_SELECTION - } - }; - cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); -} - -void battleStatePreRoundUpdate(void) { - -} - -void battleStatePreRoundDispose(void) { - -} diff --git a/src/dusk/rpg/battle/state/battlestatepreround.h b/src/dusk/rpg/battle/state/battlestatepreround.h deleted file mode 100644 index 0f26afe9..00000000 --- a/src/dusk/rpg/battle/state/battlestatepreround.h +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - -} battlestatepreround_t; - -/** - * Called when the battle enters BATTLE_STATE_PRE_ROUND. - */ -void battleStatePreRoundInit(void); - -/** - * Updates BATTLE_STATE_PRE_ROUND for one frame. - */ -void battleStatePreRoundUpdate(void); - -/** - * Called when the battle leaves BATTLE_STATE_PRE_ROUND. - */ -void battleStatePreRoundDispose(void); diff --git a/src/dusk/rpg/battle/state/battlestateselection.c b/src/dusk/rpg/battle/state/battlestateselection.c deleted file mode 100644 index 8bd2e2f8..00000000 --- a/src/dusk/rpg/battle/state/battlestateselection.c +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestateselection.h" -#include "rpg/battle/battle.h" -#include "assert/assert.h" - -void battleStateSelectionInit(void) { - BATTLE.stateData.selection.selectionIndex = 0; -} - -bool_t battleStateSelectionFighterHasDecided(const uint8_t fighterIndex) { - assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "Fighter OOB"); - return BATTLE.actionsPending[fighterIndex].move != BATTLE_MOVE_ID_NULL; -} - -bool_t battleStateSelectionFighterNeedsDecision(const uint8_t fighterIndex) { - assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "Fighter OOB"); - - battlefighter_t *fighter = BATTLE.fighters[fighterIndex]; - if(fighter == NULL) return false; - if(!battleFighterIsAlive(fighter)) return false; - - return !battleStateSelectionFighterHasDecided(fighterIndex); -} - -bool_t battleStateSelectionIsComplete(void) { - return BATTLE.stateData.selection.selectionIndex >= BATTLE.fighterOrderCount; -} - -void battleStateSelectionAiDecide(const uint8_t fighterIndex) { - battlefighter_t *fighter = BATTLE.fighters[fighterIndex]; - const battlefighterteam_t enemyTeam = - fighter->team == BATTLE_FIGHTER_TEAM_ALLY ? - BATTLE_FIGHTER_TEAM_ENEMY : BATTLE_FIGHTER_TEAM_ALLY; - - // Simple placeholder AI: attack the first living fighter on the - // opposing team. No move variety or target scoring yet. - for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) { - battlefighter_t *candidate = BATTLE.fighters[i]; - if(candidate == NULL) continue; - if(candidate->team != enemyTeam) continue; - if(!battleFighterIsAlive(candidate)) continue; - - battleStateSelectionQueueAttack(fighterIndex, i); - return; - } -} - -void battleStateSelectionUpdate(void) { - battlestateselection_t *state = &BATTLE.stateData.selection; - - // Are we still waiting for a selection? - while(state->selectionIndex < BATTLE.fighterOrderCount) { - const uint8_t fighterIndex = BATTLE.fighterOrder[state->selectionIndex]; - - // Is this fighter still owed a decision? - if(!battleStateSelectionFighterNeedsDecision(fighterIndex)) { - state->selectionIndex++; - continue; - } - - // Players will act on their own. - battlefighter_t *fighter = BATTLE.fighters[fighterIndex]; - if(fighter->controller != BATTLE_FIGHTER_CONTROLLER_AI) return; - - battleStateSelectionAiDecide(fighterIndex); - state->selectionIndex++; - } -} - -void battleStateSelectionDispose(void) { - -} - -void battleStateSelectionQueueAttack( - const uint8_t fighterIndex, - const uint8_t targetIndex -) { - assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "Fighter OOB"); - assertTrue(targetIndex < BATTLE_FIGHTER_POS_COUNT, "Target OOB"); - assertNotNull(BATTLE.fighters[fighterIndex], "Fighter cannot be NULL"); - assertNotNull(BATTLE.fighters[targetIndex], "Target cannot be NULL"); - - const battlemoveid_t move = battleMoveGetIdByName("slash"); - assertTrue(move != BATTLE_MOVE_ID_NULL, "Missing 'slash' battle move"); - - battlemoveaction_t *action = &BATTLE.actionsPending[fighterIndex]; - if(action->move == BATTLE_MOVE_ID_NULL) BATTLE.actionsPendingCount++; - action->move = move; - action->targets[0] = targetIndex; - action->user = BATTLE.fighters[fighterIndex]; -} - -battlefighter_t *battleStateSelectionGetCurrentFighter(void) { - if(BATTLE.state != BATTLE_STATE_SELECTION) return NULL; - - const uint8_t index = BATTLE.stateData.selection.selectionIndex; - if(index >= BATTLE.fighterOrderCount) return NULL; - - return BATTLE.fighters[BATTLE.fighterOrder[index]]; -} - -void battleStateSelectionPlayerAttack(const uint8_t targetIndex) { - battlefighter_t *fighter = battleStateSelectionGetCurrentFighter(); - if(fighter == NULL) return; - - const uint8_t selectionIndex = BATTLE.stateData.selection.selectionIndex; - const uint8_t fighterIndex = BATTLE.fighterOrder[selectionIndex]; - battleStateSelectionQueueAttack(fighterIndex, targetIndex); - BATTLE.stateData.selection.selectionIndex++; -} - -void battleStateSelectionGoBack(void) { - battlestateselection_t *state = &BATTLE.stateData.selection; - uint8_t index = state->selectionIndex; - - while(index > 0) { - index--; - - const uint8_t fighterIndex = BATTLE.fighterOrder[index]; - battlefighter_t *fighter = BATTLE.fighters[fighterIndex]; - if(fighter == NULL) continue; - if(fighter->controller != BATTLE_FIGHTER_CONTROLLER_PLAYER) continue; - if(!battleStateSelectionFighterHasDecided(fighterIndex)) continue; - - battlemoveaction_t *action = &BATTLE.actionsPending[fighterIndex]; - action->move = BATTLE_MOVE_ID_NULL; - BATTLE.actionsPendingCount--; - - state->selectionIndex = index; - return; - } -} - -void battleStateSelectionPlayerFlee(void) { - battlefighter_t *fighter = battleStateSelectionGetCurrentFighter(); - if(fighter == NULL) return; - - BATTLE.result = BATTLE_RESULT_FLED; - battleSetState(BATTLE_STATE_FLED); -} \ No newline at end of file diff --git a/src/dusk/rpg/battle/state/battlestateselection.h b/src/dusk/rpg/battle/state/battlestateselection.h deleted file mode 100644 index 3a43439e..00000000 --- a/src/dusk/rpg/battle/state/battlestateselection.h +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/battle/fighter/battlefighter.h" - -typedef struct { - // Cursor into BATTLE.fighters (by position index) for the fighter - // currently deciding a move this round. - uint8_t selectionIndex; -} battlestateselection_t; - -/** - * Called when the battle enters BATTLE_STATE_SELECTION. - */ -void battleStateSelectionInit(void); - -/** - * Updates BATTLE_STATE_SELECTION for one frame: auto-decides (see - * battleStateSelectionAiDecide) for every AI-controlled fighter the - * cursor passes, stopping once it reaches a player-controlled fighter - * awaiting input (see battleStateSelectionGetCurrentFighter). Once every - * position has decided, BATTLE_WAIT_SELECTION (see - * cutsceneBattleWaitSelectionUpdate) queues the move to - * BATTLE_STATE_EXECUTING. - */ -void battleStateSelectionUpdate(void); - -/** - * Called when the battle leaves BATTLE_STATE_SELECTION. - */ -void battleStateSelectionDispose(void); - -/** - * Checks whether the fighter at fighterIndex has already queued a move - * this round. - * - * @param fighterIndex Index into BATTLE.fighters to check. - * @return True if an action is already pending for this fighter. - */ -bool_t battleStateSelectionFighterHasDecided(const uint8_t fighterIndex); - -/** - * Checks whether the fighter at fighterIndex is a live, undecided - * candidate that BATTLE_STATE_SELECTION should still be deciding a move - * for. - * - * @param fighterIndex Index into BATTLE.fighters to check. - * @return True if the fighter is alive and has no move queued yet. - */ -bool_t battleStateSelectionFighterNeedsDecision(const uint8_t fighterIndex); - -/** - * Decides and queues a move for an AI-controlled fighter: currently - * always a basic attack against the first living fighter on the - * opposing team (see battleStateSelectionQueueAttack). No-ops if there's - * no living target (the battle should already be over by then via - * BATTLE_POST_MOVE). - * - * @param fighterIndex Index into BATTLE.fighters of the deciding fighter. - */ -void battleStateSelectionAiDecide(const uint8_t fighterIndex); - -/** - * Checks whether every fighter in BATTLE.fighterOrder has decided a move - * this round. - * - * @return True once the selection cursor has passed every fighter. - */ -bool_t battleStateSelectionIsComplete(void); - -/** - * Queues a basic attack (the "slash" move) from fighterIndex against - * targetIndex. - * - * @param fighterIndex Index into BATTLE.fighters of the deciding fighter. - * @param targetIndex Index into BATTLE.fighters of the target. - */ -void battleStateSelectionQueueAttack( - const uint8_t fighterIndex, - const uint8_t targetIndex -); - -/** - * Returns the fighter currently awaiting a decision in - * BATTLE_STATE_SELECTION. - * - * @return Pointer to the fighter awaiting a decision, or NULL if the - * battle isn't in BATTLE_STATE_SELECTION or every position has - * already decided. - */ -battlefighter_t *battleStateSelectionGetCurrentFighter(void); - -/** - * Submits the currently-selecting fighter's attack against a target, if - * the battle is in BATTLE_STATE_SELECTION and awaiting a decision. Queues - * the move and advances the selection cursor. - * - * @param targetIndex Index into BATTLE.fighters of the target. - */ -void battleStateSelectionPlayerAttack(const uint8_t targetIndex); - -/** - * Ends the battle immediately with BATTLE_STATE_FLED, if the battle is in - * BATTLE_STATE_SELECTION and awaiting a decision. - */ -void battleStateSelectionPlayerFlee(void); - -/** - * Rewinds the selection cursor to the nearest earlier player-controlled - * fighter that has already decided a move this round, clearing its - * pending action so it can be re-decided. AI-decided fighters along the - * way are left alone (skipped over, not rewound onto). No-ops if there's - * no such fighter (e.g. already at the first decision of the round). - */ -void battleStateSelectionGoBack(void); \ No newline at end of file diff --git a/src/dusk/rpg/battle/state/battlestatewon.c b/src/dusk/rpg/battle/state/battlestatewon.c deleted file mode 100644 index 95d09f06..00000000 --- a/src/dusk/rpg/battle/state/battlestatewon.c +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "battlestatewon.h" -#include "rpg/cutscene/item/cutsceneitem.h" -#include "rpg/cutscene/cutscenesystem.h" - -void battleStateWonInit(void) { - const cutsceneitem_t items[] = { - { - .type = CUTSCENE_ITEM_TYPE_TEXT, - .text = { .text = "You won the battle!" } - }, - { - .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, - .battleSetState = { .state = BATTLE_STATE_ENDING } - } - }; - cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); -} - -void battleStateWonUpdate(void) { - -} - -void battleStateWonDispose(void) { - -} diff --git a/src/dusk/rpg/battle/state/battlestatewon.h b/src/dusk/rpg/battle/state/battlestatewon.h deleted file mode 100644 index d9c72dec..00000000 --- a/src/dusk/rpg/battle/state/battlestatewon.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - -} battlestatewon_t; - -/** - * Called when the battle enters BATTLE_STATE_WON. Queues a textbox - * announcing the win, then transitions to BATTLE_STATE_ENDING once - * dismissed. - */ -void battleStateWonInit(void); - -/** - * Updates BATTLE_STATE_WON for one frame. - */ -void battleStateWonUpdate(void); - -/** - * Called when the battle leaves BATTLE_STATE_WON. - */ -void battleStateWonDispose(void); diff --git a/src/dusk/rpg/cutscene/CMakeLists.txt b/src/dusk/rpg/cutscene/CMakeLists.txt deleted file mode 100644 index 7655eb25..00000000 --- a/src/dusk/rpg/cutscene/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutscene.c - cutsceneref.c - cutscenesystem.c -) - -# Subdirs -add_subdirectory(item) \ No newline at end of file diff --git a/src/dusk/rpg/cutscene/cutscene.c b/src/dusk/rpg/cutscene/cutscene.c deleted file mode 100644 index 0b03d15e..00000000 --- a/src/dusk/rpg/cutscene/cutscene.c +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscene.h" -#include "item/json/cutscenejsonpauseflags.h" -#include "util/memory.h" -#include "assert/assert.h" -#include "asset/asset.h" -#include "asset/loader/assetloader.h" - -errorret_t cutsceneParseItem( - yyjson_val *itemObj, - cutsceneitem_t *item, - const uint8_t index -) { - assertNotNull(itemObj, "Item object cannot be NULL"); - assertNotNull(item, "Item cannot be NULL"); - - if(!yyjson_is_obj(itemObj)) { - errorThrow("Cutscene item %u is not an object", (uint32_t)index); - } - - const char_t *typeName = yyjson_get_str(yyjson_obj_get(itemObj, "type")); - if(!cutsceneItemLookupType(typeName, &item->type)) { - errorThrow( - "Cutscene item %u: missing/unsupported item type %s", - (uint32_t)index, typeName - ); - } - - cutsceneitemloadcallback_t *load = CUTSCENE_ITEM_CALLBACKS[item->type].load; - if(load == NULL) { - errorThrow( - "Cutscene item %u: unhandled item type %u", - (uint32_t)index, (uint32_t)item->type - ); - } - - errorret_t result = load(itemObj, item); - if(errorIsNotOk(result)) errorChain(result); - - errorOk(); -} - -size_t cutsceneCountItems(yyjson_doc *doc) { - assertNotNull(doc, "Doc cannot be NULL"); - - yyjson_val *root = yyjson_doc_get_root(doc); - yyjson_val *itemsArr = yyjson_obj_get(root, "items"); - if(!yyjson_is_arr(itemsArr)) return 0; - return yyjson_arr_size(itemsArr); -} - -errorret_t cutsceneParseDoc( - yyjson_doc *doc, - cutscene_t *scene, - cutsceneitem_t *items, - size_t itemsMax -) { - assertNotNull(doc, "Doc cannot be NULL"); - assertNotNull(scene, "Scene cannot be NULL"); - assertNotNull(items, "Items cannot be NULL"); - assertTrue(itemsMax > 0, "Items max cannot be 0"); - - yyjson_val *root = yyjson_doc_get_root(doc); - yyjson_val *itemsArr = yyjson_obj_get(root, "items"); - if(!yyjson_is_arr(itemsArr)) { - errorThrow("Cutscene JSON is missing an 'items' array"); - } - - size_t itemCount = yyjson_arr_size(itemsArr); - if(itemCount > itemsMax) { - errorThrow("Cutscene has more items than its target buffer allows"); - } - - cutscenepause_t pause = CUTSCENE_PAUSE_DEFAULT; - yyjson_val *pauseArr = yyjson_obj_get(root, "pause"); - if(yyjson_is_arr(pauseArr)) { - if(!cutsceneJsonParsePauseFlags(pauseArr, &pause)) { - errorThrow("Cutscene JSON has an invalid 'pause' flag"); - } - } - - memoryZero(items, itemCount * sizeof(cutsceneitem_t)); - - yyjson_arr_iter itemIter = yyjson_arr_iter_with(itemsArr); - yyjson_val *itemVal; - uint8_t index = 0; - while((itemVal = yyjson_arr_iter_next(&itemIter)) != NULL) { - errorret_t itemResult = cutsceneParseItem(itemVal, &items[index], index); - if(errorIsNotOk(itemResult)) errorChain(itemResult); - index++; - } - - scene->itemCount = (uint8_t)itemCount; - scene->itemsMax = itemsMax; - scene->pause = pause; - - errorOk(); -} - -void cutsceneAppendAt( - cutscene_t *scene, - const uint8_t atIndex, - const cutsceneitem_t *items, - const uint8_t count -) { - assertNotNull(scene, "Scene cannot be NULL"); - assertNotNull(items, "Items cannot be NULL"); - assertTrue(count > 0, "Count cannot be 0"); - assertTrue(atIndex <= scene->itemCount, "atIndex out of bounds"); - assertTrue( - (size_t)scene->itemCount + count <= scene->itemsMax, - "Cutscene item append exceeds itemsMax" - ); - - const size_t tailCount = (size_t)scene->itemCount - atIndex; - if(tailCount > 0) { - memoryMove( - &scene->items[atIndex + count], - &scene->items[atIndex], - tailCount * sizeof(cutsceneitem_t) - ); - } - memoryCopy(&scene->items[atIndex], items, count * sizeof(cutsceneitem_t)); - scene->itemCount += count; -} - -void cutsceneAppend( - cutscene_t *scene, - const cutsceneitem_t *items, - const uint8_t count -) { - cutsceneAppendAt(scene, scene->itemCount, items, count); -} - -void cutsceneAppendNext( - cutscene_t *scene, - const uint8_t currentIndex, - const cutsceneitem_t *items, - const uint8_t count -) { - cutsceneAppendAt(scene, (uint8_t)(currentIndex + 1), items, count); -} - -errorret_t cutsceneLoadParse( - const char_t *path, - cutscene_t *scene, - cutsceneitem_t *items, - const size_t itemsMax -) { - assertNotNull(path, "Path cannot be NULL"); - assertNotNull(scene, "Scene cannot be NULL"); - assertNotNull(items, "Items cannot be NULL"); - - // Lock the asset, require it loaded. - assetentry_t *entry = assetLock(path, ASSET_LOADER_TYPE_JSON, NULL); - errorret_t result = assetRequireLoaded(entry); - if(errorIsNotOk(result)) { - assetUnlockEntry(entry); - errorChain(result); - } - - // Parse the loaded JSON - errorret_t parseResult = cutsceneParseDoc( - entry->data.json, scene, items, itemsMax - ); - - // Free up the asset entry. - assetUnlockEntry(entry); - errorChain(parseResult); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/cutscene.h b/src/dusk/rpg/cutscene/cutscene.h deleted file mode 100644 index 07939299..00000000 --- a/src/dusk/rpg/cutscene/cutscene.h +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitem.h" -#include "rpg/cutscene/cutscenepause.h" - -typedef struct cutscene_s { - cutsceneitem_t *items; - uint8_t itemCount; - // Capacity of items - see cutsceneAppend/cutsceneAppendAt/ - // cutsceneAppendNext, which assert against this rather than taking a - // capacity parameter of their own. Set once, either by cutsceneParseDoc - // (to whatever itemsMax it was given) or by whoever declares a - // C-authored cutscene_t directly (to items' true array capacity, which - // may be larger than itemCount to leave room for a later append/insert). - size_t itemsMax; - cutscenepause_t pause; -} cutscene_t; - -/** - * Parses one cutscene item object into item. Every field/enum name is - * validated as real (untrusted, authored) file content - failures are - * real errors, not asserts. Dispatches to the matching type's Load - * callback (see CUTSCENE_ITEM_CALLBACKS in item/cutsceneitem.h) - the - * per-type parsing logic itself lives there, colocated with that type's - * runtime behavior. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @param index Index of this item within the cutscene, for error messages. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneParseItem( - yyjson_val *itemObj, - cutsceneitem_t *item, - const uint8_t index -); - -/** - * Counts how many entries are in doc's top-level "items" array, without - * parsing any of them - e.g. so a caller that wants to malloc its own - * items array instead of using a fixed one could size it first (we don't - * do that ourselves, but the option stays open). - * - * @param doc Already-parsed JSON document. - * @return Number of entries in "items", or 0 if it's missing/not an array. - */ -size_t cutsceneCountItems(yyjson_doc *doc); - -/** - * Parses doc's "items" (and optional "pause") into items, and updates - * scene's itemCount/itemsMax/pause - scene->items is left untouched, since - * the caller already points it at items. Purely a parser: doesn't touch - * the asset system, doesn't lock/load anything, and doesn't take - * ownership of doc - the caller is responsible for both loading doc - * beforehand and for its lifetime afterward (though as of cutscene items - * owning their own string fields by value, nothing parsed here actually - * needs doc to survive past this call returning). - * - * @param doc Already-parsed JSON document to read from. - * @param scene Destination scene - itemCount/itemsMax/pause are - * overwritten. - * @param items Destination item array, capacity itemsMax. - * @param itemsMax Capacity of items. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneParseDoc( - yyjson_doc *doc, - cutscene_t *scene, - cutsceneitem_t *items, - size_t itemsMax -); - -/** - * Inserts items into scene->items starting at atIndex, growing - * scene->itemCount. Every existing item from atIndex onward shifts right - * by count to make room. Asserts if this would exceed scene->itemsMax. - * - * @param scene Cutscene to insert into. - * @param atIndex Index the first inserted item should end up at - must be - * <= scene->itemCount. - * @param items Items to insert, copied in order. - * @param count Number of entries in items. - */ -void cutsceneAppendAt( - cutscene_t *scene, - const uint8_t atIndex, - const cutsceneitem_t *items, - const uint8_t count -); - -/** - * Same as cutsceneAppendAt, but always inserts at the end of - * scene->items. - * - * @param scene Cutscene to append to. - * @param items Items to append, copied in order. - * @param count Number of entries in items. - */ -void cutsceneAppend( - cutscene_t *scene, - const cutsceneitem_t *items, - const uint8_t count -); - -/** - * Same as cutsceneAppendAt, but inserts right after currentIndex (i.e. at - * currentIndex + 1) instead of at a literal index - e.g. pass - * CUTSCENE_SYSTEM.currentItem to make these items run immediately after - * the currently-executing one. currentIndex may be 0xFF (nothing has run - * yet), which wraps to insert at index 0, matching that sentinel's usual - * meaning elsewhere in the cutscene system. - * - * @param scene Cutscene to insert into. - * @param currentIndex Index to insert after. - * @param items Items to insert, copied in order. - * @param count Number of entries in items. - */ -void cutsceneAppendNext( - cutscene_t *scene, - const uint8_t currentIndex, - const cutsceneitem_t *items, - const uint8_t count -); - -/** - * Loads a cutscene JSON asset by path and parses it into scene/items (see - * cutsceneParseDoc) - the common "lock the asset, require it loaded, - * parse its 'items' array, unlock it again" sequence shared by every - * place that resolves a cutscene by file path (cutsceneSystemLoad, - * cutsceneCutsceneResolve, cutsceneInsertResolveAndSplice, - * battleStateExecutingInit, cutsceneSystemGoTo's reload). Purely propagates the - * error on failure (like cutsceneParseDoc) - this is core cutscene logic, - * not UI, so it doesn't reach for uiFatalErrorOpen itself; each caller - * decides how to surface the failure (typically errorCatch(errorPrint()) - * + uiFatalErrorOpen(NULL), same as any other asset load failure). - * - * @param path Asset path of the cutscene JSON file to load. - * @param scene Destination scene - itemCount/itemsMax/pause overwritten. - * @param items Destination item array, capacity itemsMax. - * @param itemsMax Capacity of items. - * @return Error code indicating success or failure of the load/parse. - */ -errorret_t cutsceneLoadParse( - const char_t *path, - cutscene_t *scene, - cutsceneitem_t *items, - const size_t itemsMax -); diff --git a/src/dusk/rpg/cutscene/cutscenepause.h b/src/dusk/rpg/cutscene/cutscenepause.h deleted file mode 100644 index e9aad3fd..00000000 --- a/src/dusk/rpg/cutscene/cutscenepause.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef uint8_t cutscenepause_t; - -#define CUTSCENE_PAUSE_NONE ((cutscenepause_t)0) -#define CUTSCENE_PAUSE_NPC ((cutscenepause_t)(1 << 0)) -#define CUTSCENE_PAUSE_PLAYER ((cutscenepause_t)(1 << 1)) -#define CUTSCENE_PAUSE_WORLD ((cutscenepause_t)(1 << 2)) -#define CUTSCENE_PAUSE_BATTLE ((cutscenepause_t)(1 << 3)) - -#define CUTSCENE_PAUSE_DEFAULT ((cutscenepause_t)( \ - CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER \ -)) - -#define CUTSCENE_PAUSE_ALL ((cutscenepause_t)( \ - CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_PLAYER | CUTSCENE_PAUSE_WORLD | \ - CUTSCENE_PAUSE_BATTLE \ -)) diff --git a/src/dusk/rpg/cutscene/cutsceneref.c b/src/dusk/rpg/cutscene/cutsceneref.c deleted file mode 100644 index 095965e9..00000000 --- a/src/dusk/rpg/cutscene/cutsceneref.c +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutsceneref.h" -#include "assert/assert.h" - -void cutsceneRefReset( - cutsceneref_t *refs, - entity_t *interact, - entity_t *interacted -) { - assertNotNull(refs, "Refs cannot be null"); - - refs->entityInteract = interact; - refs->entityInteracted = interacted; - refs->entityLastCreated = NULL; - refs->entityLastRef = NULL; - refs->areaLastCreated = CUTSCENE_REF_AREA_LAST_CREATED; - refs->textMiniLastCreated = CUTSCENE_REF_TEXT_MINI_LAST_CREATED; -} diff --git a/src/dusk/rpg/cutscene/cutsceneref.h b/src/dusk/rpg/cutscene/cutsceneref.h deleted file mode 100644 index 7197460c..00000000 --- a/src/dusk/rpg/cutscene/cutsceneref.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct entity_s entity_t; - -#define CUTSCENE_REF_AREA_LAST_CREATED ((uint8_t)0xFF) -#define CUTSCENE_REF_TEXT_MINI_LAST_CREATED ((uint8_t)0xFA) - -typedef struct { - entity_t *entityInteract; - entity_t *entityInteracted; - entity_t *entityLastCreated; - entity_t *entityLastRef; - uint8_t areaLastCreated; - uint8_t textMiniLastCreated; -} cutsceneref_t; - -/** - * Resets refs to an empty state. - * - * @param refs Refs to reset. - * @param interact The entity that initiated the interaction, or NULL. - * @param interacted The entity being interacted with, or NULL. - */ -void cutsceneRefReset( - cutsceneref_t *refs, - entity_t *interact, - entity_t *interacted -); diff --git a/src/dusk/rpg/cutscene/cutscenesystem.c b/src/dusk/rpg/cutscene/cutscenesystem.c deleted file mode 100644 index a68c8cdf..00000000 --- a/src/dusk/rpg/cutscene/cutscenesystem.c +++ /dev/null @@ -1,358 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenesystem.h" -#include "rpg/entity/entity.h" -#include "util/memory.h" -#include "util/string.h" -#include "assert/assert.h" -#include "ui/overlay/uifatalerror.h" -#include "save/slot/saveslot.h" - -cutscenesystem_t CUTSCENE_SYSTEM; - -void cutsceneSystemInit() { - memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t)); - CUTSCENE_SYSTEM.loadedScene.items = CUTSCENE_SYSTEM.loadedItems; - CUTSCENE_SYSTEM.loadedScene.itemsMax = CUTSCENE_LOADED_ITEMS_MAX; -} - -void cutsceneSystemUpdate() { - if(CUTSCENE_SYSTEM.scene == NULL) return; - - const cutsceneitem_t *item = cutsceneSystemGetCurrentItem(); - if(cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data)) cutsceneSystemNext(); -} - -void cutsceneSystemDispose() { - CUTSCENE_SYSTEM.scene = NULL; - CUTSCENE_SYSTEM.currentItem = CUTSCENE_CURRENT_ITEM_NONE; - CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE; - cutsceneRefReset(&CUTSCENE_SYSTEM.refs, NULL, NULL); - CUTSCENE_SYSTEM.textCache[0] = '\0'; - CUTSCENE_SYSTEM.onComplete = NULL; - CUTSCENE_SYSTEM.loadedFile[0] = '\0'; -} - -void cutsceneSystemPrepare( - cutscene_t *cutscene, - entity_t *interact, - entity_t *interacted -) { - assertNotNull(cutscene, "Cutscene cannot be NULL"); - - CUTSCENE_SYSTEM.scene = cutscene; - CUTSCENE_SYSTEM.pause = cutscene->pause; - cutsceneRefReset(&CUTSCENE_SYSTEM.refs, interact, interacted); - CUTSCENE_SYSTEM.textCache[0] = '\0'; - CUTSCENE_SYSTEM.onComplete = NULL; - - // cutsceneNext will cause overflow to index 0 (intentional). - CUTSCENE_SYSTEM.currentItem = CUTSCENE_CURRENT_ITEM_NONE; - - // Clear loaded file if cutscene not running from file. - if(cutscene != &CUTSCENE_SYSTEM.loadedScene) { - CUTSCENE_SYSTEM.loadedFile[0] = '\0'; - } -} - -void cutsceneSystemStartCutscene(cutscene_t *cutscene) { - cutsceneSystemStartCutsceneWith(cutscene, NULL, NULL); -} - -void cutsceneSystemStartCutsceneWith( - cutscene_t *cutscene, - entity_t *interact, - entity_t *interacted -) { - assertNotNull(cutscene, "Cutscene cannot be NULL"); - assertTrue( - cutscene->itemCount <= cutscene->itemsMax, - "Cutscene itemCount exceeds itemsMax" - ); - - // Prep cutscene, save it to the save slot, and advance, aka overflow to 0. - cutsceneSystemPrepare(cutscene, interact, interacted); - saveSlotSetCurrentCutscene(CUTSCENE_SYSTEM.loadedFile); - cutsceneSystemNext(); -} - -void cutsceneSystemStartCutsceneAndGoToMarker( - cutscene_t *cutscene, - const char_t *marker -) { - assertNotNull(marker, "Marker cannot be NULL"); - - // Restarts the cutscene - cutsceneSystemPrepare(cutscene, NULL, NULL); - cutsceneSystemGoTo(marker); -} - -void cutsceneSystemInsertItemsNext( - const cutsceneitem_t *items, - const size_t itemCount -) { - assertNotNull(items, "Items cannot be NULL"); - assertNotNull( - CUTSCENE_SYSTEM.scene, - "cutsceneSystemInsertItemsNext called with no cutscene running" - ); - cutsceneAppendNext( - CUTSCENE_SYSTEM.scene, CUTSCENE_SYSTEM.currentItem, items, itemCount - ); -} - -void cutsceneSystemInsertItems( - const cutsceneitem_t *items, - const size_t itemCount -) { - cutsceneSystemInsertItemsNext(items, itemCount); - cutsceneSystemNext(); -} - -void cutsceneSystemInsertCutscene(const cutscene_t *cutscene) { - assertNotNull(cutscene, "Cutscene cannot be NULL"); - cutsceneSystemInsertItems(cutscene->items, cutscene->itemCount); -} - -void cutsceneSystemClearAfter(const uint8_t index) { - assertNotNull( - CUTSCENE_SYSTEM.scene, - "cutsceneSystemClearAfter called with no cutscene running" - ); - assertTrue( - index < CUTSCENE_SYSTEM.scene->itemCount, - "cutsceneSystemClearAfter index out of range" - ); - - CUTSCENE_SYSTEM.scene->itemCount = index + 1; -} - -void cutsceneSystemLoad(const char_t *file) { - assertNotNull(file, "File cannot be NULL"); - - // Take a copy of file, as it may be a part of the cutscene itself. - char_t fileCopy[ASSET_FILE_NAME_MAX]; - stringCopy(fileCopy, file, ASSET_FILE_NAME_MAX - 1); - - // Load the cutscene asset, parse it, and start it. - errorret_t result = cutsceneLoadParse( - fileCopy, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, - CUTSCENE_LOADED_ITEMS_MAX - ); - if(errorIsNotOk(result)) { - errorCatch(errorPrint(result)); - uiFatalErrorOpen(NULL); - return; - } - - // Store the loaded file so we can restart if needed. - stringCopy(CUTSCENE_SYSTEM.loadedFile, fileCopy, ASSET_FILE_NAME_MAX - 1); - cutsceneSystemStartCutscene(&CUTSCENE_SYSTEM.loadedScene); -} - -void cutsceneRestart(void) { - assertNotNull( - CUTSCENE_SYSTEM.scene, "cutsceneRestart called with no cutscene running" - ); - assertTrue( - CUTSCENE_SYSTEM.scene == &CUTSCENE_SYSTEM.loadedScene, - "cutsceneRestart called with a cutscene not loaded from file" - ); - assertTrue( - CUTSCENE_SYSTEM.loadedFile[0] != '\0', - "cutsceneRestart called with no loaded file" - ); - - // Copy the refs and callbacks - cutscenecallback_t onComplete = CUTSCENE_SYSTEM.onComplete; - cutsceneref_t refs = CUTSCENE_SYSTEM.refs; - - // Reload the file. - char_t file[ASSET_FILE_NAME_MAX]; - stringCopy(file, CUTSCENE_SYSTEM.loadedFile, ASSET_FILE_NAME_MAX - 1); - cutsceneSystemLoad(file); - - // Restore the refs and callbacks. - CUTSCENE_SYSTEM.onComplete = onComplete; - CUTSCENE_SYSTEM.refs = refs; -} - -void cutsceneSystemSetOnComplete(cutscenecallback_t onComplete) { - assertNotNull( - CUTSCENE_SYSTEM.scene, - "cutsceneSystemSetOnComplete called with no cutscene running" - ); - CUTSCENE_SYSTEM.onComplete = onComplete; -} - -entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex) { - entity_t *entity; - - if(entityIndex == CUTSCENE_ENTITY_INTERACT) { - assertNotNull( - CUTSCENE_SYSTEM.refs.entityInteract, - "CUTSCENE_ENTITY_INTERACT used but no interact entity is set" - ); - entity = CUTSCENE_SYSTEM.refs.entityInteract; - } else if(entityIndex == CUTSCENE_ENTITY_INTERACTED) { - assertNotNull( - CUTSCENE_SYSTEM.refs.entityInteracted, - "CUTSCENE_ENTITY_INTERACTED used but no interacted entity is set" - ); - entity = CUTSCENE_SYSTEM.refs.entityInteracted; - } else if(entityIndex == CUTSCENE_ENTITY_LAST_CREATED) { - assertNotNull( - CUTSCENE_SYSTEM.refs.entityLastCreated, - "CUTSCENE_ENTITY_LAST_CREATED used but no entity has been created" - ); - entity = CUTSCENE_SYSTEM.refs.entityLastCreated; - } else if(entityIndex == CUTSCENE_ENTITY_LAST_REF) { - assertNotNull( - CUTSCENE_SYSTEM.refs.entityLastRef, - "CUTSCENE_ENTITY_LAST_REF used but no entity has been referenced" - ); - entity = CUTSCENE_SYSTEM.refs.entityLastRef; - } else { - assertTrue( - entityIndex < ENTITY_COUNT, - "Entity index is out of range" - ); - entity = &ENTITIES[entityIndex]; - } - - CUTSCENE_SYSTEM.refs.entityLastRef = entity; - return entity; -} - -uint8_t cutsceneSystemGetAreaId(const uint8_t areaId) { - if(areaId == CUTSCENE_REF_AREA_LAST_CREATED) { - assertTrue( - CUTSCENE_SYSTEM.refs.areaLastCreated != CUTSCENE_REF_AREA_LAST_CREATED, - "CUTSCENE_REF_AREA_LAST_CREATED used but no map area has been created" - ); - return CUTSCENE_SYSTEM.refs.areaLastCreated; - } - return areaId; -} - -uint8_t cutsceneSystemGetTextMiniId(const uint8_t index) { - if(index == CUTSCENE_REF_TEXT_MINI_LAST_CREATED) { - assertTrue( - CUTSCENE_SYSTEM.refs.textMiniLastCreated != - CUTSCENE_REF_TEXT_MINI_LAST_CREATED, - "CUTSCENE_REF_TEXT_MINI_LAST_CREATED used but no mini textbox has been " - "shown" - ); - return CUTSCENE_SYSTEM.refs.textMiniLastCreated; - } - return index; -} - -const char_t * cutsceneSystemGetTextCache(void) { - return CUTSCENE_SYSTEM.textCache; -} - -void cutsceneSystemSetTextCache(const char_t *text) { - assertNotNull(text, "Text cannot be NULL"); - - stringCopy( - CUTSCENE_SYSTEM.textCache, text, CUTSCENE_TEXT_CACHE_MAX - 1 - ); -} - -void cutsceneSystemNext() { - if(CUTSCENE_SYSTEM.scene == NULL) return; - - // Pop the just-finished item off the front. - if(CUTSCENE_SYSTEM.currentItem == CUTSCENE_CURRENT_ITEM_NONE) { - CUTSCENE_SYSTEM.currentItem = 0; - } else { - cutscene_t *scene = CUTSCENE_SYSTEM.scene; - const size_t tailCount = (size_t)scene->itemCount - 1; - if(tailCount > 0) { - memoryMove( - &scene->items[0], &scene->items[1], tailCount * sizeof(cutsceneitem_t) - ); - } - scene->itemCount--; - } - - // End of the cutscene? - if(CUTSCENE_SYSTEM.scene->itemCount == 0) { - cutscenecallback_t onComplete = CUTSCENE_SYSTEM.onComplete; - - CUTSCENE_SYSTEM.scene = NULL; - CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE; - - if(onComplete != NULL) onComplete(NULL); - return; - } - - // Start next (now current) item - const cutsceneitem_t *item = cutsceneSystemGetCurrentItem(); - memset(&CUTSCENE_SYSTEM.data, 0, sizeof(CUTSCENE_SYSTEM.data)); - cutsceneItemStart(item, &CUTSCENE_SYSTEM.data); -} - -void cutsceneSystemGoTo(const char_t *name) { - assertNotNull(name, "Name cannot be NULL"); - assertNotNull( - CUTSCENE_SYSTEM.scene, "cutsceneSystemGoTo called with no cutscene running" - ); - assertTrue( - CUTSCENE_SYSTEM.scene == &CUTSCENE_SYSTEM.loadedScene, - "cutsceneSystemGoTo requires the running cutscene file reference (1)" - ); - assertTrue( - CUTSCENE_SYSTEM.loadedFile[0] != '\0', - "cutsceneSystemGoTo requires the running cutscene file reference (2)" - ); - - cutscene_t freshScene = { .items = CUTSCENE_SYSTEM.gotoScratchItems }; - errorret_t result = cutsceneLoadParse( - CUTSCENE_SYSTEM.loadedFile, &freshScene, CUTSCENE_SYSTEM.gotoScratchItems, - CUTSCENE_LOADED_ITEMS_MAX - ); - if(errorIsNotOk(result)) { - errorCatch(errorPrint(result)); - uiFatalErrorOpen(NULL); - return; - } - - // Search for the marker with the given name... - for(uint8_t i = 0; i < freshScene.itemCount; i++) { - const cutsceneitem_t *item = &freshScene.items[i]; - if( - item->type != CUTSCENE_ITEM_TYPE_MARKER || - !stringEquals(item->marker.name, name) - ) continue; - - // Found, so splice the cutscene at that point and start it, the remaining - // items being the rest of the cutscene after the marker. - const uint8_t remaining = freshScene.itemCount - i; - memoryCopy( - CUTSCENE_SYSTEM.loadedItems, &freshScene.items[i], - remaining * sizeof(cutsceneitem_t) - ); - CUTSCENE_SYSTEM.loadedScene.itemCount = remaining; - CUTSCENE_SYSTEM.currentItem = 0; - - memoryZero(&CUTSCENE_SYSTEM.data, sizeof(CUTSCENE_SYSTEM.data)); - cutsceneItemStart( - &CUTSCENE_SYSTEM.loadedItems[0], &CUTSCENE_SYSTEM.data - ); - return; - } - - assertTrue(false, "cutsceneSystemGoTo: no marker found with that name"); -} - -const cutsceneitem_t * cutsceneSystemGetCurrentItem() { - if(CUTSCENE_SYSTEM.scene == NULL) return NULL; - return &CUTSCENE_SYSTEM.scene->items[0]; -} diff --git a/src/dusk/rpg/cutscene/cutscenesystem.h b/src/dusk/rpg/cutscene/cutscenesystem.h deleted file mode 100644 index 3921d549..00000000 --- a/src/dusk/rpg/cutscene/cutscenesystem.h +++ /dev/null @@ -1,241 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "cutscene.h" -#include "cutsceneref.h" - -#define CUTSCENE_ENTITY_INTERACT ((uint8_t)0xFE) -#define CUTSCENE_ENTITY_INTERACTED ((uint8_t)0xFD) -#define CUTSCENE_ENTITY_LAST_CREATED ((uint8_t)0xFC) -#define CUTSCENE_ENTITY_LAST_REF ((uint8_t)0xFB) -#define CUTSCENE_CURRENT_ITEM_NONE ((uint8_t)0xFF) - -#define CUTSCENE_TEXT_CACHE_MAX 64 -#define CUTSCENE_LOADED_ITEMS_MAX 128 -#define CUTSCENE_INSERT_ITEMS_MAX 16 - -typedef struct { - cutscene_t *scene; - uint8_t currentItem; - cutscenepause_t pause; - - cutsceneref_t refs; - - // Free-form text cache for the running cutscene - see - // cutsceneSystemGetTextCache/cutsceneSystemSetTextCache. - char_t textCache[CUTSCENE_TEXT_CACHE_MAX]; - - // Runtime data for the current item. - cutsceneitemdata_t data; - - // See cutsceneSystemSetOnComplete. - cutscenecallback_t onComplete; - - // Cache and storage used for the dynamically loaded cutscenes. - cutsceneitem_t loadedItems[CUTSCENE_LOADED_ITEMS_MAX]; - cutscene_t loadedScene; - char_t loadedFile[ASSET_FILE_NAME_MAX]; - - // Cutscene items scratch, mostly used for CUTSCENE_INSERT items. - cutsceneitem_t gotoScratchItems[CUTSCENE_LOADED_ITEMS_MAX]; -} cutscenesystem_t; - -extern cutscenesystem_t CUTSCENE_SYSTEM; - -/** - * Initialize the cutscene system. - */ -void cutsceneSystemInit(); - -/** - * Disposes of the cutscene system, stopping any active cutscene. - */ -void cutsceneSystemDispose(); - -/** - * Resets CUTSCENE_SYSTEM to run cutscene from its first item, binding - * interact/interacted entities. Does not itself advance to the first item. - * - * @param cutscene Pointer to the cutscene to prepare. - * @param interact The entity that initiated the interaction (player), or - * NULL. - * @param interacted The entity being interacted with (NPC), or NULL. - */ -void cutsceneSystemPrepare( - cutscene_t *cutscene, - entity_t *interact, - entity_t *interacted -); - -/** - * Start a cutscene with no bound entities. - * - * @param cutscene Pointer to the cutscene to start. - */ -void cutsceneSystemStartCutscene(cutscene_t *cutscene); - -/** - * Start a cutscene with the two entities that triggered it. - * - * @param cutscene Pointer to the cutscene to start. - * @param interact The entity that initiated the interaction (player). - * @param interacted The entity that was interacted with (NPC). - */ -void cutsceneSystemStartCutsceneWith( - cutscene_t *cutscene, - entity_t *interact, - entity_t *interacted -); - -/** - * Starts a cutscene with no bound entities, jumping straight to the - * marker with the given name instead of running from the first item. - * Asserts if no marker with that name exists. - * - * @param cutscene Pointer to the cutscene to start. - * @param marker Marker name to jump to. - */ -void cutsceneSystemStartCutsceneAndGoToMarker( - cutscene_t *cutscene, - const char_t *marker -); - -/** - * Splices items into the running cutscene so that items[0] becomes the - * very next item, without otherwise disturbing the currently-running - * item's progress. - * - * @param items Items to splice in. - * @param itemCount Number of items in items. - */ -void cutsceneSystemInsertItemsNext( - const cutsceneitem_t *items, - const size_t itemCount -); - -/** - * Splices items into the running cutscene right after the current item - * (see cutsceneSystemInsertItemsNext), then immediately advances into the - * first spliced item. - * - * @param items Items to splice in. - * @param itemCount Number of items in items. - */ -void cutsceneSystemInsertItems( - const cutsceneitem_t *items, - const size_t itemCount -); - -/** - * Splices cutscene's items into the running cutscene. - * - * @param cutscene The cutscene whose items to splice in. - */ -void cutsceneSystemInsertCutscene(const cutscene_t *cutscene); - -/** - * Discards every item queued after index (e.g. index 0 keeps only the - * currently-running item and drops everything spliced in behind it), - * without disturbing index's own progress - e.g. so a callback that - * decides the rest of a batch-spliced sequence is now moot (a battle - * ending mid-round, with other fighters' still-queued move cutscenes and - * the round's trailing state transition left behind) can drop it before - * splicing in its own follow-up items, instead of leaving it to run - * afterward against torn-down state. - * - * @param index Index to keep - everything after it is discarded. Must be - * < the running cutscene's current item count. - */ -void cutsceneSystemClearAfter(const uint8_t index); - -/** - * Loads and immediately starts a cutscene asset by file name. - * - * @param file Cutscene file name. - */ -void cutsceneSystemLoad(const char_t *file); - -/** - * Restarts the currently running cutscene. Will perform a load if the cutscene - * was initially from a file. - */ -void cutsceneRestart(void); - -/** - * Sets a native callback to fire once when the currently running cutscene - * finishes. - * - * @param onComplete Callback to fire on completion. - */ -void cutsceneSystemSetOnComplete(cutscenecallback_t onComplete); - -/** - * Resolves a raw entity index (or sentinel) to an entity pointer. - * - * @param entityIndex Raw entity index or sentinel value. - * @returns Pointer to the resolved entity. - */ -entity_t * cutsceneSystemGetEntity(const uint8_t entityIndex); - -/** - * Resolves a raw map area ID (or CUTSCENE_REF_AREA_LAST_CREATED sentinel) to - * a concrete map area ID. - * - * @param areaId Raw map area ID or sentinel value. - * @returns The resolved map area ID. - */ -uint8_t cutsceneSystemGetAreaId(const uint8_t areaId); - -/** - * Resolves a raw mini textbox slot index (or CUTSCENE_REF_TEXT_MINI_LAST_CREATED - * sentinel) to a concrete UI_TEXTBOX_MINI_LIST slot index. - * - * @param index Raw slot index or sentinel value. - * @returns The resolved slot index. - */ -uint8_t cutsceneSystemGetTextMiniId(const uint8_t index); - -/** - * Returns the running cutscene's cached text - see - * cutsceneSystemSetTextCache. Empty ("") if nothing has been cached yet. - * - * @returns The cached text. - */ -const char_t * cutsceneSystemGetTextCache(void); - -/** - * Overwrites the running cutscene's cached text. - * - * @param text The text to cache; copied internally. - */ -void cutsceneSystemSetTextCache(const char_t *text); - -/** - * Advance to the next item in the cutscene. - */ -void cutsceneSystemNext(); - -/** - * Jumps to a cutscene marker directly. If the cutscene is a loaded cutscene, - * this will result in it being reloaded (due to unshift nature of cutscenes). - * - * @param name Marker name to search for. - */ -void cutsceneSystemGoTo(const char_t *name); - -/** - * Update the cutscene system for one frame. - */ -void cutsceneSystemUpdate(); - -/** - * Get the current cutscene item. - * - * @return Pointer to the current cutscene item. - */ -const cutsceneitem_t * cutsceneSystemGetCurrentItem(); diff --git a/src/dusk/rpg/cutscene/item/CMakeLists.txt b/src/dusk/rpg/cutscene/item/CMakeLists.txt deleted file mode 100755 index bffc8bf0..00000000 --- a/src/dusk/rpg/cutscene/item/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutsceneitem.c - cutsceneitembase.c - cutscenecallback.c - cutsceneprint.c -) - -add_subdirectory(control) -add_subdirectory(entity) -add_subdirectory(item) -add_subdirectory(maparea) -add_subdirectory(ui) -add_subdirectory(battle) -add_subdirectory(save) -add_subdirectory(audio) -add_subdirectory(json) -add_subdirectory(story) -add_subdirectory(party) diff --git a/src/dusk/rpg/cutscene/item/audio/CMakeLists.txt b/src/dusk/rpg/cutscene/item/audio/CMakeLists.txt deleted file mode 100644 index 2fdb6a5b..00000000 --- a/src/dusk/rpg/cutscene/item/audio/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutsceneaudioplay.c - cutsceneaudiostop.c - cutsceneaudiopause.c - cutsceneaudioresume.c - cutsceneaudiofade.c - cutsceneaudiofadewait.c - cutsceneaudiosetpan.c - cutsceneaudiosetloop.c - cutsceneaudioset.c -) diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.c deleted file mode 100644 index fbacb232..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.c +++ /dev/null @@ -1,59 +0,0 @@ -/** - * 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 "audio/mixer/audiomixer.h" -#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" -#include "rpg/cutscene/item/json/cutscenejsoneasing.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneAudioFadeStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - audioMixerFadeTo( - item->audioFade.channel, - item->audioFade.from, - item->audioFade.to, - item->audioFade.duration, - item->audioFade.easing - ); -} - -bool_t cutsceneAudioFadeUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneAudioFadeLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - audiomixerchannel_t channelValue; - if(!cutsceneJsonLookupAudioChannel( - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - errorThrow("Cutscene item (AUDIO_FADE): invalid 'channel'"); - } - item->audioFade.channel = channelValue; - item->audioFade.from = cutsceneJsonGetFloat(itemObj, "from", 0.0f); - item->audioFade.to = cutsceneJsonGetFloat(itemObj, "to", 0.0f); - item->audioFade.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); - - const char_t *easingName = - yyjson_get_str(yyjson_obj_get(itemObj, "easing")); - easingtype_t easingValue = EASING_LINEAR; - if(easingName != NULL && - !cutsceneJsonLookupEasing(easingName, &easingValue)) { - errorThrow("Cutscene item (AUDIO_FADE): invalid 'easing'"); - } - item->audioFade.easing = easingValue; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h deleted file mode 100644 index d6845c85..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "audio/mixer/audiomixerchannel.h" -#include "animation/easing.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - audiomixerchannel_t channel; - float_t from; - float_t to; - float_t duration; - easingtype_t easing; -} cutsceneaudiofade_t; - -/** - * Starts an audio fade step (starts the fade via audioMixerFadeTo() - - * doesn't wait for it; pair with CUTSCENE_AUDIO_FADE_WAIT to block on it). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneAudioFadeStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an audio fade step (always completes immediately - the fade - * itself keeps running independently of cutscene progression). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneAudioFadeUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an AUDIO_FADE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneAudioFadeLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.c deleted file mode 100644 index fd8a0912..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.c +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 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 "audio/mixer/audiomixer.h" -#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" - -bool_t cutsceneAudioFadeWaitUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return !audioMixerIsFading(item->audioFadeWaitChannel); -} - -errorret_t cutsceneAudioFadeWaitLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioFadeWaitChannel); -} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h deleted file mode 100644 index 68a3ada7..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "audio/mixer/audiomixerchannel.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Updates an audio fade-wait step, completing once the watched channel's - * fade (started by a CUTSCENE_AUDIO_FADE elsewhere) finishes. Has no Start - * callback -- there's nothing to do until the fade is actually done. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once the channel is no longer fading. - */ -bool_t cutsceneAudioFadeWaitUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an AUDIO_FADE_WAIT cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneAudioFadeWaitLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.c deleted file mode 100644 index 7b098c3c..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.c +++ /dev/null @@ -1,31 +0,0 @@ -/** - * 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 "audio/mixer/audiomixer.h" -#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" - -void cutsceneAudioPauseStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - audioMixerPause(item->audioPauseChannel); -} - -bool_t cutsceneAudioPauseUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneAudioPauseLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioPauseChannel); -} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h deleted file mode 100644 index 787f1498..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "audio/mixer/audiomixerchannel.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Starts an audio pause step (pauses the channel immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneAudioPauseStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an audio pause step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneAudioPauseUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an AUDIO_PAUSE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneAudioPauseLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.c deleted file mode 100644 index e742a20b..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.c +++ /dev/null @@ -1,62 +0,0 @@ -/** - * 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/item/json/cutscenejsonaudiochannel.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneAudioPlayStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - audioMixerQueuePlay( - item->audioPlay.file, - item->audioPlay.channel, - item->audioPlay.volume, - item->audioPlay.pan, - item->audioPlay.looping, - item->audioPlay.loopCount, - item->audioPlay.loopStart, - item->audioPlay.loopTo - ); -} - -bool_t cutsceneAudioPlayUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneAudioPlayLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonCopyString( - itemObj, "file", NULL, item->audioPlay.file, AUDIO_PATH_MAX - )) { - errorThrow("Cutscene item (AUDIO_PLAY): missing/too-long 'file'"); - } - audiomixerchannel_t channelValue; - if(!cutsceneJsonLookupAudioChannel( - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - errorThrow("Cutscene item (AUDIO_PLAY): invalid 'channel'"); - } - item->audioPlay.channel = channelValue; - - item->audioPlay.volume = cutsceneJsonGetFloat(itemObj, "volume", 1.0f); - item->audioPlay.pan = cutsceneJsonGetFloat(itemObj, "pan", 0.0f); - item->audioPlay.looping = cutsceneJsonGetBool(itemObj, "looping", false); - item->audioPlay.loopCount = - (uint8_t)cutsceneJsonGetInt(itemObj, "loopCount", 0); - item->audioPlay.loopStart = - cutsceneJsonGetFloat(itemObj, "loopStart", -1.0f); - item->audioPlay.loopTo = cutsceneJsonGetFloat(itemObj, "loopTo", 0.0f); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h deleted file mode 100644 index 5348af96..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "audio/mixer/audiomixer.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - char_t file[AUDIO_PATH_MAX]; - audiomixerchannel_t channel; - float_t volume; - float_t pan; - bool_t looping; - uint8_t loopCount; - float_t loopStart; - float_t loopTo; -} cutsceneaudioplay_t; - -/** - * Starts an audio play step (queues the sound via audioMixerQueuePlay() - - * see its own comment on when it actually starts). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneAudioPlayStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an audio play step (always completes immediately - the sound - * keeps loading/playing independently of cutscene progression). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneAudioPlayUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an AUDIO_PLAY cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneAudioPlayLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.c deleted file mode 100644 index c6ab25dc..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.c +++ /dev/null @@ -1,31 +0,0 @@ -/** - * 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 "audio/mixer/audiomixer.h" -#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" - -void cutsceneAudioResumeStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - audioMixerResume(item->audioResumeChannel); -} - -bool_t cutsceneAudioResumeUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneAudioResumeLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioResumeChannel); -} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h deleted file mode 100644 index 91740eb2..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "audio/mixer/audiomixerchannel.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Starts an audio resume step (resumes the channel immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneAudioResumeStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an audio resume step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneAudioResumeUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an AUDIO_RESUME cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneAudioResumeLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.c deleted file mode 100644 index 2e6ef12a..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.c +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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 "audio/mixer/audiomixer.h" -#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneAudioSetStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - audioMixerSetPan(item->audioSet.channel, item->audioSet.pan); - audioMixerSetLoop( - item->audioSet.channel, - item->audioSet.looping, - item->audioSet.loopCount, - item->audioSet.loopStart, - item->audioSet.loopTo - ); -} - -bool_t cutsceneAudioSetUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneAudioSetLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - audiomixerchannel_t channelValue; - if(!cutsceneJsonLookupAudioChannel( - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - errorThrow("Cutscene item (AUDIO_SET): invalid 'channel'"); - } - item->audioSet.channel = channelValue; - item->audioSet.pan = cutsceneJsonGetFloat(itemObj, "pan", 0.0f); - item->audioSet.looping = cutsceneJsonGetBool(itemObj, "looping", true); - item->audioSet.loopCount = - (uint8_t)cutsceneJsonGetInt(itemObj, "loopCount", 0); - item->audioSet.loopStart = - cutsceneJsonGetFloat(itemObj, "loopStart", -1.0f); - item->audioSet.loopTo = cutsceneJsonGetFloat(itemObj, "loopTo", 0.0f); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h deleted file mode 100644 index 648dfad2..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "audio/mixer/audiomixerchannel.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - audiomixerchannel_t channel; - float_t pan; - bool_t looping; - uint8_t loopCount; - float_t loopStart; - float_t loopTo; -} cutsceneaudioset_t; - -/** - * Starts an audio set step - applies pan and looping behaviour together in - * one item (via audioMixerSetPan()/audioMixerSetLoop()), for when you want - * to change both at once rather than using CUTSCENE_AUDIO_SET_PAN/ - * CUTSCENE_AUDIO_SET_LOOP separately. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneAudioSetStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an audio set step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneAudioSetUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an AUDIO_SET cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneAudioSetLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.c deleted file mode 100644 index 101c563b..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.c +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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 "audio/mixer/audiomixer.h" -#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneAudioSetLoopStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - audioMixerSetLoop( - item->audioSetLoop.channel, - item->audioSetLoop.looping, - item->audioSetLoop.loopCount, - item->audioSetLoop.loopStart, - item->audioSetLoop.loopTo - ); -} - -bool_t cutsceneAudioSetLoopUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneAudioSetLoopLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - audiomixerchannel_t channelValue; - if(!cutsceneJsonLookupAudioChannel( - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - errorThrow("Cutscene item (AUDIO_SET_LOOP): invalid 'channel'"); - } - item->audioSetLoop.channel = channelValue; - item->audioSetLoop.looping = cutsceneJsonGetBool(itemObj, "looping", true); - item->audioSetLoop.loopCount = - (uint8_t)cutsceneJsonGetInt(itemObj, "loopCount", 0); - item->audioSetLoop.loopStart = - cutsceneJsonGetFloat(itemObj, "loopStart", -1.0f); - item->audioSetLoop.loopTo = cutsceneJsonGetFloat(itemObj, "loopTo", 0.0f); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h deleted file mode 100644 index 5f964719..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "audio/mixer/audiomixerchannel.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - audiomixerchannel_t channel; - bool_t looping; - uint8_t loopCount; - float_t loopStart; - float_t loopTo; -} cutsceneaudiosetloop_t; - -/** - * Starts an audio set-loop step (applies the new looping behaviour - * immediately, via audioMixerSetLoop()). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneAudioSetLoopStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an audio set-loop step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneAudioSetLoopUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an AUDIO_SET_LOOP cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneAudioSetLoopLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.c deleted file mode 100644 index 04cef26d..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.c +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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 "audio/mixer/audiomixer.h" -#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneAudioSetPanStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - audioMixerSetPan(item->audioSetPan.channel, item->audioSetPan.pan); -} - -bool_t cutsceneAudioSetPanUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneAudioSetPanLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - audiomixerchannel_t channelValue; - if(!cutsceneJsonLookupAudioChannel( - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), &channelValue - )) { - errorThrow("Cutscene item (AUDIO_SET_PAN): invalid 'channel'"); - } - item->audioSetPan.channel = channelValue; - item->audioSetPan.pan = cutsceneJsonGetFloat(itemObj, "pan", 0.0f); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h deleted file mode 100644 index bbc62cd7..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "audio/mixer/audiomixerchannel.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - audiomixerchannel_t channel; - float_t pan; -} cutsceneaudiosetpan_t; - -/** - * Starts an audio set-pan step (applies the new pan immediately, via - * audioMixerSetPan()). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneAudioSetPanStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an audio set-pan step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneAudioSetPanUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an AUDIO_SET_PAN cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneAudioSetPanLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.c b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.c deleted file mode 100644 index 0666a89d..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.c +++ /dev/null @@ -1,31 +0,0 @@ -/** - * 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 "audio/mixer/audiomixer.h" -#include "rpg/cutscene/item/json/cutscenejsonaudiochannel.h" - -void cutsceneAudioStopStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - audioMixerStop(item->audioStopChannel); -} - -bool_t cutsceneAudioStopUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneAudioStopLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - return cutsceneJsonLoadAudioChannelOnly(itemObj, &item->audioStopChannel); -} diff --git a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h b/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h deleted file mode 100644 index 1423a98d..00000000 --- a/src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "audio/mixer/audiomixerchannel.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Starts an audio stop step (stops the channel immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneAudioStopStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an audio stop step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneAudioStopUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an AUDIO_STOP cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneAudioStopLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/battle/CMakeLists.txt b/src/dusk/rpg/cutscene/item/battle/CMakeLists.txt deleted file mode 100644 index 55c2fdd3..00000000 --- a/src/dusk/rpg/cutscene/item/battle/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutscenebattlestart.c - cutscenebattlesetstate.c - cutscenebattlewaitselection.c - cutscenebattlepostmove.c - cutscenebattlefighterdamage.c - cutscenebattlefighterheal.c -) diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.c deleted file mode 100644 index 36a9bdbf..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.c +++ /dev/null @@ -1,68 +0,0 @@ -/** - * 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/battle/battle.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" -#include "assert/assert.h" -#include "util/string.h" - -#define CUTSCENE_BATTLE_FIGHTER_REF_MAX 16 - -void cutsceneBattleFighterDamageStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - const cutscenebattlefighterdamage_t *damage = &item->battleFighterDamage; - assertTrue( - damage->fighterIndex < BATTLE_FIGHTER_POS_COUNT, - "fighterIndex sentinel was never resolved" - ); - battlefighter_t *fighter = BATTLE.fighters[damage->fighterIndex]; - assertNotNull(fighter, "Fighter cannot be NULL"); - - battleFighterDamage(fighter, damage->amount); -} - -bool_t cutsceneBattleFighterDamageUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneBattleFighterDamageLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - char_t fighterRef[CUTSCENE_BATTLE_FIGHTER_REF_MAX]; - if(!cutsceneJsonCopyString( - itemObj, "fighter", NULL, fighterRef, CUTSCENE_BATTLE_FIGHTER_REF_MAX - )) { - errorThrow("Cutscene item (BATTLE_FIGHTER_DAMAGE): missing 'fighter'"); - } - if(stringEquals(fighterRef, "target")) { - item->battleFighterDamage.fighterIndex = BATTLE_FIGHTER_POS_TARGET; - } else if(stringEquals(fighterRef, "user")) { - item->battleFighterDamage.fighterIndex = BATTLE_FIGHTER_POS_USER; - } else { - errorThrow( - "Cutscene item (BATTLE_FIGHTER_DAMAGE): 'fighter' must be " - "'target' or 'user'" - ); - } - - const int32_t amount = cutsceneJsonGetInt(itemObj, "amount", -1); - if(amount < 0 || amount > UINT16_MAX) { - errorThrow( - "Cutscene item (BATTLE_FIGHTER_DAMAGE): invalid/missing 'amount'" - ); - } - item->battleFighterDamage.amount = (uint16_t)amount; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.h deleted file mode 100644 index 7d592732..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "rpg/battle/fighter/battlefighterpos.h" - -typedef struct { - // Index into BATTLE.fighters of the fighter to damage, or one of - // BATTLE_FIGHTER_POS_TARGET/BATTLE_FIGHTER_POS_USER, resolved against - // the currently-executing action by battleStateExecutingInit before - // this item ever runs. - battlefighterpos_t fighterIndex; - uint16_t amount; -} cutscenebattlefighterdamage_t; - -/** - * Starts a BATTLE_FIGHTER_DAMAGE item, applying the configured damage to - * BATTLE.fighters[fighterIndex] immediately. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneBattleFighterDamageStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a BATTLE_FIGHTER_DAMAGE item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneBattleFighterDamageUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a BATTLE_FIGHTER_DAMAGE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneBattleFighterDamageLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.c deleted file mode 100644 index ee288541..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.c +++ /dev/null @@ -1,68 +0,0 @@ -/** - * 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/battle/battle.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" -#include "assert/assert.h" -#include "util/string.h" - -#define CUTSCENE_BATTLE_FIGHTER_REF_MAX 16 - -void cutsceneBattleFighterHealStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - const cutscenebattlefighterheal_t *heal = &item->battleFighterHeal; - assertTrue( - heal->fighterIndex < BATTLE_FIGHTER_POS_COUNT, - "fighterIndex sentinel was never resolved" - ); - battlefighter_t *fighter = BATTLE.fighters[heal->fighterIndex]; - assertNotNull(fighter, "Fighter cannot be NULL"); - - battleFighterHeal(fighter, heal->amount); -} - -bool_t cutsceneBattleFighterHealUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneBattleFighterHealLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - char_t fighterRef[CUTSCENE_BATTLE_FIGHTER_REF_MAX]; - if(!cutsceneJsonCopyString( - itemObj, "fighter", NULL, fighterRef, CUTSCENE_BATTLE_FIGHTER_REF_MAX - )) { - errorThrow("Cutscene item (BATTLE_FIGHTER_HEAL): missing 'fighter'"); - } - if(stringEquals(fighterRef, "target")) { - item->battleFighterHeal.fighterIndex = BATTLE_FIGHTER_POS_TARGET; - } else if(stringEquals(fighterRef, "user")) { - item->battleFighterHeal.fighterIndex = BATTLE_FIGHTER_POS_USER; - } else { - errorThrow( - "Cutscene item (BATTLE_FIGHTER_HEAL): 'fighter' must be " - "'target' or 'user'" - ); - } - - const int32_t amount = cutsceneJsonGetInt(itemObj, "amount", -1); - if(amount < 0 || amount > UINT16_MAX) { - errorThrow( - "Cutscene item (BATTLE_FIGHTER_HEAL): invalid/missing 'amount'" - ); - } - item->battleFighterHeal.amount = (uint16_t)amount; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.h deleted file mode 100644 index ae414b9d..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "rpg/battle/fighter/battlefighterpos.h" - -typedef struct { - // Index into BATTLE.fighters of the fighter to heal, or one of - // BATTLE_FIGHTER_POS_TARGET/BATTLE_FIGHTER_POS_USER, resolved against - // the currently-executing action by battleStateExecutingInit before - // this item ever runs. - battlefighterpos_t fighterIndex; - uint16_t amount; -} cutscenebattlefighterheal_t; - -/** - * Starts a BATTLE_FIGHTER_HEAL item, applying the configured heal to - * BATTLE.fighters[fighterIndex] immediately. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneBattleFighterHealStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a BATTLE_FIGHTER_HEAL item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneBattleFighterHealUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a BATTLE_FIGHTER_HEAL cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneBattleFighterHealLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.c deleted file mode 100644 index ba7e1b26..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.c +++ /dev/null @@ -1,60 +0,0 @@ -/** - * 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/battle/battle.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "console/console.h" - -void cutsceneBattlePostMoveStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - consolePrint("BATTLE_POST_MOVE\n"); - - // Once a result has already been decided (e.g. the player fled), there - // is nothing left to check. - if(BATTLE.result != BATTLE_RESULT_NONE) return; - - bool_t allyAlive = false; - bool_t enemyAlive = false; - for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) { - battlefighter_t *fighter = BATTLE.fighters[i]; - if(fighter == NULL) continue; - if(!battleFighterIsAlive(fighter)) continue; - - if(fighter->team == BATTLE_FIGHTER_TEAM_ALLY) allyAlive = true; - else enemyAlive = true; - } - - if(allyAlive && enemyAlive) return; - - BATTLE.result = allyAlive ? BATTLE_RESULT_WIN : BATTLE_RESULT_LOSS; - - // Drop the rest of this round's already-spliced items (other fighters' - // still-pending move cutscenes, the trailing BATTLE_SET_STATE(PRE_ROUND) - - // see battleStateExecutingInit) - the battle is over, so none of that - // should run against the WON/LOST/ENDING transition about to replace it. - cutsceneSystemClearAfter(CUTSCENE_SYSTEM.currentItem); - - const cutsceneitem_t items[] = { - { - .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, - .battleSetState = { - .state = allyAlive ? BATTLE_STATE_WON : BATTLE_STATE_LOST - } - } - }; - cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); -} - -bool_t cutsceneBattlePostMoveUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.h deleted file mode 100644 index 58b62943..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Starts a BATTLE_POST_MOVE item. Runs after each queued move's cutscene - * finishes: checks whether every ally or every enemy fighter has been - * knocked out and, if so, sets BATTLE.result and splices in a - * BATTLE_SET_STATE to BATTLE_STATE_WON/LOST. No-ops the health check if - * BATTLE.result is already set (e.g. the player fled). Not - * JSON-authorable -- constructed dynamically by battleStateExecutingInit. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneBattlePostMoveStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a BATTLE_POST_MOVE item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneBattlePostMoveUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlesetstate.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlesetstate.c deleted file mode 100644 index 5b715c4c..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlesetstate.c +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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/battle/battle.h" -#include "rpg/cutscene/item/json/cutscenejsonbattlestate.h" -#include "assert/assert.h" - -void cutsceneBattleSetStateStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - assertNotNull(item, "item cannot be NULL"); - battleSetState(item->battleSetState.state); -} - -bool_t cutsceneBattleSetStateUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneBattleSetStateLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - battlestate_t state; - if(!cutsceneJsonLookupBattleState( - yyjson_get_str(yyjson_obj_get(itemObj, "state")), &state - )) { - errorThrow("Cutscene item (BATTLE_SET_STATE): invalid 'state'"); - } - item->battleSetState.state = state; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlesetstate.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlesetstate.h deleted file mode 100644 index 2d4753bf..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlesetstate.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/battle/state/battlestate.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - battlestate_t state; -} cutscenebattlesetstate_t; - -/** - * Starts a BATTLE_SET_STATE item, transitioning the battle to the - * configured state immediately. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneBattleSetStateStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a BATTLE_SET_STATE item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneBattleSetStateUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a BATTLE_SET_STATE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneBattleSetStateLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlestart.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlestart.c deleted file mode 100644 index d136db47..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlestart.c +++ /dev/null @@ -1,95 +0,0 @@ -/** - * 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/battle/battle.h" -#include "rpg/battle/party.h" -#include "assert/assert.h" -#include "util/memory.h" - -battlefighter_t CUTSCENE_BATTLE_START_ENEMY_POOL[ - CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX -]; - -void cutsceneBattleStartStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - assertNotNull(item, "item cannot be NULL"); - - const cutscenebattlestart_t *config = &item->battleStart; - assertTrue( - config->enemyCount <= CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX, - "enemyCount out of bounds" - ); - - battlefighter_t *fighters[BATTLE_FIGHTER_POS_COUNT] = { 0 }; - - memoryZero( - CUTSCENE_BATTLE_START_ENEMY_POOL, - sizeof(CUTSCENE_BATTLE_START_ENEMY_POOL) - ); - for(uint8_t i = 0; i < config->enemyCount; i++) { - battlefighter_t *enemy = &CUTSCENE_BATTLE_START_ENEMY_POOL[i]; - *enemy = config->enemies[i].fighter; - fighters[i] = enemy; - } - - for(uint8_t i = 0; i < PARTY_ACTIVE_SIZE_MAX; i++) { - battlefighter_t *member = partyGetOrderMember(i); - if(member == NULL) continue; - - fighters[BATTLE_FIGHTER_POS_4 + i] = member; - } - - battleStart(fighters); -} - -bool_t cutsceneBattleStartUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneBattleStartLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - yyjson_val *enemies = yyjson_obj_get(itemObj, "enemies"); - size_t enemyCount = yyjson_arr_size(enemies); - if( - !yyjson_is_arr(enemies) || enemyCount == 0 || - enemyCount > CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX - ) { - errorThrow( - "Cutscene item (BATTLE_START): 'enemies' must have 1-%u entries", - (uint32_t)CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX - ); - } - item->battleStart.enemyCount = (uint8_t)enemyCount; - - yyjson_arr_iter iter = yyjson_arr_iter_with(enemies); - yyjson_val *enemyVal; - size_t e = 0; - while((enemyVal = yyjson_arr_iter_next(&iter)) != NULL) { - cutscenebattlestartenemy_t *enemy = &item->battleStart.enemies[e]; - - errorret_t parseResult = battleFighterParse(&enemy->fighter, enemyVal); - if(errorIsNotOk(parseResult)) errorChain(parseResult); - - // Enemy JSON never specifies team/controller (battleFighterParse just - // defaulted them to ALLY/PLAYER) - BATTLE_START enemies are always - // ENEMY/AI. - enemy->fighter.team = BATTLE_FIGHTER_TEAM_ENEMY; - enemy->fighter.controller = BATTLE_FIGHTER_CONTROLLER_AI; - - e++; - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlestart.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlestart.h deleted file mode 100644 index c6024903..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlestart.h +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/battle/fighter/battlefighter.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -// Enemies always occupy the left-side positions (BATTLE_FIGHTER_POS_0-3). -#define CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX 4 - -typedef struct { - // Fully parsed at Load time via battleFighterParse (team/controller - // forced to ENEMY/AI right after, since enemy JSON only ever supplies - // stats/abilities) - Start splices this directly into - // CUTSCENE_BATTLE_START_ENEMY_POOL, no further init needed. - battlefighter_t fighter; -} cutscenebattlestartenemy_t; - -typedef struct { - cutscenebattlestartenemy_t enemies[CUTSCENE_BATTLE_START_ENEMY_COUNT_MAX]; - uint8_t enemyCount; -} cutscenebattlestart_t; - -/** - * Starts a BATTLE_START item: spawns the configured enemies, fills the - * remaining positions with the active party (via partyGetOrderMember), and - * calls battleStart. Always completes immediately - the battle scene takes - * over from here. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneBattleStartStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a BATTLE_START item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneBattleStartUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a BATTLE_START cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneBattleStartLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitselection.c b/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitselection.c deleted file mode 100644 index 763dbbff..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitselection.c +++ /dev/null @@ -1,29 +0,0 @@ -/** - * 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/battle/battle.h" -#include "rpg/battle/state/battlestateselection.h" - -bool_t cutsceneBattleWaitSelectionUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - if(BATTLE.state != BATTLE_STATE_SELECTION) return true; - if(!battleStateSelectionIsComplete()) return false; - - // Every fighter has decided - queue the move to BATTLE_STATE_EXECUTING. - const cutsceneitem_t items[] = { - { - .type = CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, - .battleSetState = { .state = BATTLE_STATE_EXECUTING } - } - }; - cutsceneSystemInsertItemsNext(items, sizeof(items) / sizeof(cutsceneitem_t)); - return true; -} diff --git a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitselection.h b/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitselection.h deleted file mode 100644 index c5235734..00000000 --- a/src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitselection.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Updates a BATTLE_WAIT_SELECTION step, completing once every fighter has - * decided a move this round (letting the queued BATTLE_SET_STATE step - * that follows move the battle on to BATTLE_STATE_EXECUTING) or once the - * selecting fighter has fled (moving on to BATTLE_STATE_FLED). Has no - * Start callback -- there's nothing to do until selection actually - * finishes. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once selection has completed or the battle has left - * BATTLE_STATE_SELECTION. - */ -bool_t cutsceneBattleWaitSelectionUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); diff --git a/src/dusk/rpg/cutscene/item/control/CMakeLists.txt b/src/dusk/rpg/cutscene/item/control/CMakeLists.txt deleted file mode 100644 index 43f8e9dd..00000000 --- a/src/dusk/rpg/cutscene/item/control/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutscenewait.c - cutscenesetpause.c - cutsceneconcurrent.c - cutscenemarker.c - cutscenerestart.c - cutscenequitgame.c - cutscenescene.c - cutsceneidle.c -) diff --git a/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.c b/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.c deleted file mode 100644 index 7145b383..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.c +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 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 "assert/assert.h" - -void cutsceneConcurrentStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - assertTrue( - item->concurrent.count <= CUTSCENE_CONCURRENT_MAX, - "Too many items in CUTSCENE_CONCURRENT" - ); - for(uint8_t i = 0; i < item->concurrent.count; i++) { - assertTrue( - item->concurrent.items[i].type != CUTSCENE_ITEM_TYPE_CONCURRENT, - "Concurrent items cannot be nested" - ); - cutsceneItemStart( - &item->concurrent.items[i], - (cutsceneitemdata_t *)&data->concurrent.childData[i] - ); - } -} - -bool_t cutsceneConcurrentUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - for(uint8_t i = 0; i < item->concurrent.count; i++) { - if(data->concurrent.doneMask & (1u << i)) continue; - if(cutsceneItemUpdate( - &item->concurrent.items[i], - (cutsceneitemdata_t *)&data->concurrent.childData[i] - )) { - data->concurrent.doneMask |= (uint8_t)(1u << i); - } - } - uint8_t allDone = (uint8_t)((1u << item->concurrent.count) - 1u); - return data->concurrent.doneMask == allDone; -} diff --git a/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h b/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h deleted file mode 100644 index 71b0ba1e..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "cutscenewait.h" -#include "rpg/cutscene/item/entity/cutsceneentitywalkto.h" - -/** Maximum number of items that may run inside a CUTSCENE_CONCURRENT. */ -#define CUTSCENE_CONCURRENT_MAX 8 - -/** - * Static (const) data for a concurrent cutscene item. - */ -typedef struct { - const cutsceneitem_t *items; - uint8_t count; -} cutsceneconcurrent_t; - -/** - * Runtime data for one non-concurrent child item. - * Concurrent items cannot be nested. - */ -typedef union { - cutscenewaitdata_t wait; - cutsceneentitywalktodata_t entityWalkTo; -} cutsceneconcurrentchilddata_t; - -/** Runtime data for a running concurrent item. */ -typedef struct { - cutsceneconcurrentchilddata_t childData[CUTSCENE_CONCURRENT_MAX]; - uint8_t doneMask; -} cutsceneconcurrentdata_t; - -/** - * Starts a concurrent item (starts all child items simultaneously). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneConcurrentStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a concurrent item (ticks all unfinished children). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once every child has completed. - */ -bool_t cutsceneConcurrentUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); diff --git a/src/dusk/rpg/cutscene/item/control/cutsceneidle.c b/src/dusk/rpg/cutscene/item/control/cutsceneidle.c deleted file mode 100644 index fcadce3e..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutsceneidle.c +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/cutscene/item/cutsceneitem.h" - -bool_t cutsceneIdleUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return false; -} diff --git a/src/dusk/rpg/cutscene/item/control/cutsceneidle.h b/src/dusk/rpg/cutscene/item/control/cutsceneidle.h deleted file mode 100644 index b230e186..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutsceneidle.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Updates an idle item. Never completes on its own - the cutscene blocks - * here indefinitely until something external (e.g. a UI callback) calls - * cutsceneSystemGoTo to jump elsewhere. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns false always. - */ -bool_t cutsceneIdleUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -// IDLE has no fields of its own to parse - see cutsceneItemLoadNoop -// (cutsceneitembase.h), used directly as its .load callback. diff --git a/src/dusk/rpg/cutscene/item/control/cutscenemarker.c b/src/dusk/rpg/cutscene/item/control/cutscenemarker.c deleted file mode 100644 index 0c210f5c..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenemarker.c +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 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/item/json/cutscenejsonfield.h" - -void cutsceneMarkerStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { -} - -bool_t cutsceneMarkerUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneMarkerLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonCopyString( - itemObj, "name", NULL, item->marker.name, CUTSCENE_MARKER_NAME_MAX - )) { - errorThrow("Cutscene item (MARKER): missing/too-long 'name'"); - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenemarker.h b/src/dusk/rpg/cutscene/item/control/cutscenemarker.h deleted file mode 100644 index 0dc10e16..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenemarker.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -// Also the max length of a marker name referenced (not declared) by other -// item types - see cutscenesavedevicecheck_t/cutscenesaveloadallslots_t/ -// cutscenemodaloptionsmarkers_t. -#define CUTSCENE_MARKER_NAME_MAX 32 - -typedef struct { - char_t name[CUTSCENE_MARKER_NAME_MAX]; -} cutscenemarker_t; - -/** - * Starts a marker item. A marker does nothing on its own - it exists - * purely as a named position for cutsceneSystemGoTo to jump to. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneMarkerStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a marker item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneMarkerUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a MARKER cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneMarkerLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/control/cutscenequitgame.c b/src/dusk/rpg/cutscene/item/control/cutscenequitgame.c deleted file mode 100644 index b07a5e8e..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenequitgame.c +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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 "engine/engine.h" - -void cutsceneQuitGameStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - ENGINE.running = false; -} - -bool_t cutsceneQuitGameUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return false; -} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenequitgame.h b/src/dusk/rpg/cutscene/item/control/cutscenequitgame.h deleted file mode 100644 index 9848a84f..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenequitgame.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Starts a quit-game item (sets ENGINE.running to false, ending the - * process's main loop). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneQuitGameStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a quit-game item. The engine is shutting down by the time this - * would run, so this always reports incomplete (mirrors - * cutsceneRestartUpdate). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns false always. - */ -bool_t cutsceneQuitGameUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -// QUIT_GAME has no fields of its own to parse - see cutsceneItemLoadNoop -// (cutsceneitembase.h), used directly as its .load callback. diff --git a/src/dusk/rpg/cutscene/item/control/cutscenerestart.c b/src/dusk/rpg/cutscene/item/control/cutscenerestart.c deleted file mode 100644 index 160a35ba..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenerestart.c +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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" - -void cutsceneRestartStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - cutsceneRestart(); -} - -bool_t cutsceneRestartUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return false; -} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenerestart.h b/src/dusk/rpg/cutscene/item/control/cutscenerestart.h deleted file mode 100644 index a1e05dd4..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenerestart.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Starts a restart item (restarts the currently running cutscene from - * its first item via cutsceneRestart). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneRestartStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a restart item. By the time this would run, the cutscene has - * already restarted from its first item, so this always reports - * incomplete (mirrors cutsceneCutsceneUpdate). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns false always. - */ -bool_t cutsceneRestartUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -// RESTART has no fields of its own to parse - see cutsceneItemLoadNoop -// (cutsceneitembase.h), used directly as its .load callback. diff --git a/src/dusk/rpg/cutscene/item/control/cutscenescene.c b/src/dusk/rpg/cutscene/item/control/cutscenescene.c deleted file mode 100644 index 57c92249..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenescene.c +++ /dev/null @@ -1,39 +0,0 @@ -/** - * 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 "scene/scene.h" -#include "rpg/cutscene/item/json/cutscenejsonscenetype.h" - -void cutsceneSceneStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - sceneSet(item->sceneChange.type); -} - -bool_t cutsceneSceneUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneSceneLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - scenetype_t sceneValue; - if(!cutsceneJsonLookupSceneType( - yyjson_get_str(yyjson_obj_get(itemObj, "sceneType")), &sceneValue - )) { - errorThrow("Cutscene item (SCENE): invalid 'sceneType'"); - } - item->sceneChange.type = sceneValue; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenescene.h b/src/dusk/rpg/cutscene/item/control/cutscenescene.h deleted file mode 100644 index f3e2d23b..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenescene.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "scene/scenetype.h" - -typedef struct { - scenetype_t type; -} cutscenescene_t; - -/** - * Starts a scene item (requests a switch to the given scene via - * sceneSet). The switch itself doesn't happen until the next - * sceneUpdate() tick, so the rest of the current frame - including - * whatever else this cutscene does after this item - still runs - * against the scene that's being left. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneSceneStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a scene item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneSceneUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a SCENE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneSceneLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.c b/src/dusk/rpg/cutscene/item/control/cutscenesetpause.c deleted file mode 100644 index e434a73d..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.c +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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/cutscene/item/json/cutscenejsonpauseflags.h" - -void cutsceneSetPauseStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - CUTSCENE_SYSTEM.pause = item->setPause; -} - -bool_t cutsceneSetPauseUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneSetPauseLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - yyjson_val *flags = yyjson_obj_get(itemObj, "flags"); - cutscenepause_t value; - if(!cutsceneJsonParsePauseFlags(flags, &value)) { - errorThrow( - "Cutscene item (SET_PAUSE): 'flags' must be an array of valid pause " - "flags" - ); - } - item->setPause = value; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h b/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h deleted file mode 100644 index 8636a50f..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenesetpause.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/cutscenepause.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Starts a set-pause item (applies the new pause flags immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneSetPauseStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a set-pause item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneSetPauseUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a SET_PAUSE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneSetPauseLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/control/cutscenewait.c b/src/dusk/rpg/cutscene/item/control/cutscenewait.c deleted file mode 100644 index be5b84f6..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenewait.c +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/cutscene/item/cutsceneitem.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" -#include "time/time.h" - -void cutsceneWaitStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - data->wait = item->wait; -} - -bool_t cutsceneWaitUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - data->wait -= TIME.delta; - return data->wait <= 0; -} - -errorret_t cutsceneWaitLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - item->wait = cutsceneJsonGetFloat(itemObj, "seconds", 0.0f); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/control/cutscenewait.h b/src/dusk/rpg/cutscene/item/control/cutscenewait.h deleted file mode 100644 index 64ccd7cb..00000000 --- a/src/dusk/rpg/cutscene/item/control/cutscenewait.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef float_t cutscenewait_t; -typedef float_t cutscenewaitdata_t; - -/** - * Starts a wait item (stores the duration in data). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneWaitStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a wait item. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true when the wait has elapsed. - */ -bool_t cutsceneWaitUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a WAIT cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneWaitLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); \ No newline at end of file diff --git a/src/dusk/rpg/cutscene/item/cutscenecallback.c b/src/dusk/rpg/cutscene/item/cutscenecallback.c deleted file mode 100644 index 9f5c0743..00000000 --- a/src/dusk/rpg/cutscene/item/cutscenecallback.c +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutsceneitem.h" -#include "rpg/cutscene/cutscenesystem.h" - -void cutsceneCallbackStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - if(item->callback != NULL) item->callback(NULL); -} - -bool_t cutsceneCallbackUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} diff --git a/src/dusk/rpg/cutscene/item/cutscenecallback.h b/src/dusk/rpg/cutscene/item/cutscenecallback.h deleted file mode 100644 index 41029607..00000000 --- a/src/dusk/rpg/cutscene/item/cutscenecallback.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef void (*cutscenecallback_t)(void *userData); - -/** - * Starts a callback item (invokes the callback immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneCallbackStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a callback item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneCallbackUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); \ No newline at end of file diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c deleted file mode 100644 index 558b810a..00000000 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ /dev/null @@ -1,562 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/cutscene/cutscenesystem.h" -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "util/memory.h" -#include "util/string.h" -#include "ui/overlay/uifatalerror.h" - -cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = { - [CUTSCENE_ITEM_TYPE_NULL] = { 0 }, - - // Control - [CUTSCENE_ITEM_TYPE_CALLBACK] = { - .init = cutsceneCallbackStart, - .update = cutsceneCallbackUpdate - }, - - [CUTSCENE_ITEM_TYPE_WAIT] = { - .init = cutsceneWaitStart, - .update = cutsceneWaitUpdate, - .jsonName = "WAIT", - .load = cutsceneWaitLoad - }, - - [CUTSCENE_ITEM_TYPE_CUTSCENE] = { - .init = cutsceneCutsceneStart, - .update = cutsceneCutsceneUpdate, - .jsonName = "CUTSCENE", - .load = cutsceneCutsceneLoad - }, - - [CUTSCENE_ITEM_TYPE_INSERT] = { - .init = cutsceneInsertStart, - .update = cutsceneInsertUpdate, - .jsonName = "INSERT", - .load = cutsceneInsertLoad - }, - - [CUTSCENE_ITEM_TYPE_SET_PAUSE] = { - .init = cutsceneSetPauseStart, - .update = cutsceneSetPauseUpdate, - .jsonName = "SET_PAUSE", - .load = cutsceneSetPauseLoad - }, - - [CUTSCENE_ITEM_TYPE_CONCURRENT] = { - .init = cutsceneConcurrentStart, - .update = cutsceneConcurrentUpdate - }, - - [CUTSCENE_ITEM_TYPE_MARKER] = { - .init = cutsceneMarkerStart, - .update = cutsceneMarkerUpdate, - .jsonName = "MARKER", - .load = cutsceneMarkerLoad - }, - - [CUTSCENE_ITEM_TYPE_RESTART] = { - .init = cutsceneRestartStart, - .update = cutsceneRestartUpdate, - .jsonName = "RESTART", - .load = cutsceneItemLoadNoop - }, - - [CUTSCENE_ITEM_TYPE_QUIT_GAME] = { - .init = cutsceneQuitGameStart, - .update = cutsceneQuitGameUpdate, - .jsonName = "QUIT_GAME", - .load = cutsceneItemLoadNoop - }, - - [CUTSCENE_ITEM_TYPE_SCENE] = { - .init = cutsceneSceneStart, - .update = cutsceneSceneUpdate, - .jsonName = "SCENE", - .load = cutsceneSceneLoad - }, - - [CUTSCENE_ITEM_TYPE_IDLE] = { - .update = cutsceneIdleUpdate, - .jsonName = "IDLE", - .load = cutsceneItemLoadNoop - }, - - // Entity - [CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT] = { - .init = cutsceneEntityTeleportStart, - .update = cutsceneEntityTeleportUpdate, - .jsonName = "ENTITY_TELEPORT", - .load = cutsceneEntityTeleportLoad - }, - - [CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO] = { - .init = cutsceneEntityWalkToStart, - .update = cutsceneEntityWalkToUpdate, - .jsonName = "ENTITY_WALK_TO", - .load = cutsceneEntityWalkToLoad - }, - - [CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY] = { - .init = cutsceneEntityWalkToEntityStart, - .update = cutsceneEntityWalkToEntityUpdate, - .jsonName = "ENTITY_WALK_TO_ENTITY", - .load = cutsceneEntityWalkToEntityLoad - }, - - [CUTSCENE_ITEM_TYPE_ENTITY_REMOVE] = { - .init = cutsceneEntityRemoveStart, - .update = cutsceneEntityRemoveUpdate, - .jsonName = "ENTITY_REMOVE", - .load = cutsceneEntityRemoveLoad - }, - - [CUTSCENE_ITEM_TYPE_ENTITY_ADD] = { - .init = cutsceneEntityAddStart, - .update = cutsceneEntityAddUpdate, - .jsonName = "ENTITY_ADD", - .load = cutsceneEntityAddLoad - }, - - [CUTSCENE_ITEM_TYPE_ENTITY_TURN] = { - .init = cutsceneEntityTurnStart, - .update = cutsceneEntityTurnUpdate, - .jsonName = "ENTITY_TURN", - .load = cutsceneEntityTurnLoad - }, - - // UI - [CUTSCENE_ITEM_TYPE_TEXT] = { - .init = cutsceneTextStart, - .update = cutsceneTextUpdate, - .jsonName = "TEXT", - .load = cutsceneTextLoad - }, - - [CUTSCENE_ITEM_TYPE_TEXT_MINI] = { - .init = cutsceneTextMiniStart, - .update = cutsceneTextMiniUpdate, - .jsonName = "TEXT_MINI", - .load = cutsceneTextMiniLoad - }, - - [CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE] = { - .init = cutsceneTextMiniHideStart, - .update = cutsceneTextMiniHideUpdate, - .jsonName = "TEXT_MINI_HIDE", - .load = cutsceneTextMiniHideLoad - }, - - [CUTSCENE_ITEM_TYPE_PRINT] = { - .init = cutscenePrintStart, - .update = cutscenePrintUpdate, - .jsonName = "PRINT", - .load = cutscenePrintLoad - }, - - [CUTSCENE_ITEM_TYPE_FADE] = { - .init = cutsceneFadeStart, - .update = cutsceneFadeUpdate, - .jsonName = "FADE", - .load = cutsceneFadeLoad - }, - - [CUTSCENE_ITEM_TYPE_EMOJI] = { - .init = cutsceneEmojiStart, - .update = cutsceneEmojiUpdate, - .jsonName = "EMOJI", - .load = cutsceneEmojiLoad - }, - - [CUTSCENE_ITEM_TYPE_SHAKE] = { - .init = cutsceneShakeStart, - .update = cutsceneShakeUpdate, - .jsonName = "SHAKE", - .load = cutsceneShakeLoad - }, - - [CUTSCENE_ITEM_TYPE_MODAL] = { - .init = cutsceneModalStart, - .update = cutsceneModalUpdate, - .jsonName = "MODAL", - .load = cutsceneModalLoad - }, - - [CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS] = { - .init = cutsceneModalOptionsMarkersStart, - .update = cutsceneModalOptionsMarkersUpdate, - .jsonName = "MODAL_OPTIONS_MARKERS", - .load = cutsceneModalOptionsMarkersLoad - }, - - [CUTSCENE_ITEM_TYPE_MODAL_CLOSE] = { - .init = cutsceneModalCloseStart, - .update = cutsceneModalCloseUpdate, - .jsonName = "MODAL_CLOSE", - .load = cutsceneItemLoadNoop - }, - - [CUTSCENE_ITEM_TYPE_KEYBOARD] = { - .init = cutsceneKeyboardStart, - .update = cutsceneKeyboardUpdate - }, - - [CUTSCENE_ITEM_TYPE_UI_SHOW] = { - .init = cutsceneUIShowStart, - .update = cutsceneUIShowUpdate, - .jsonName = "UI_SHOW", - .load = cutsceneUIShowLoad - }, - - // Item - [CUTSCENE_ITEM_TYPE_ITEM_GIVE] = { - .init = cutsceneItemGiveStart, - .update = cutsceneItemGiveUpdate, - .jsonName = "ITEM_GIVE", - .load = cutsceneItemGiveLoad - }, - - // Map area - [CUTSCENE_ITEM_TYPE_MAP_AREA_ADD] = { - .init = cutsceneMapAreaAddStart, - .update = cutsceneMapAreaAddUpdate - }, - - [CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE] = { - .init = cutsceneMapAreaRemoveStart, - .update = cutsceneMapAreaRemoveUpdate, - .jsonName = "MAP_AREA_REMOVE", - .load = cutsceneMapAreaRemoveLoad - }, - - [CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT] = { - .init = cutsceneMapAreaWaitStart, - .update = cutsceneMapAreaWaitUpdate, - .jsonName = "MAP_AREA_WAIT", - .load = cutsceneMapAreaWaitLoad - }, - - // Battle - [CUTSCENE_ITEM_TYPE_BATTLE_START] = { - .init = cutsceneBattleStartStart, - .update = cutsceneBattleStartUpdate, - .jsonName = "BATTLE_START", - .load = cutsceneBattleStartLoad - }, - - [CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE] = { - .init = cutsceneBattleSetStateStart, - .update = cutsceneBattleSetStateUpdate, - .jsonName = "BATTLE_SET_STATE", - .load = cutsceneBattleSetStateLoad - }, - - [CUTSCENE_ITEM_TYPE_BATTLE_WAIT_SELECTION] = { - .update = cutsceneBattleWaitSelectionUpdate, - .jsonName = "BATTLE_WAIT_SELECTION", - .load = cutsceneItemLoadNoop - }, - - [CUTSCENE_ITEM_TYPE_BATTLE_POST_MOVE] = { - .init = cutsceneBattlePostMoveStart, - .update = cutsceneBattlePostMoveUpdate - }, - - [CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_DAMAGE] = { - .init = cutsceneBattleFighterDamageStart, - .update = cutsceneBattleFighterDamageUpdate, - .jsonName = "BATTLE_FIGHTER_DAMAGE", - .load = cutsceneBattleFighterDamageLoad - }, - - [CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_HEAL] = { - .init = cutsceneBattleFighterHealStart, - .update = cutsceneBattleFighterHealUpdate, - .jsonName = "BATTLE_FIGHTER_HEAL", - .load = cutsceneBattleFighterHealLoad - }, - - // Save - [CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK] = { - .init = cutsceneSaveDeviceCheckStart, - .update = cutsceneSaveDeviceCheckUpdate, - .jsonName = "SAVE_DEVICE_CHECK", - .load = cutsceneSaveDeviceCheckLoad - }, - - [CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS] = { - .init = cutsceneSaveLoadAllSlotsStart, - .update = cutsceneSaveLoadAllSlotsUpdate, - .jsonName = "SAVE_LOAD_ALL_SLOTS", - .load = cutsceneSaveLoadAllSlotsLoad - }, - - // Audio - [CUTSCENE_ITEM_TYPE_AUDIO_PLAY] = { - .init = cutsceneAudioPlayStart, - .update = cutsceneAudioPlayUpdate, - .jsonName = "AUDIO_PLAY", - .load = cutsceneAudioPlayLoad - }, - - [CUTSCENE_ITEM_TYPE_AUDIO_STOP] = { - .init = cutsceneAudioStopStart, - .update = cutsceneAudioStopUpdate, - .jsonName = "AUDIO_STOP", - .load = cutsceneAudioStopLoad - }, - - [CUTSCENE_ITEM_TYPE_AUDIO_PAUSE] = { - .init = cutsceneAudioPauseStart, - .update = cutsceneAudioPauseUpdate, - .jsonName = "AUDIO_PAUSE", - .load = cutsceneAudioPauseLoad - }, - - [CUTSCENE_ITEM_TYPE_AUDIO_RESUME] = { - .init = cutsceneAudioResumeStart, - .update = cutsceneAudioResumeUpdate, - .jsonName = "AUDIO_RESUME", - .load = cutsceneAudioResumeLoad - }, - - [CUTSCENE_ITEM_TYPE_AUDIO_FADE] = { - .init = cutsceneAudioFadeStart, - .update = cutsceneAudioFadeUpdate, - .jsonName = "AUDIO_FADE", - .load = cutsceneAudioFadeLoad - }, - - [CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT] = { - .update = cutsceneAudioFadeWaitUpdate, - .jsonName = "AUDIO_FADE_WAIT", - .load = cutsceneAudioFadeWaitLoad - }, - - [CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN] = { - .init = cutsceneAudioSetPanStart, - .update = cutsceneAudioSetPanUpdate, - .jsonName = "AUDIO_SET_PAN", - .load = cutsceneAudioSetPanLoad - }, - - [CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP] = { - .init = cutsceneAudioSetLoopStart, - .update = cutsceneAudioSetLoopUpdate, - .jsonName = "AUDIO_SET_LOOP", - .load = cutsceneAudioSetLoopLoad - }, - - [CUTSCENE_ITEM_TYPE_AUDIO_SET] = { - .init = cutsceneAudioSetStart, - .update = cutsceneAudioSetUpdate, - .jsonName = "AUDIO_SET", - .load = cutsceneAudioSetLoad - }, - - // Story - [CUTSCENE_ITEM_TYPE_STORY_SET_FLAG] = { - .init = cutsceneStorySetFlagStart, - .update = cutsceneStorySetFlagUpdate, - .jsonName = "STORY_SET_FLAG", - .load = cutsceneStorySetFlagLoad - }, - - [CUTSCENE_ITEM_TYPE_STORY_WAIT_FLAG] = { - .update = cutsceneStoryWaitFlagUpdate, - .jsonName = "STORY_WAIT_FLAG", - .load = cutsceneStoryWaitFlagLoad - }, - - // Party - [CUTSCENE_ITEM_TYPE_PARTY_ADD_MEMBER] = { - .init = cutscenePartyAddMemberStart, - .update = cutscenePartyAddMemberUpdate, - .jsonName = "PARTY_ADD_MEMBER", - .load = cutscenePartyAddMemberLoad - }, - - [CUTSCENE_ITEM_TYPE_PARTY_REMOVE_MEMBER] = { - .init = cutscenePartyRemoveMemberStart, - .update = cutscenePartyRemoveMemberUpdate, - .jsonName = "PARTY_REMOVE_MEMBER", - .load = cutscenePartyRemoveMemberLoad - }, - - [CUTSCENE_ITEM_TYPE_PARTY_HEAL_MEMBER] = { - .init = cutscenePartyHealMemberStart, - .update = cutscenePartyHealMemberUpdate, - .jsonName = "PARTY_HEAL_MEMBER", - .load = cutscenePartyHealMemberLoad - }, - - [CUTSCENE_ITEM_TYPE_PARTY_HEAL_ALL] = { - .init = cutscenePartyHealAllStart, - .update = cutscenePartyHealAllUpdate, - .jsonName = "PARTY_HEAL_ALL", - .load = cutsceneItemLoadNoop - } -}; - -bool_t cutsceneItemLookupType( - const char_t *name, - cutsceneitemtype_t *outType -) { - if(name == NULL) return false; - for(size_t i = 0; i < CUTSCENE_ITEM_TYPE_COUNT; i++) { - const char_t *jsonName = CUTSCENE_ITEM_CALLBACKS[i].jsonName; - if(jsonName != NULL && stringEquals(jsonName, name)) { - *outType = (cutsceneitemtype_t)i; - return true; - } - } - return false; -} - -void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) { - cutsceneiteminitcallback_t *init = CUTSCENE_ITEM_CALLBACKS[item->type].init; - if(init != NULL) init(item, data); -} - -bool_t cutsceneItemUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - cutsceneitemupdatecallback_t *update = - CUTSCENE_ITEM_CALLBACKS[item->type].update; - if(update == NULL) return false; - - return update(item, data); -} - -void cutsceneCutsceneStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - cutscene_t *cutscene = item->cutsceneRef.cutscene; - if(cutscene == NULL && item->cutsceneRef.name[0] != '\0') { - // Not cached back into item - with only one shared, fixed load buffer - // (see cutscenesystem.h's CUTSCENE_LOADED_ITEMS_MAX doc comment), - // every JSON-authored reference resolves to the same fixed address - // every time anyway, and item itself lives inside that very buffer - - // caching a pointer into it here would go stale (or, worse, get - // clobbered mid-resolve) the moment a later load overwrites it. - cutscene = cutsceneCutsceneResolve(item->cutsceneRef.name); - } - if(cutscene != NULL) cutsceneSystemStartCutscene(cutscene); -} - -bool_t cutsceneCutsceneUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return false; -} - -cutscene_t * cutsceneCutsceneResolve(const char_t *name) { - // Copy name out before it can be invalidated: this is commonly called - // with a pointer into CUTSCENE_SYSTEM.loadedItems itself (a running - // CUTSCENE/INSERT item's own cutsceneRef.name/insert.name field, when - // the running cutscene IS loadedScene, e.g. initial.jsonc's closing - // CUTSCENE item into main_menu.jsonc) - cutsceneLoadParse below parses - // straight into that same shared loadedItems buffer, overwriting the - // very memory name points into with the target cutscene's own first - // item before this function is done reading name. - char_t nameCopy[ASSET_FILE_NAME_MAX]; - stringCopy(nameCopy, name, ASSET_FILE_NAME_MAX - 1); - - errorret_t result = cutsceneLoadParse( - nameCopy, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, - CUTSCENE_LOADED_ITEMS_MAX - ); - if(errorIsNotOk(result)) { - errorCatch(errorPrint(result)); - uiFatalErrorOpen(NULL); - return NULL; - } - - // Shares loadedScene/loadedItems with cutsceneSystemLoad, so track the - // source the same way it does - cutsceneSystemGoTo needs this to reload the - // untouched original when jumping to a marker behind the current - // position (see cutsceneSystemGoTo's doc comment). - stringCopy(CUTSCENE_SYSTEM.loadedFile, nameCopy, ASSET_FILE_NAME_MAX - 1); - - return &CUTSCENE_SYSTEM.loadedScene; -} - -errorret_t cutsceneCutsceneLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - return cutsceneCutsceneRefLoad(itemObj, &item->cutsceneRef, "CUTSCENE"); -} - -errorret_t cutsceneCutsceneRefLoad( - yyjson_val *itemObj, - cutscenecutsceneref_t *outRef, - const char_t *itemTypeLabel -) { - const char_t *name = yyjson_get_str(yyjson_obj_get(itemObj, "name")); - if(name == NULL) { - errorThrow("Cutscene item (%s): missing 'name'", itemTypeLabel); - } - - // Only validate that name itself would fit - the reference itself is - // not resolved/locked until this item actually runs (see - // cutsceneCutsceneStart/cutsceneCutsceneResolve). - int32_t nameLen = stringFormat(NULL, 0, "%s", name); - if(nameLen < 0 || (size_t)nameLen >= CUTSCENE_CUTSCENE_REF_NAME_MAX) { - errorThrow("Cutscene item (%s): 'name' too long", itemTypeLabel); - } - - outRef->cutscene = NULL; - stringCopy(outRef->name, name, CUTSCENE_CUTSCENE_REF_NAME_MAX); - - errorOk(); -} - -void cutsceneInsertStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - if(item->insert.cutscene != NULL) { - cutsceneSystemInsertCutscene(item->insert.cutscene); - } else if(item->insert.name[0] != '\0') { - cutsceneInsertResolveAndSplice(item->insert.name); - } -} - -bool_t cutsceneInsertUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return false; -} - -void cutsceneInsertResolveAndSplice(const char_t *name) { - // Local scratch, not a shared buffer - this target is spliced straight - // into the running scene below and never referenced again afterward, so - // it only needs to live for the rest of this call. - cutsceneitem_t items[CUTSCENE_INSERT_ITEMS_MAX]; - cutscene_t scene = { .items = items }; - errorret_t result = cutsceneLoadParse(name, &scene, items, CUTSCENE_INSERT_ITEMS_MAX); - if(errorIsNotOk(result)) { - errorCatch(errorPrint(result)); - uiFatalErrorOpen(NULL); - return; - } - - cutsceneSystemInsertCutscene(&scene); -} - -errorret_t cutsceneInsertLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - return cutsceneCutsceneRefLoad(itemObj, &item->insert, "INSERT"); -} diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.h b/src/dusk/rpg/cutscene/item/cutsceneitem.h deleted file mode 100644 index 84b09277..00000000 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.h +++ /dev/null @@ -1,367 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "yyjson.h" -#include "asset/assetfile.h" -#include "cutsceneitembase.h" -#include "cutscenecallback.h" -#include "cutsceneprint.h" -#include "control/cutscenewait.h" -#include "control/cutscenesetpause.h" -#include "control/cutsceneconcurrent.h" -#include "control/cutscenemarker.h" -#include "control/cutscenerestart.h" -#include "control/cutscenequitgame.h" -#include "control/cutscenescene.h" -#include "control/cutsceneidle.h" -#include "entity/cutsceneentityteleport.h" -#include "entity/cutsceneentitywalkto.h" -#include "entity/cutsceneentityremove.h" -#include "entity/cutsceneentityadd.h" -#include "entity/cutsceneentityturn.h" -#include "entity/cutsceneentitywalktoentity.h" -#include "ui/cutscenetext.h" -#include "ui/cutscenetextmini.h" -#include "ui/cutscenetextminihide.h" -#include "ui/cutscenefade.h" -#include "ui/cutsceneemoji.h" -#include "ui/cutsceneshake.h" -#include "ui/cutscenemodal.h" -#include "ui/cutscenemodaloptionsmarkers.h" -#include "ui/cutscenekeyboard.h" -#include "ui/cutsceneuishow.h" -#include "item/cutsceneitemgive.h" -#include "maparea/cutscenemapareaadd.h" -#include "maparea/cutscenemaparearemove.h" -#include "maparea/cutscenemapareawait.h" -#include "battle/cutscenebattlestart.h" -#include "battle/cutscenebattlesetstate.h" -#include "battle/cutscenebattlewaitselection.h" -#include "battle/cutscenebattlepostmove.h" -#include "battle/cutscenebattlefighterdamage.h" -#include "battle/cutscenebattlefighterheal.h" -#include "save/cutscenesavedevicecheck.h" -#include "save/cutscenesaveloadallslots.h" -#include "audio/cutsceneaudioplay.h" -#include "audio/cutsceneaudiostop.h" -#include "audio/cutsceneaudiopause.h" -#include "audio/cutsceneaudioresume.h" -#include "audio/cutsceneaudiofade.h" -#include "audio/cutsceneaudiofadewait.h" -#include "audio/cutsceneaudiosetpan.h" -#include "audio/cutsceneaudiosetloop.h" -#include "audio/cutsceneaudioset.h" -#include "story/cutscenestorysetflag.h" -#include "story/cutscenestorywaitflag.h" -#include "party/cutscenepartyaddmember.h" -#include "party/cutscenepartyremovemember.h" -#include "party/cutscenepartyhealmember.h" -#include "party/cutscenepartyhealall.h" - -typedef struct cutscene_s cutscene_t; - -// Max length of a CUTSCENE/INSERT item's "name" field - it's the full -// asset path to load, so this is bounded by ASSET_FILE_NAME_MAX directly. -#define CUTSCENE_CUTSCENE_REF_NAME_MAX ASSET_FILE_NAME_MAX - -// A CUTSCENE (or INSERT) item's target. A C-authored cutscene_t* set -// directly (e.g. by a test) leaves name empty ("") - safe to hold onto -// indefinitely, since C-authored cutscenes are compiled directly into -// their own static arrays, never a shared load buffer. JSON-authored -// cutscenes (cutsceneCutsceneLoad/cutsceneInsertLoad) do the opposite - -// only name is set at parse time (copied by value out of the JSON doc, -// so it stays valid regardless of the doc's own lifetime), since -// resolving/locking the referenced asset there would eagerly load every -// cutscene a cutscene merely references, even ones never played. -// cutsceneCutsceneStart resolves name to cutscene the first time this -// item actually runs (via cutsceneCutsceneResolve) but does NOT cache the -// result back into this field - with only one shared, fixed buffer (see -// CUTSCENE_LOADED_ITEMS_MAX in cutscenesystem.h), every JSON-authored -// reference resolves to the same fixed address every time anyway, and -// item itself may live inside that very buffer, so caching a pointer -// into it here would go stale (or, worse, get clobbered mid-resolve) the -// moment a later load overwrites it. cutsceneInsertStart instead resolves -// AND splices name in one step (cutsceneInsertResolveAndSplice) using a -// local scratch buffer that's gone by the time this item's Start -// returns, so there's nothing to cache back for INSERT even in principle. -// Shared by both CUTSCENE (item->cutsceneRef) and INSERT (item->insert), -// since both need exactly this "resolved pointer, or a name to lazily -// resolve" shape. -typedef struct { - cutscene_t *cutscene; - char_t name[CUTSCENE_CUTSCENE_REF_NAME_MAX]; -} cutscenecutsceneref_t; - -struct cutsceneitem_s { - cutsceneitemtype_t type; - - union { - cutscenetext_t text; - cutscenetextmini_t textMini; - cutscenetextminihide_t textMiniHide; - cutscenecallback_t callback; - cutscenewait_t wait; - cutscenecutsceneref_t cutsceneRef; - cutscenecutsceneref_t insert; - cutsceneentityteleport_t entityTeleport; - cutsceneentitywalkto_t entityWalkTo; - cutscenefade_t fade; - cutscenepause_t setPause; - cutsceneconcurrent_t concurrent; - cutsceneitemgive_t itemGive; - cutsceneentityremove_t entityRemove; - cutsceneentityadd_t entityAdd; - cutsceneentityturn_t entityTurn; - cutsceneentitywalktoentity_t entityWalkToEntity; - cutscenemapareaadd_t mapAreaAdd; - cutscenemaparearemove_t mapAreaRemove; - cutscenemapareawait_t mapAreaWait; - cutscenebattlestart_t battleStart; - cutscenebattlesetstate_t battleSetState; - cutscenebattlefighterdamage_t battleFighterDamage; - cutscenebattlefighterheal_t battleFighterHeal; - cutsceneemoji_t emoji; - cutsceneshake_t shake; - cutscenemodal_t modal; - cutscenemodaloptionsmarkers_t modalOptionsMarkers; - cutsceneprint_t print; - cutscenemarker_t marker; - cutscenescene_t sceneChange; - cutscenesavedevicecheck_t saveDeviceCheck; - cutscenesaveloadallslots_t saveLoadAllSlots; - cutscenekeyboard_t keyboard; - cutsceneaudioplay_t audioPlay; - audiomixerchannel_t audioStopChannel; - audiomixerchannel_t audioPauseChannel; - audiomixerchannel_t audioResumeChannel; - cutsceneaudiofade_t audioFade; - audiomixerchannel_t audioFadeWaitChannel; - cutsceneaudiosetpan_t audioSetPan; - cutsceneaudiosetloop_t audioSetLoop; - cutsceneaudioset_t audioSet; - cutsceneuishow_t uiShow; - cutscenestorysetflag_t storySetFlag; - cutscenestorywaitflag_t storyWaitFlag; - cutscenepartyaddmember_t partyAddMember; - cutscenepartyremovemember_t partyRemoveMember; - cutscenepartyhealmember_t partyHealMember; - }; -}; - -typedef union cutsceneitemdata_u { - cutscenewaitdata_t wait; - cutsceneentitywalktodata_t entityWalkTo; - cutsceneconcurrentdata_t concurrent; - cutscenemapareawaitdata_t mapAreaWait; -} cutsceneitemdata_t; - -typedef void (cutsceneiteminitcallback_t)( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -typedef bool_t (cutsceneitemupdatecallback_t)( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -typedef errorret_t (cutsceneitemloadcallback_t)( - yyjson_val *itemObj, - cutsceneitem_t *item -); - -typedef struct { - cutsceneiteminitcallback_t *init; - cutsceneitemupdatecallback_t *update; - const char_t *jsonName; // NULL if not JSON-authorable - cutsceneitemloadcallback_t *load; // NULL if not JSON-authorable -} cutsceneitemcallbacks_t; - -extern cutsceneitemcallbacks_t - CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT]; - -/** - * Start the given cutscene item. - * - * @param item The cutscene item to start. - * @param data Runtime data storage (pre-zeroed by caller). - */ -void cutsceneItemStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Tick the given cutscene item (one frame). - * - * @param item The cutscene item to tick. - * @param data Runtime data storage. - * @returns true if the item is complete and the cutscene should advance. - */ -bool_t cutsceneItemUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Looks up name (exact case) against every JSON-authorable item type's - * jsonName. - * - * @param name Name to search for. - * @param outType Set to the matching item type on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneItemLookupType( - const char_t *name, - cutsceneitemtype_t *outType -); - -/** - * Starts a nested-cutscene item, handing control over to the referenced - * cutscene. If item->cutsceneRef.cutscene hasn't been resolved yet (a - * JSON-authored reference, only known by name so far), resolves it first - * via cutsceneCutsceneResolve (not cached back - see cutscenecutsceneref_t's - * doc comment for why). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneCutsceneStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Resolves a JSON-authored CUTSCENE item's referenced cutscene by name: - * locks+loads name as a plain ASSET_LOADER_TYPE_JSON asset, parses it into - * CUTSCENE_SYSTEM.loadedItems/loadedScene, then unlocks the asset entry - * like any other asset - the parsed items don't reference the doc past - * that (see cutsceneInsertResolveAndSplice's matching comment for why). - * Called lazily from cutsceneCutsceneStart the first time the item - * actually runs, not at parse time, so a cutscene referencing many others - * doesn't load every one of them up front. On failure, opens the fatal - * error overlay (matching cutsceneSystemLoad's runtime-failure handling) - * and returns NULL. - * - * @param name Full asset path to the referenced cutscene's JSON file. - * @return The resolved cutscene, or NULL on failure. - */ -cutscene_t * cutsceneCutsceneResolve(const char_t *name); - -/** - * Updates a nested-cutscene item. By the time this would run, control - * has already moved on to the referenced cutscene, so this always - * reports incomplete. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns false always. - */ -bool_t cutsceneCutsceneUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a CUTSCENE cutscene item's JSON fields into item. Kept inline here - * (rather than its own file) since it's tightly bound to the asset/loader - * system itself. Only stores the 'name' string (which points into the - * cutscene asset's still-alive yyjson doc, so no copy is needed) - - * resolving it to the referenced asset is deferred to cutsceneCutsceneStart, - * the first time this item actually runs, so a cutscene referencing many - * others doesn't pay to load every one of them up front. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneCutsceneLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); - -/** - * Shared JSON parsing body for both CUTSCENE and INSERT items - both need - * exactly a 'name' string field, the full asset path to the referenced - * cutscene's JSON file, validated to fit CUTSCENE_CUTSCENE_REF_NAME_MAX. - * Only stores the name (see cutsceneCutsceneLoad for why resolving is - * deferred). - * - * @param itemObj The item's JSON object. - * @param outRef Destination ref, already zeroed by the caller. - * @param itemTypeLabel Item type name used only in the error message on - * failure (e.g. "CUTSCENE" or "INSERT"). - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneCutsceneRefLoad( - yyjson_val *itemObj, - cutscenecutsceneref_t *outRef, - const char_t *itemTypeLabel -); - -/** - * Starts an INSERT item, splicing the referenced cutscene's items into the - * running cutscene in place via cutsceneSystemInsertCutscene - unlike - * CUTSCENE (a one-way jump that replaces the running cutscene outright), - * whatever already followed this item continues normally once the - * inserted items run out, just shifted further down the same array. If - * item->insert.cutscene is set (a compile-time target), splices it - * directly; otherwise (a JSON-authored reference, only known by name so - * far) resolves and splices it via cutsceneInsertResolveAndSplice. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneInsertStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Resolves a JSON-authored INSERT item's referenced cutscene by name and - * splices it into the running cutscene (CUTSCENE_SYSTEM.scene): locks+ - * loads name as a plain ASSET_LOADER_TYPE_JSON asset, parses it into a - * small scratch buffer local to this call (capacity - * CUTSCENE_INSERT_ITEMS_MAX) straight out of the asset entry's own doc, - * then unlocks the entry like any other asset - nothing needs to keep the - * doc itself resident past the parse: every cutscene item field that could - * reference it owns its string data by value (see - * cutscenecutsceneref_t/cutscenemarker_t's doc comments), and - * cutsceneSystemInsertCutscene copies the scratch items into the running - * scene's own array right after. On failure, opens the fatal error - * overlay, matching cutsceneCutsceneResolve. - * - * @param name Full asset path to the referenced cutscene's JSON file. - */ -void cutsceneInsertResolveAndSplice(const char_t *name); - -/** - * Updates an INSERT item. By the time this would run, control has already - * moved on to the inserted items, so this always reports incomplete. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns false always. - */ -bool_t cutsceneInsertUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an INSERT cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneInsertLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/cutsceneitembase.c b/src/dusk/rpg/cutscene/item/cutsceneitembase.c deleted file mode 100644 index 11cb848e..00000000 --- a/src/dusk/rpg/cutscene/item/cutsceneitembase.c +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutsceneitembase.h" - -errorret_t cutsceneItemLoadNoop( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/cutsceneitembase.h b/src/dusk/rpg/cutscene/item/cutsceneitembase.h deleted file mode 100644 index b53bcbbf..00000000 --- a/src/dusk/rpg/cutscene/item/cutsceneitembase.h +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "error/error.h" -#include "yyjson.h" - -typedef struct cutsceneitem_s cutsceneitem_t; -typedef union cutsceneitemdata_u cutsceneitemdata_t; - -typedef enum { - CUTSCENE_ITEM_TYPE_NULL, - - // Control - CUTSCENE_ITEM_TYPE_CALLBACK, - CUTSCENE_ITEM_TYPE_WAIT, - CUTSCENE_ITEM_TYPE_CUTSCENE, - CUTSCENE_ITEM_TYPE_INSERT, - CUTSCENE_ITEM_TYPE_SET_PAUSE, - CUTSCENE_ITEM_TYPE_CONCURRENT, - CUTSCENE_ITEM_TYPE_MARKER, - CUTSCENE_ITEM_TYPE_RESTART, - CUTSCENE_ITEM_TYPE_QUIT_GAME, - CUTSCENE_ITEM_TYPE_SCENE, - CUTSCENE_ITEM_TYPE_IDLE, - - // Entity - CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT, - CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO, - CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY, - CUTSCENE_ITEM_TYPE_ENTITY_REMOVE, - CUTSCENE_ITEM_TYPE_ENTITY_ADD, - CUTSCENE_ITEM_TYPE_ENTITY_TURN, - - // UI - CUTSCENE_ITEM_TYPE_TEXT, - CUTSCENE_ITEM_TYPE_TEXT_MINI, - CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE, - CUTSCENE_ITEM_TYPE_PRINT, - CUTSCENE_ITEM_TYPE_FADE, - CUTSCENE_ITEM_TYPE_EMOJI, - CUTSCENE_ITEM_TYPE_SHAKE, - CUTSCENE_ITEM_TYPE_MODAL, - CUTSCENE_ITEM_TYPE_MODAL_OPTIONS_MARKERS, - CUTSCENE_ITEM_TYPE_MODAL_CLOSE, - CUTSCENE_ITEM_TYPE_KEYBOARD, - CUTSCENE_ITEM_TYPE_UI_SHOW, - - // Item - CUTSCENE_ITEM_TYPE_ITEM_GIVE, - - // Map area - CUTSCENE_ITEM_TYPE_MAP_AREA_ADD, - CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, - CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, - - // Battle - CUTSCENE_ITEM_TYPE_BATTLE_START, - CUTSCENE_ITEM_TYPE_BATTLE_SET_STATE, - CUTSCENE_ITEM_TYPE_BATTLE_WAIT_SELECTION, - CUTSCENE_ITEM_TYPE_BATTLE_POST_MOVE, - CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_DAMAGE, - CUTSCENE_ITEM_TYPE_BATTLE_FIGHTER_HEAL, - - // Save - CUTSCENE_ITEM_TYPE_SAVE_DEVICE_CHECK, - CUTSCENE_ITEM_TYPE_SAVE_LOAD_ALL_SLOTS, - - // Audio - CUTSCENE_ITEM_TYPE_AUDIO_PLAY, - CUTSCENE_ITEM_TYPE_AUDIO_STOP, - CUTSCENE_ITEM_TYPE_AUDIO_PAUSE, - CUTSCENE_ITEM_TYPE_AUDIO_RESUME, - CUTSCENE_ITEM_TYPE_AUDIO_FADE, - CUTSCENE_ITEM_TYPE_AUDIO_FADE_WAIT, - CUTSCENE_ITEM_TYPE_AUDIO_SET_PAN, - CUTSCENE_ITEM_TYPE_AUDIO_SET_LOOP, - CUTSCENE_ITEM_TYPE_AUDIO_SET, - - // Story - CUTSCENE_ITEM_TYPE_STORY_SET_FLAG, - CUTSCENE_ITEM_TYPE_STORY_WAIT_FLAG, - - // Party - CUTSCENE_ITEM_TYPE_PARTY_ADD_MEMBER, - CUTSCENE_ITEM_TYPE_PARTY_REMOVE_MEMBER, - CUTSCENE_ITEM_TYPE_PARTY_HEAL_MEMBER, - CUTSCENE_ITEM_TYPE_PARTY_HEAL_ALL, - - CUTSCENE_ITEM_TYPE_COUNT -} cutsceneitemtype_t; - -/** - * Shared no-op Load for item types with no JSON fields of their own to - * parse (IDLE, RESTART, MODAL_CLOSE) - always succeeds without touching - * item. - * - * @param itemObj The item's JSON object (unused). - * @param item Destination item (unused). - * @return Always errorOk(). - */ -errorret_t cutsceneItemLoadNoop( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/cutsceneprint.c b/src/dusk/rpg/cutscene/item/cutsceneprint.c deleted file mode 100644 index 45b084cf..00000000 --- a/src/dusk/rpg/cutscene/item/cutsceneprint.c +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutsceneitem.h" -#include "console/console.h" -#include "json/cutscenejsonfield.h" - -void cutscenePrintStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - consolePrint("%s", item->print.text); -} - -bool_t cutscenePrintUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutscenePrintLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonCopyString( - itemObj, "text", NULL, item->print.text, CUTSCENE_PRINT_MAX_CHARS - )) { - errorThrow("Cutscene item (PRINT): missing/too-long 'text'"); - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/cutsceneprint.h b/src/dusk/rpg/cutscene/item/cutsceneprint.h deleted file mode 100644 index 640dab3e..00000000 --- a/src/dusk/rpg/cutscene/item/cutsceneprint.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -#define CUTSCENE_PRINT_MAX_CHARS 128 - -typedef struct { - char_t text[CUTSCENE_PRINT_MAX_CHARS]; -} cutsceneprint_t; - -/** - * Starts a print item (prints the item's text to the console). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutscenePrintStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a print item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutscenePrintUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a PRINT cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutscenePrintLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/entity/CMakeLists.txt b/src/dusk/rpg/cutscene/item/entity/CMakeLists.txt deleted file mode 100644 index e6dfbd2d..00000000 --- a/src/dusk/rpg/cutscene/item/entity/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutsceneentityteleport.c - cutsceneentitywalkto.c - cutsceneentityremove.c - cutsceneentityadd.c - cutsceneentityturn.c - cutsceneentitywalktoentity.c -) diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c deleted file mode 100644 index af8cca35..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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" -#include "assert/assert.h" -#include "rpg/cutscene/item/json/cutscenejsonentitytype.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneEntityAddStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - uint8_t entIndex = entityGetAvailable(); - assertTrue( - entIndex != 0xFF, "No available entity slots for CUTSCENE_ENTITY_ADD" - ); - - entity_t *entity = &ENTITIES[entIndex]; - entityInit(entity, item->entityAdd.entityType); - entityPositionSet(entity, item->entityAdd.position);// Also assigns chunk. - - CUTSCENE_SYSTEM.refs.entityLastCreated = entity; - CUTSCENE_SYSTEM.refs.entityLastRef = entity; -} - -bool_t cutsceneEntityAddUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneEntityAddLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - entitytype_t entityTypeValue; - if(!cutsceneJsonLookupEntityType( - yyjson_get_str(yyjson_obj_get(itemObj, "entityType")), &entityTypeValue - )) { - errorThrow("Cutscene item (ENTITY_ADD): invalid 'entityType'"); - } - item->entityAdd.entityType = entityTypeValue; - if(!cutsceneJsonReadWorldPos( - yyjson_obj_get(itemObj, "position"), &item->entityAdd.position - )) { - errorThrow("Cutscene item (ENTITY_ADD): 'position' must be [x, y, z]"); - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h deleted file mode 100644 index 440cd415..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/entity/entitytype.h" -#include "rpg/overworld/worldpos.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - entitytype_t entityType; - worldpos_t position; -} cutsceneentityadd_t; - -/** - * Starts an entity add step (spawns the entity into the world immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneEntityAddStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an entity add step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneEntityAddUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an ENTITY_ADD cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneEntityAddLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.c deleted file mode 100644 index 3a1a02c2..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.c +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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" -#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" - -void cutsceneEntityRemoveStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - entity_t *entity = - cutsceneSystemGetEntity(item->entityRemove.entityIndex); - entitySetChunk(entity, CHUNK_INDEX_INVALID); - entity->type = ENTITY_TYPE_NULL; -} - -bool_t cutsceneEntityRemoveUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneEntityRemoveLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonGetEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), &item->entityRemove.entityIndex - )) { - errorThrow("Cutscene item (ENTITY_REMOVE): invalid 'entityIndex'"); - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h deleted file mode 100644 index 26eb81d1..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - uint8_t entityIndex; -} cutsceneentityremove_t; - -/** - * Starts an entity remove step (removes the entity from the world immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneEntityRemoveStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an entity remove step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneEntityRemoveUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an ENTITY_REMOVE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneEntityRemoveLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.c deleted file mode 100644 index b63608fb..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.c +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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" -#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneEntityTeleportStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - entityPositionSet( - cutsceneSystemGetEntity(item->entityTeleport.entityIndex), - item->entityTeleport.target - ); -} - -bool_t cutsceneEntityTeleportUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneEntityTeleportLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonGetEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), - &item->entityTeleport.entityIndex - )) { - errorThrow("Cutscene item (ENTITY_TELEPORT): invalid 'entityIndex'"); - } - if(!cutsceneJsonReadWorldPos( - yyjson_obj_get(itemObj, "target"), &item->entityTeleport.target - )) { - errorThrow( - "Cutscene item (ENTITY_TELEPORT): 'target' must be [x, y, z]" - ); - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h deleted file mode 100644 index 029771d9..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/worldpos.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - uint8_t entityIndex; - worldpos_t target; -} cutsceneentityteleport_t; - -/** - * Starts an entity teleport item (teleports the entity immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneEntityTeleportStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an entity teleport item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneEntityTeleportUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an ENTITY_TELEPORT cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneEntityTeleportLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c deleted file mode 100644 index 00b7f7cb..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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" -#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" -#include "rpg/cutscene/item/json/cutscenejsonentitydir.h" - -void cutsceneEntityTurnStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { -} - -bool_t cutsceneEntityTurnUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - entity_t *entity = cutsceneSystemGetEntity(item->entityTurn.entityIndex); - if( - entity->direction == item->entityTurn.direction && - entity->animation == ENTITY_ANIM_IDLE - ) return true; - - entityTurn(entity, item->entityTurn.direction); - return false; -} - -errorret_t cutsceneEntityTurnLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonGetEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), &item->entityTurn.entityIndex - )) { - errorThrow("Cutscene item (ENTITY_TURN): invalid 'entityIndex'"); - } - entitydir_t dirValue; - if(!cutsceneJsonLookupEntityDir( - yyjson_get_str(yyjson_obj_get(itemObj, "direction")), &dirValue - )) { - errorThrow("Cutscene item (ENTITY_TURN): invalid 'direction'"); - } - item->entityTurn.direction = dirValue; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h deleted file mode 100644 index 164deaf7..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/entity/entitydir.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - uint8_t entityIndex; - entitydir_t direction; -} cutsceneentityturn_t; - -/** - * Starts an entity turn step. The turn itself is driven from Update, since - * the entity may still be finishing a previous action. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneEntityTurnStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an entity turn step, retrying entityTurn until it takes effect - * and its animation completes. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once the entity is idle and facing the target direction. - */ -bool_t cutsceneEntityTurnUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an ENTITY_TURN cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneEntityTurnLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.c deleted file mode 100644 index 16a82a08..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.c +++ /dev/null @@ -1,85 +0,0 @@ -/** - * 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/entitypathstep.h" -#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" -#include "assert/assert.h" - -void cutsceneEntityWalkToStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - assertTrue( - item->entityWalkTo.count <= CUTSCENE_ENTITY_WALK_TO_MAX, - "Too many waypoints in CUTSCENE_ENTITY_WALK_TO" - ); - data->entityWalkTo.currentIndex = 0; -} - -bool_t cutsceneEntityWalkToUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - uint8_t i = data->entityWalkTo.currentIndex; - entity_t *e = cutsceneSystemGetEntity(item->entityWalkTo.entityIndex); - if(!entityPathStep( - e, - item->entityWalkTo.positions[i], - item->entityWalkTo.walkAround - )) return false; - i++; - if(i < item->entityWalkTo.count) { - data->entityWalkTo.currentIndex = i; - return false; - } - return true; -} - -errorret_t cutsceneEntityWalkToLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonGetEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), &item->entityWalkTo.entityIndex - )) { - errorThrow("Cutscene item (ENTITY_WALK_TO): invalid 'entityIndex'"); - } - item->entityWalkTo.walkAround = - cutsceneJsonGetBool(itemObj, "walkAround", true); - - yyjson_val *positions = yyjson_obj_get(itemObj, "positions"); - size_t count = yyjson_arr_size(positions); - if( - !yyjson_is_arr(positions) || count == 0 || - count > CUTSCENE_ENTITY_WALK_TO_MAX - ) { - errorThrow( - "Cutscene item (ENTITY_WALK_TO): 'positions' must have 1-%u entries " - "of [x, y, z]", - (uint32_t)CUTSCENE_ENTITY_WALK_TO_MAX - ); - } - - yyjson_arr_iter iter = yyjson_arr_iter_with(positions); - yyjson_val *posVal; - size_t i = 0; - while((posVal = yyjson_arr_iter_next(&iter)) != NULL) { - if(!cutsceneJsonReadWorldPos(posVal, &item->entityWalkTo.positions[i])) { - errorThrow( - "Cutscene item (ENTITY_WALK_TO): position %u must be [x, y, z]", - (uint32_t)i - ); - } - i++; - } - item->entityWalkTo.count = (uint8_t)count; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h deleted file mode 100644 index 2ad31f09..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/worldpos.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -// Maximum number of waypoints a single ENTITY_WALK_TO/ -// CUTSCENE_ENTITY_WALK_PATH may hold - generous relative to every current -// asset/C-authored cutscene (none use more than a couple waypoints). -#define CUTSCENE_ENTITY_WALK_TO_MAX 16 - -typedef struct { - uint8_t entityIndex; - worldpos_t positions[CUTSCENE_ENTITY_WALK_TO_MAX]; - uint8_t count; - bool_t walkAround; -} cutsceneentitywalkto_t; - -typedef struct { - uint8_t currentIndex; -} cutsceneentitywalktodata_t; - -/** - * Starts an entity walk-to item (resets the waypoint index). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneEntityWalkToStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an entity walk-to item (steps the entity toward the next waypoint). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once all waypoints have been reached. - */ -bool_t cutsceneEntityWalkToUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an ENTITY_WALK_TO cutscene item's JSON fields into item, writing - * its "positions" array directly into item->entityWalkTo.positions (at - * most CUTSCENE_ENTITY_WALK_TO_MAX entries). - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneEntityWalkToLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c deleted file mode 100644 index 208beea7..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c +++ /dev/null @@ -1,65 +0,0 @@ -/** - * 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" -#include "rpg/entity/entitypathstep.h" -#include "rpg/overworld/map.h" -#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" - -void cutsceneEntityWalkToEntityStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { -} - -bool_t cutsceneEntityWalkToEntityUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - entity_t *entity = cutsceneSystemGetEntity( - item->entityWalkToEntity.entityIndex - ); - entity_t *target = cutsceneSystemGetEntity( - item->entityWalkToEntity.targetEntityIndex - ); - - worldpos_t dest = { - .x = (worldunit_t)(target->position.x + item->entityWalkToEntity.offsetX), - .y = (worldunit_t)(target->position.y + item->entityWalkToEntity.offsetY), - .z = target->position.z - }; - - worldunit_t z; - if(mapGetWalkableZNear(dest.x, dest.y, target->position.z, &z)) dest.z = z; - - return entityPathStep(entity, dest, true); -} - -errorret_t cutsceneEntityWalkToEntityLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonGetEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), - &item->entityWalkToEntity.entityIndex - ) || !cutsceneJsonGetEntityIndex( - yyjson_obj_get(itemObj, "targetEntityIndex"), - &item->entityWalkToEntity.targetEntityIndex - )) { - errorThrow( - "Cutscene item (ENTITY_WALK_TO_ENTITY): invalid entity index" - ); - } - item->entityWalkToEntity.offsetX = - (worldunit_t)yyjson_get_int(yyjson_obj_get(itemObj, "offsetX")); - item->entityWalkToEntity.offsetY = - (worldunit_t)yyjson_get_int(yyjson_obj_get(itemObj, "offsetY")); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h b/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h deleted file mode 100644 index 2dc0c254..00000000 --- a/src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/worldpos.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - uint8_t entityIndex; - uint8_t targetEntityIndex; - worldunit_t offsetX; - worldunit_t offsetY; -} cutsceneentitywalktoentity_t; - -/** - * Starts an entity walk-to-entity item. No setup is needed, the destination - * is recomputed from the target's live position every Update. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneEntityWalkToEntityStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates an entity walk-to-entity item. Re-reads the target entity's - * current position each frame, applies the X/Y offset, resolves the - * destination Z from nearby terrain (to account for ramps), and steps - * the entity toward it. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once the entity has reached the target's side. - */ -bool_t cutsceneEntityWalkToEntityUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an ENTITY_WALK_TO_ENTITY cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneEntityWalkToEntityLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.c b/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.c deleted file mode 100644 index 975aada8..00000000 --- a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.c +++ /dev/null @@ -1,36 +0,0 @@ -/** - * 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/item/itemgive.h" -#include "ui/rpg/textbox/uitextboxmain.h" - -void cutsceneItemGiveStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - itemGive(item->itemGive.item, item->itemGive.quantity); -} - -bool_t cutsceneItemGiveUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return !uiTextboxMainIsActive(); -} - -errorret_t cutsceneItemGiveLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - item->itemGive.item = - (itemid_t)yyjson_get_int(yyjson_obj_get(itemObj, "item")); - item->itemGive.quantity = - (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "quantity")); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h b/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h deleted file mode 100644 index 06762ba6..00000000 --- a/src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/item/item.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - itemid_t item; - uint8_t quantity; -} cutsceneitemgive_t; - -/** - * Starts a give-item step (adds the item to the player's backpack immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneItemGiveStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a give-item step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneItemGiveUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses an ITEM_GIVE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneItemGiveLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/json/CMakeLists.txt b/src/dusk/rpg/cutscene/item/json/CMakeLists.txt deleted file mode 100644 index 60a8f286..00000000 --- a/src/dusk/rpg/cutscene/item/json/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutscenejsonenum.c - cutscenejsonfield.c - cutscenejsonentityindex.c - cutscenejsonareaid.c - cutscenejsonpauseflags.c - cutscenejsoneasing.c - cutscenejsonemoji.c - cutscenejsonentitytype.c - cutscenejsonentitydir.c - cutscenejsonbattlestate.c - cutscenejsonscenetype.c - cutscenejsonuiscreentype.c - cutscenejsonaudiochannel.c - cutscenejsonstoryflag.c -) diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.c deleted file mode 100644 index 36d1db38..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.c +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonareaid.h" -#include "util/string.h" - -bool_t cutsceneJsonGetAreaId(yyjson_val *val, uint8_t *outAreaId) { - if(yyjson_is_str(val)) { - const char_t *name = yyjson_get_str(val); - if(stringCompareInsensitive(name, "LAST_CREATED") == 0) { - *outAreaId = CUTSCENE_JSON_AREA_SENTINEL_LAST_CREATED; - return true; - } - return false; - } - if(!yyjson_is_int(val)) return false; - *outAreaId = (uint8_t)yyjson_get_int(val); - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.h deleted file mode 100644 index d12ee505..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.h +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "yyjson.h" - -// Sentinel accepted in place of a literal area id - see cutscenesystem.h's -// CUTSCENE_REF_AREA_LAST_CREATED. Kept local since it's a JSON-authoring -// convenience, not part of the item struct definitions themselves. -#define CUTSCENE_JSON_AREA_SENTINEL_LAST_CREATED ((uint8_t)0xFF) - -/** - * Resolves a JSON value that's either the "LAST_CREATED" map area sentinel - * string or a plain integer area id. - * - * @param val JSON value to resolve (a string or a number). - * @param outAreaId Set to the resolved area id on success. - * @return true if val resolved to a valid area id, false otherwise. - */ -bool_t cutsceneJsonGetAreaId(yyjson_val *val, uint8_t *outAreaId); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.c deleted file mode 100644 index 065a6245..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.c +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonaudiochannel.h" -#include "cutscenejsonenum.h" - -static const cutscenejsonenumentry_t CUTSCENE_JSON_AUDIO_CHANNEL_TABLE[] = { - { "BGM_0", AUDIO_MIXER_CHANNEL_BGM_0 }, - { "VOICE_0", AUDIO_MIXER_CHANNEL_VOICE_0 }, - { "VOICE_1", AUDIO_MIXER_CHANNEL_VOICE_1 }, - { "SFX_0", AUDIO_MIXER_CHANNEL_SFX_0 }, - { "SFX_1", AUDIO_MIXER_CHANNEL_SFX_1 }, - { "SFX_2", AUDIO_MIXER_CHANNEL_SFX_2 }, - { "SFX_3", AUDIO_MIXER_CHANNEL_SFX_3 }, -}; -#define CUTSCENE_JSON_AUDIO_CHANNEL_TABLE_COUNT \ - (sizeof(CUTSCENE_JSON_AUDIO_CHANNEL_TABLE) / \ - sizeof(cutscenejsonenumentry_t)) - -bool_t cutsceneJsonLookupAudioChannel( - const char_t *name, - audiomixerchannel_t *out -) { - int32_t value; - if(!cutsceneJsonLookupEnumInsensitive( - CUTSCENE_JSON_AUDIO_CHANNEL_TABLE, - CUTSCENE_JSON_AUDIO_CHANNEL_TABLE_COUNT, name, &value - )) { - return false; - } - *out = (audiomixerchannel_t)value; - return true; -} - -errorret_t cutsceneJsonLoadAudioChannelOnly( - yyjson_val *itemObj, - audiomixerchannel_t *outChannel -) { - if(!cutsceneJsonLookupAudioChannel( - yyjson_get_str(yyjson_obj_get(itemObj, "channel")), outChannel - )) { - errorThrow("Cutscene item: invalid 'channel'"); - } - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.h deleted file mode 100644 index c6e2234f..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.h +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "error/error.h" -#include "yyjson.h" -#include "audio/mixer/audiomixerchannel.h" - -/** - * Looks up name (case-insensitively) against the audio mixer channel - * table. - * - * @param name Name to search for. - * @param out Set to the matching channel on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupAudioChannel( - const char_t *name, - audiomixerchannel_t *out -); - -/** - * Reads itemObj's "channel" field via cutsceneJsonLookupAudioChannel, - * erroring if missing/invalid - shared by every channel-only item type - * (AUDIO_STOP/PAUSE/RESUME/FADE_WAIT), which differ only in which item - * union field they store the result in. - * - * @param itemObj The item's JSON object. - * @param outChannel Set to the parsed channel on success. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneJsonLoadAudioChannelOnly( - yyjson_val *itemObj, - audiomixerchannel_t *outChannel -); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.c deleted file mode 100644 index 60993646..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.c +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonbattlestate.h" -#include "cutscenejsonenum.h" - -static const cutscenejsonenumentry_t CUTSCENE_JSON_BATTLE_STATE_TABLE[] = { - { "OPENING", BATTLE_STATE_OPENING }, - { "PRE_ROUND", BATTLE_STATE_PRE_ROUND }, - { "SELECTION", BATTLE_STATE_SELECTION }, - { "EXECUTING", BATTLE_STATE_EXECUTING }, - { "POST_ROUND", BATTLE_STATE_POST_ROUND }, - { "LOST", BATTLE_STATE_LOST }, - { "WON", BATTLE_STATE_WON }, - { "FLED", BATTLE_STATE_FLED }, - { "ENDING", BATTLE_STATE_ENDING }, -}; -#define CUTSCENE_JSON_BATTLE_STATE_TABLE_COUNT \ - (sizeof(CUTSCENE_JSON_BATTLE_STATE_TABLE) / sizeof(cutscenejsonenumentry_t)) - -bool_t cutsceneJsonLookupBattleState( - const char_t *name, - battlestate_t *out -) { - int32_t value; - if(!cutsceneJsonLookupEnumInsensitive( - CUTSCENE_JSON_BATTLE_STATE_TABLE, - CUTSCENE_JSON_BATTLE_STATE_TABLE_COUNT, name, &value - )) { - return false; - } - *out = (battlestate_t)value; - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.h deleted file mode 100644 index 1cf302f2..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/battle/state/battlestate.h" - -/** - * Looks up name (case-insensitive) against every battlestate_t name. - * - * @param name Name to search for. - * @param out Set to the matching state on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupBattleState( - const char_t *name, - battlestate_t *out -); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.c b/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.c deleted file mode 100644 index b70df9be..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.c +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsoneasing.h" -#include "cutscenejsonenum.h" - -static const cutscenejsonenumentry_t CUTSCENE_JSON_EASING_TABLE[] = { - { "LINEAR", EASING_LINEAR }, - { "IN_SINE", EASING_IN_SINE }, - { "OUT_SINE", EASING_OUT_SINE }, - { "IN_OUT_SINE", EASING_IN_OUT_SINE }, - { "IN_QUAD", EASING_IN_QUAD }, - { "OUT_QUAD", EASING_OUT_QUAD }, - { "IN_OUT_QUAD", EASING_IN_OUT_QUAD }, - { "IN_CUBIC", EASING_IN_CUBIC }, - { "OUT_CUBIC", EASING_OUT_CUBIC }, - { "IN_OUT_CUBIC", EASING_IN_OUT_CUBIC }, - { "IN_QUART", EASING_IN_QUART }, - { "OUT_QUART", EASING_OUT_QUART }, - { "IN_OUT_QUART", EASING_IN_OUT_QUART }, - { "IN_BACK", EASING_IN_BACK }, - { "OUT_BACK", EASING_OUT_BACK }, - { "IN_OUT_BACK", EASING_IN_OUT_BACK }, -}; -#define CUTSCENE_JSON_EASING_TABLE_COUNT \ - (sizeof(CUTSCENE_JSON_EASING_TABLE) / sizeof(cutscenejsonenumentry_t)) - -bool_t cutsceneJsonLookupEasing(const char_t *name, easingtype_t *out) { - int32_t value; - if(!cutsceneJsonLookupEnumInsensitive( - CUTSCENE_JSON_EASING_TABLE, CUTSCENE_JSON_EASING_TABLE_COUNT, name, - &value - )) { - return false; - } - *out = (easingtype_t)value; - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.h b/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.h deleted file mode 100644 index 26d282e5..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "animation/easing.h" - -/** - * Looks up name (case-insensitively) against the easing type table. - * - * @param name Name to search for. - * @param out Set to the matching easing type on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupEasing(const char_t *name, easingtype_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.c deleted file mode 100644 index ce878f41..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.c +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonemoji.h" -#include "cutscenejsonenum.h" - -static const cutscenejsonenumentry_t CUTSCENE_JSON_UI_EMOJI_TABLE[] = { - { "NULL", UI_EMOJI_NULL }, - { "QUESTION_MARK", UI_EMOJI_QUESTION_MARK }, - { "EXCLAMATION_MARK", UI_EMOJI_EXCLAMATION_MARK }, -}; -#define CUTSCENE_JSON_UI_EMOJI_TABLE_COUNT \ - (sizeof(CUTSCENE_JSON_UI_EMOJI_TABLE) / sizeof(cutscenejsonenumentry_t)) - -bool_t cutsceneJsonLookupEmoji(const char_t *name, uiemojitype_t *out) { - int32_t value; - if(!cutsceneJsonLookupEnumInsensitive( - CUTSCENE_JSON_UI_EMOJI_TABLE, CUTSCENE_JSON_UI_EMOJI_TABLE_COUNT, name, - &value - )) { - return false; - } - *out = (uiemojitype_t)value; - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.h deleted file mode 100644 index 55b483c3..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * 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" - -/** - * Looks up name (case-insensitively) against the emoji type table. - * - * @param name Name to search for. - * @param out Set to the matching emoji type on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupEmoji(const char_t *name, uiemojitype_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.c deleted file mode 100644 index 85ac7baa..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.c +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonentitydir.h" -#include "cutscenejsonenum.h" - -static const cutscenejsonenumentry_t CUTSCENE_JSON_ENTITY_DIR_TABLE[] = { - { "NORTH", ENTITY_DIR_NORTH }, - { "EAST", ENTITY_DIR_EAST }, - { "SOUTH", ENTITY_DIR_SOUTH }, - { "WEST", ENTITY_DIR_WEST }, - { "UP", ENTITY_DIR_UP }, - { "RIGHT", ENTITY_DIR_RIGHT }, - { "DOWN", ENTITY_DIR_DOWN }, - { "LEFT", ENTITY_DIR_LEFT }, -}; -#define CUTSCENE_JSON_ENTITY_DIR_TABLE_COUNT \ - (sizeof(CUTSCENE_JSON_ENTITY_DIR_TABLE) / sizeof(cutscenejsonenumentry_t)) - -bool_t cutsceneJsonLookupEntityDir(const char_t *name, entitydir_t *out) { - int32_t value; - if(!cutsceneJsonLookupEnumInsensitive( - CUTSCENE_JSON_ENTITY_DIR_TABLE, CUTSCENE_JSON_ENTITY_DIR_TABLE_COUNT, - name, &value - )) { - return false; - } - *out = (entitydir_t)value; - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.h deleted file mode 100644 index b3a3d226..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "rpg/entity/entitydir.h" - -/** - * Looks up name (case-insensitively) against the entity direction table. - * - * @param name Name to search for. - * @param out Set to the matching direction on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupEntityDir(const char_t *name, entitydir_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.c deleted file mode 100644 index 9cc076bf..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.c +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonentityindex.h" -#include "util/string.h" - -bool_t cutsceneJsonGetEntityIndex(yyjson_val *val, uint8_t *outIndex) { - if(yyjson_is_str(val)) { - const char_t *name = yyjson_get_str(val); - if(stringCompareInsensitive(name, "INTERACT") == 0) { - *outIndex = CUTSCENE_JSON_ENTITY_SENTINEL_INTERACT; - return true; - } - if(stringCompareInsensitive(name, "INTERACTED") == 0) { - *outIndex = CUTSCENE_JSON_ENTITY_SENTINEL_INTERACTED; - return true; - } - return false; - } - if(!yyjson_is_int(val)) return false; - *outIndex = (uint8_t)yyjson_get_int(val); - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.h deleted file mode 100644 index e7740d63..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "yyjson.h" - -// Sentinels accepted in place of a literal entity index - see -// cutscenesystem.h's CUTSCENE_ENTITY_INTERACT/INTERACTED. Kept local -// since they're a JSON-authoring convenience, not part of the item -// struct definitions themselves. -#define CUTSCENE_JSON_ENTITY_SENTINEL_INTERACT ((uint8_t)0xFE) -#define CUTSCENE_JSON_ENTITY_SENTINEL_INTERACTED ((uint8_t)0xFD) - -/** - * Resolves a JSON value that's either an entity index sentinel string - * ("INTERACT"/"INTERACTED") or a plain integer entity index. - * - * @param val JSON value to resolve (a string or a number). - * @param outIndex Set to the resolved index on success. - * @return true if val resolved to a valid index, false otherwise. - */ -bool_t cutsceneJsonGetEntityIndex(yyjson_val *val, uint8_t *outIndex); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.c deleted file mode 100644 index ac2f9e13..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.c +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonentitytype.h" -#include "cutscenejsonenum.h" - -static const cutscenejsonenumentry_t CUTSCENE_JSON_ENTITY_TYPE_TABLE[] = { - { "NULL", ENTITY_TYPE_NULL }, - { "PLAYER", ENTITY_TYPE_PLAYER }, - { "NPC", ENTITY_TYPE_NPC }, - { "ITEM", ENTITY_TYPE_ITEM }, -}; -#define CUTSCENE_JSON_ENTITY_TYPE_TABLE_COUNT \ - (sizeof(CUTSCENE_JSON_ENTITY_TYPE_TABLE) / sizeof(cutscenejsonenumentry_t)) - -bool_t cutsceneJsonLookupEntityType(const char_t *name, entitytype_t *out) { - int32_t value; - if(!cutsceneJsonLookupEnumInsensitive( - CUTSCENE_JSON_ENTITY_TYPE_TABLE, CUTSCENE_JSON_ENTITY_TYPE_TABLE_COUNT, - name, &value - )) { - return false; - } - *out = (entitytype_t)value; - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.h deleted file mode 100644 index f08b45e8..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "rpg/entity/entitytype.h" - -/** - * Looks up name (case-insensitively) against the entity type table. - * - * @param name Name to search for. - * @param out Set to the matching entity type on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupEntityType(const char_t *name, entitytype_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.c deleted file mode 100644 index 3c7a1d67..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.c +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonenum.h" -#include "util/string.h" - -bool_t cutsceneJsonLookupEnum( - const cutscenejsonenumentry_t *table, - const size_t tableCount, - const char_t *name, - int32_t *outValue -) { - if(name == NULL) return false; - for(size_t i = 0; i < tableCount; i++) { - if(stringEquals(table[i].name, name)) { - *outValue = table[i].value; - return true; - } - } - return false; -} - -bool_t cutsceneJsonLookupEnumInsensitive( - const cutscenejsonenumentry_t *table, - const size_t tableCount, - const char_t *name, - int32_t *outValue -) { - if(name == NULL) return false; - for(size_t i = 0; i < tableCount; i++) { - if(stringCompareInsensitive(table[i].name, name) == 0) { - *outValue = table[i].value; - return true; - } - } - return false; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.h deleted file mode 100644 index 8130007d..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonenum.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct { - const char_t *name; - int32_t value; -} cutscenejsonenumentry_t; - -/** - * Looks up name in a {name, value} table via stringEquals - exact case, - * used only for the top-level item "type" name (the one enum-ish field - * the original tool's ITEM_TYPE dict looked up without .upper()). - * - * @param table Table to search. - * @param tableCount Number of entries in table. - * @param name Name to search for. - * @param outValue Set to the matching entry's value on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupEnum( - const cutscenejsonenumentry_t *table, - const size_t tableCount, - const char_t *name, - int32_t *outValue -); - -/** - * Looks up name in a {name, value} table via stringCompareInsensitive - - * every other enum-ish field (pause flags, entity/scene/battle/audio - * enums, ...) is authored case-insensitively, matching the original - * tool's ENUM_TABLE[name.upper()] lookups. - * - * @param table Table to search. - * @param tableCount Number of entries in table. - * @param name Name to search for. - * @param outValue Set to the matching entry's value on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupEnumInsensitive( - const cutscenejsonenumentry_t *table, - const size_t tableCount, - const char_t *name, - int32_t *outValue -); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.c deleted file mode 100644 index 25b78d86..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.c +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonfield.h" -#include "util/string.h" - -bool_t cutsceneJsonCopyString( - yyjson_val *obj, - const char_t *key, - const char_t *defaultValue, - char_t *dest, - const size_t destCapacity -) { - yyjson_val *val = yyjson_obj_get(obj, key); - const char_t *str = yyjson_get_str(val); - size_t len; - if(str == NULL) { - if(defaultValue == NULL) return false; - str = defaultValue; - len = (size_t)stringFormat(NULL, 0, "%s", str); - } else { - len = yyjson_get_len(val); - } - if(len >= destCapacity) return false; - stringCopy(dest, str, destCapacity); - return true; -} - -bool_t cutsceneJsonReadWorldPos(yyjson_val *arr, worldpos_t *out) { - if(!yyjson_is_arr(arr) || yyjson_arr_size(arr) != 3) return false; - yyjson_val *x = yyjson_arr_get(arr, 0); - yyjson_val *y = yyjson_arr_get(arr, 1); - yyjson_val *z = yyjson_arr_get(arr, 2); - if(!yyjson_is_int(x) || !yyjson_is_int(y) || !yyjson_is_int(z)) return false; - out->x = (worldunit_t)yyjson_get_int(x); - out->y = (worldunit_t)yyjson_get_int(y); - out->z = (worldunit_t)yyjson_get_int(z); - return true; -} - -int32_t cutsceneJsonGetInt( - yyjson_val *obj, - const char_t *key, - const int32_t defaultValue -) { - yyjson_val *val = yyjson_obj_get(obj, key); - return val == NULL ? defaultValue : (int32_t)yyjson_get_int(val); -} - -float_t cutsceneJsonGetFloat( - yyjson_val *obj, - const char_t *key, - const float_t defaultValue -) { - yyjson_val *val = yyjson_obj_get(obj, key); - return val == NULL ? defaultValue : (float_t)yyjson_get_num(val); -} - -bool_t cutsceneJsonGetBool( - yyjson_val *obj, - const char_t *key, - const bool_t defaultValue -) { - yyjson_val *val = yyjson_obj_get(obj, key); - return val == NULL ? defaultValue : yyjson_get_bool(val); -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.h deleted file mode 100644 index a73f07fe..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonfield.h +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "rpg/overworld/worldpos.h" -#include "yyjson.h" - -/** - * Reads obj[key] as a string (falling back to defaultValue if the key is - * absent and defaultValue is non-NULL) into dest, a fixed-capacity - * char_t[destCapacity] item field. Fails rather than truncating/asserting - * if the string doesn't fit - this is untrusted, authored file content, - * not an in-memory invariant. - * - * @param obj The item's JSON object. - * @param key Field name to read. - * @param defaultValue Value to use if key is absent, or NULL to require it. - * @param dest Destination buffer. - * @param destCapacity Capacity of dest, including the null terminator. - * @return true on success, false if the field is missing (with no - * default) or exceeds destCapacity. - */ -bool_t cutsceneJsonCopyString( - yyjson_val *obj, - const char_t *key, - const char_t *defaultValue, - char_t *dest, - const size_t destCapacity -); - -/** - * Reads a 3-element JSON array of integers into a worldpos_t. - * - * @param arr JSON array value, expected to hold exactly 3 numbers. - * @param out Set to the decoded position on success. - * @return true if arr was a valid 3-element numeric array, false otherwise. - */ -bool_t cutsceneJsonReadWorldPos(yyjson_val *arr, worldpos_t *out); - -/** - * Reads obj[key] as an integer, falling back to defaultValue if the key - * is absent. - * - * @param obj The item's JSON object. - * @param key Field name to read. - * @param defaultValue Value to use if key is absent. - * @return The field's value, or defaultValue if absent. - */ -int32_t cutsceneJsonGetInt( - yyjson_val *obj, - const char_t *key, - const int32_t defaultValue -); - -/** - * Reads obj[key] as a float, falling back to defaultValue if the key is - * absent. - * - * @param obj The item's JSON object. - * @param key Field name to read. - * @param defaultValue Value to use if key is absent. - * @return The field's value, or defaultValue if absent. - */ -float_t cutsceneJsonGetFloat( - yyjson_val *obj, - const char_t *key, - const float_t defaultValue -); - -/** - * Reads obj[key] as a boolean, falling back to defaultValue if the key is - * absent. - * - * @param obj The item's JSON object. - * @param key Field name to read. - * @param defaultValue Value to use if key is absent. - * @return The field's value, or defaultValue if absent. - */ -bool_t cutsceneJsonGetBool( - yyjson_val *obj, - const char_t *key, - const bool_t defaultValue -); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.c deleted file mode 100644 index 29eb1b54..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.c +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonpauseflags.h" -#include "cutscenejsonenum.h" - -static const cutscenejsonenumentry_t CUTSCENE_JSON_PAUSE_FLAG_TABLE[] = { - { "NPC", CUTSCENE_PAUSE_NPC }, - { "PLAYER", CUTSCENE_PAUSE_PLAYER }, - { "WORLD", CUTSCENE_PAUSE_WORLD }, - { "BATTLE", CUTSCENE_PAUSE_BATTLE }, -}; -#define CUTSCENE_JSON_PAUSE_FLAG_TABLE_COUNT \ - (sizeof(CUTSCENE_JSON_PAUSE_FLAG_TABLE) / sizeof(cutscenejsonenumentry_t)) - -bool_t cutsceneJsonParsePauseFlags(yyjson_val *arr, cutscenepause_t *out) { - if(!yyjson_is_arr(arr)) return false; - - cutscenepause_t value = CUTSCENE_PAUSE_NONE; - yyjson_arr_iter iter = yyjson_arr_iter_with(arr); - yyjson_val *flagVal; - while((flagVal = yyjson_arr_iter_next(&iter)) != NULL) { - int32_t flagValue; - if(!cutsceneJsonLookupEnumInsensitive( - CUTSCENE_JSON_PAUSE_FLAG_TABLE, CUTSCENE_JSON_PAUSE_FLAG_TABLE_COUNT, - yyjson_get_str(flagVal), &flagValue - )) { - return false; - } - value |= (cutscenepause_t)flagValue; - } - *out = value; - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.h deleted file mode 100644 index b6bccb6b..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.h +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "rpg/cutscene/cutscenepause.h" -#include "yyjson.h" - -/** - * Parses a JSON array of pause flag name strings, OR-ing each into out. - * Shared by the top-level "pause" field (cutsceneParseDoc) and the - * SET_PAUSE item's "flags" field - both authored as the same shape. - * - * @param arr JSON array of pause flag name strings. - * @param out Set to the OR'd-together flags on success (CUTSCENE_PAUSE_NONE - * if arr is empty). - * @return true on success, false if arr is not an array or contains an - * invalid flag name. - */ -bool_t cutsceneJsonParsePauseFlags(yyjson_val *arr, cutscenepause_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.c deleted file mode 100644 index 70a5616f..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.c +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonscenetype.h" -#include "cutscenejsonenum.h" - -static const cutscenejsonenumentry_t CUTSCENE_JSON_SCENE_TYPE_TABLE[] = { - { "NULL", SCENE_TYPE_NULL }, - { "INITIAL", SCENE_TYPE_INITIAL }, - { "OVERWORLD", SCENE_TYPE_OVERWORLD }, - { "BATTLE", SCENE_TYPE_BATTLE }, -}; -#define CUTSCENE_JSON_SCENE_TYPE_TABLE_COUNT \ - (sizeof(CUTSCENE_JSON_SCENE_TYPE_TABLE) / sizeof(cutscenejsonenumentry_t)) - -bool_t cutsceneJsonLookupSceneType(const char_t *name, scenetype_t *out) { - int32_t value; - if(!cutsceneJsonLookupEnumInsensitive( - CUTSCENE_JSON_SCENE_TYPE_TABLE, CUTSCENE_JSON_SCENE_TYPE_TABLE_COUNT, - name, &value - )) { - return false; - } - *out = (scenetype_t)value; - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.h deleted file mode 100644 index 58a38271..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "scene/scenetype.h" - -/** - * Looks up name (case-insensitively) against the scene type table. - * - * @param name Name to search for. - * @param out Set to the matching scene type on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupSceneType(const char_t *name, scenetype_t *out); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonstoryflag.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonstoryflag.c deleted file mode 100644 index 2eb14384..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonstoryflag.c +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonstoryflag.h" - -bool_t cutsceneJsonGetStoryFlag(yyjson_val *val, storyflag_t *outFlag) { - if(!yyjson_is_str(val)) return false; - - storyflag_t flag = storyGetFlagByName(yyjson_get_str(val)); - if(flag >= STORY_FLAG_COUNT) return false; - - *outFlag = flag; - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonstoryflag.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonstoryflag.h deleted file mode 100644 index d5152fde..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonstoryflag.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "yyjson.h" -#include "rpg/story/story.h" - -/** - * Resolves a JSON string value naming a story flag (via storyGetFlagByName) - * into a storyflag_t. - * - * @param val JSON value to resolve (a string). - * @param outFlag Set to the resolved story flag id on success. - * @return true if val resolved to a known story flag, false otherwise. - */ -bool_t cutsceneJsonGetStoryFlag(yyjson_val *val, storyflag_t *outFlag); diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.c b/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.c deleted file mode 100644 index 4d53a349..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.c +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "cutscenejsonuiscreentype.h" -#include "cutscenejsonenum.h" - -static const cutscenejsonenumentry_t CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE[] = { - { "MAIN_MENU", UI_SCREEN_TYPE_MAIN_MENU }, -}; -#define CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE_COUNT \ - (sizeof(CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE) / \ - sizeof(cutscenejsonenumentry_t)) - -bool_t cutsceneJsonLookupUiScreenType( - const char_t *name, - uiscreentype_t *out -) { - int32_t value; - if(!cutsceneJsonLookupEnumInsensitive( - CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE, - CUTSCENE_JSON_UI_SCREEN_TYPE_TABLE_COUNT, name, &value - )) { - return false; - } - *out = (uiscreentype_t)value; - return true; -} diff --git a/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.h b/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.h deleted file mode 100644 index fd1f984d..00000000 --- a/src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "rpg/cutscene/item/ui/cutsceneuishow.h" - -/** - * Looks up name (case-insensitively) against the UI screen type table. - * - * @param name Name to search for. - * @param out Set to the matching screen type on success. - * @return true if a matching entry was found, false otherwise. - */ -bool_t cutsceneJsonLookupUiScreenType( - const char_t *name, - uiscreentype_t *out -); diff --git a/src/dusk/rpg/cutscene/item/maparea/CMakeLists.txt b/src/dusk/rpg/cutscene/item/maparea/CMakeLists.txt deleted file mode 100644 index cd32d7f6..00000000 --- a/src/dusk/rpg/cutscene/item/maparea/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutscenemapareaadd.c - cutscenemaparearemove.c - cutscenemapareawait.c -) diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.c b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.c deleted file mode 100644 index 8b408a0b..00000000 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.c +++ /dev/null @@ -1,29 +0,0 @@ -/** - * 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" - -void cutsceneMapAreaAddStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - CUTSCENE_SYSTEM.refs.areaLastCreated = mapAreaAdd( - item->mapAreaAdd.min, - item->mapAreaAdd.max, - item->mapAreaAdd.callback, - item->mapAreaAdd.notify, - item->mapAreaAdd.trigger - ); -} - -bool_t cutsceneMapAreaAddUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h deleted file mode 100644 index b4135a65..00000000 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "rpg/overworld/maparea.h" -#include "rpg/cutscene/item/maparea/cutscenemapareawait.h" -#include "rpg/cutscene/item/maparea/cutscenemaparearemove.h" - -typedef struct { - worldpos_t min; - worldpos_t max; - mapareacallback_t callback; - uint8_t notify; - uint8_t trigger; -} cutscenemapareaadd_t; - -/** - * Starts a map area add step (adds the area immediately, storing its ID - * in CUTSCENE_SYSTEM.refs.areaLastCreated). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneMapAreaAddStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a map area add step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneMapAreaAddUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.c b/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.c deleted file mode 100644 index 47a0065d..00000000 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.c +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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/overworld/maparea.h" -#include "rpg/cutscene/item/json/cutscenejsonareaid.h" - -void cutsceneMapAreaRemoveStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - mapAreaRemove(cutsceneSystemGetAreaId(item->mapAreaRemove.areaId)); -} - -bool_t cutsceneMapAreaRemoveUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneMapAreaRemoveLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonGetAreaId( - yyjson_obj_get(itemObj, "areaId"), &item->mapAreaRemove.areaId - )) { - errorThrow("Cutscene item (MAP_AREA_REMOVE): invalid 'areaId'"); - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h b/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h deleted file mode 100644 index c85ba9be..00000000 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - uint8_t areaId; -} cutscenemaparearemove_t; - -/** - * Starts a map area remove step (removes the area immediately). Accepts - * CUTSCENE_REF_AREA_LAST_CREATED in place of a literal area ID. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneMapAreaRemoveStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a map area remove step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneMapAreaRemoveUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a MAP_AREA_REMOVE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneMapAreaRemoveLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.c b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.c deleted file mode 100644 index 1acbb963..00000000 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.c +++ /dev/null @@ -1,72 +0,0 @@ -/** - * 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/overworld/maparea.h" -#include "assert/assert.h" -#include "rpg/cutscene/item/json/cutscenejsonareaid.h" - -void cutsceneMapAreaWaitStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - assertTrue( - item->mapAreaWait.count <= CUTSCENE_MAP_AREA_WAIT_MAX, - "Too many areas in CUTSCENE_MAP_AREA_WAIT" - ); - - for(uint8_t i = 0; i < item->mapAreaWait.count; i++) { - uint8_t areaId = cutsceneSystemGetAreaId(item->mapAreaWait.areaIds[i]); - data->mapAreaWait.baseline[i] = MAP_AREAS[areaId].triggerCount; - } -} - -bool_t cutsceneMapAreaWaitUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - for(uint8_t i = 0; i < item->mapAreaWait.count; i++) { - uint8_t areaId = cutsceneSystemGetAreaId(item->mapAreaWait.areaIds[i]); - if(MAP_AREAS[areaId].triggerCount != data->mapAreaWait.baseline[i]) { - return true; - } - } - - return false; -} - -errorret_t cutsceneMapAreaWaitLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - yyjson_val *areaIds = yyjson_obj_get(itemObj, "areaIds"); - size_t count = yyjson_arr_size(areaIds); - if( - !yyjson_is_arr(areaIds) || count == 0 || - count > CUTSCENE_MAP_AREA_WAIT_MAX - ) { - errorThrow( - "Cutscene item (MAP_AREA_WAIT): 'areaIds' must have 1-%u entries", - (uint32_t)CUTSCENE_MAP_AREA_WAIT_MAX - ); - } - yyjson_arr_iter iter = yyjson_arr_iter_with(areaIds); - yyjson_val *idVal; - size_t i = 0; - while((idVal = yyjson_arr_iter_next(&iter)) != NULL) { - if(!cutsceneJsonGetAreaId(idVal, &item->mapAreaWait.areaIds[i])) { - errorThrow( - "Cutscene item (MAP_AREA_WAIT): areaIds[%u] is invalid", (uint32_t)i - ); - } - i++; - } - item->mapAreaWait.count = (uint8_t)count; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h b/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h deleted file mode 100644 index 0787170f..00000000 --- a/src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** Maximum number of areas a single CUTSCENE_MAP_AREA_WAIT may watch. */ -#define CUTSCENE_MAP_AREA_WAIT_MAX 4 - -typedef struct { - uint8_t areaIds[CUTSCENE_MAP_AREA_WAIT_MAX]; - uint8_t count; -} cutscenemapareawait_t; - -typedef struct { - uint32_t baseline[CUTSCENE_MAP_AREA_WAIT_MAX]; -} cutscenemapareawaitdata_t; - -/** - * Starts a map area wait step, snapshotting each watched area's current - * trigger count. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneMapAreaWaitStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a map area wait step, completing once any watched area's - * trigger count has changed since Start (i.e. its callback fired). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once any watched area has been triggered. - */ -bool_t cutsceneMapAreaWaitUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a MAP_AREA_WAIT cutscene item's JSON fields into item, writing its - * "areaIds" array directly into item->mapAreaWait.areaIds. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneMapAreaWaitLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/party/CMakeLists.txt b/src/dusk/rpg/cutscene/item/party/CMakeLists.txt deleted file mode 100644 index 6f383c3e..00000000 --- a/src/dusk/rpg/cutscene/item/party/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutscenepartyaddmember.c - cutscenepartyremovemember.c - cutscenepartyhealmember.c - cutscenepartyhealall.c -) diff --git a/src/dusk/rpg/cutscene/item/party/cutscenepartyaddmember.c b/src/dusk/rpg/cutscene/item/party/cutscenepartyaddmember.c deleted file mode 100644 index 9612132e..00000000 --- a/src/dusk/rpg/cutscene/item/party/cutscenepartyaddmember.c +++ /dev/null @@ -1,42 +0,0 @@ -/** - * 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/battle/party.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutscenePartyAddMemberStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - partyAddMember(item->partyAddMember.memberIndex); -} - -bool_t cutscenePartyAddMemberUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutscenePartyAddMemberLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - char_t key[PARTY_KEY_LENGTH_MAX]; - if(!cutsceneJsonCopyString(itemObj, "key", NULL, key, PARTY_KEY_LENGTH_MAX)) { - errorThrow("Cutscene item (PARTY_ADD_MEMBER): missing/invalid 'key'"); - } - - const uint8_t memberIndex = partyGetMemberIndexByKey(key); - if(memberIndex == PARTY_MEMBER_NOT_FOUND) { - errorThrow("Cutscene item (PARTY_ADD_MEMBER): no member with key '%s'", key); - } - item->partyAddMember.memberIndex = memberIndex; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/party/cutscenepartyaddmember.h b/src/dusk/rpg/cutscene/item/party/cutscenepartyaddmember.h deleted file mode 100644 index 28768ef1..00000000 --- a/src/dusk/rpg/cutscene/item/party/cutscenepartyaddmember.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - // Resolved from the JSON 'key' field at load time (see - // partyGetMemberIndexByKey) - not a raw JSON field itself. - uint8_t memberIndex; -} cutscenepartyaddmember_t; - -/** - * Starts a PARTY_ADD_MEMBER item, activating the configured existing - * roster member into the current save slot's active party order (see - * partyAddMember). No-op if already active or there's no free order slot. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutscenePartyAddMemberStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a PARTY_ADD_MEMBER item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutscenePartyAddMemberUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a PARTY_ADD_MEMBER cutscene item's JSON fields into item: - * 'key', a required string matching a party.jsonc member key, resolved to - * its roster index via partyGetMemberIndexByKey. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutscenePartyAddMemberLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/party/cutscenepartyhealall.c b/src/dusk/rpg/cutscene/item/party/cutscenepartyhealall.c deleted file mode 100644 index 3b3aa111..00000000 --- a/src/dusk/rpg/cutscene/item/party/cutscenepartyhealall.c +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 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/battle/party.h" - -void cutscenePartyHealAllStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - partyHealAll(); -} - -bool_t cutscenePartyHealAllUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} diff --git a/src/dusk/rpg/cutscene/item/party/cutscenepartyhealall.h b/src/dusk/rpg/cutscene/item/party/cutscenepartyhealall.h deleted file mode 100644 index e2d12858..00000000 --- a/src/dusk/rpg/cutscene/item/party/cutscenepartyhealall.h +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -/** - * Starts a PARTY_HEAL_ALL item, fully restoring every occupied slot in - * the current save slot's party roster (see partyHealAll). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutscenePartyHealAllStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a PARTY_HEAL_ALL item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutscenePartyHealAllUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); diff --git a/src/dusk/rpg/cutscene/item/party/cutscenepartyhealmember.c b/src/dusk/rpg/cutscene/item/party/cutscenepartyhealmember.c deleted file mode 100644 index a4091e4e..00000000 --- a/src/dusk/rpg/cutscene/item/party/cutscenepartyhealmember.c +++ /dev/null @@ -1,42 +0,0 @@ -/** - * 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/battle/party.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutscenePartyHealMemberStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - partyHealMember(item->partyHealMember.memberIndex); -} - -bool_t cutscenePartyHealMemberUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutscenePartyHealMemberLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - char_t key[PARTY_KEY_LENGTH_MAX]; - if(!cutsceneJsonCopyString(itemObj, "key", NULL, key, PARTY_KEY_LENGTH_MAX)) { - errorThrow("Cutscene item (PARTY_HEAL_MEMBER): missing/invalid 'key'"); - } - - const uint8_t memberIndex = partyGetMemberIndexByKey(key); - if(memberIndex == PARTY_MEMBER_NOT_FOUND) { - errorThrow("Cutscene item (PARTY_HEAL_MEMBER): no member with key '%s'", key); - } - item->partyHealMember.memberIndex = memberIndex; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/party/cutscenepartyhealmember.h b/src/dusk/rpg/cutscene/item/party/cutscenepartyhealmember.h deleted file mode 100644 index 3a36a86c..00000000 --- a/src/dusk/rpg/cutscene/item/party/cutscenepartyhealmember.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - // Resolved from the JSON 'key' field at load time (see - // partyGetMemberIndexByKey) - not a raw JSON field itself. - uint8_t memberIndex; -} cutscenepartyhealmember_t; - -/** - * Starts a PARTY_HEAL_MEMBER item, fully restoring the configured member's - * health/mp and reviving it if dead (see partyHealMember). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutscenePartyHealMemberStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a PARTY_HEAL_MEMBER item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutscenePartyHealMemberUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a PARTY_HEAL_MEMBER cutscene item's JSON fields into item: - * 'key', a required string matching a party.jsonc member key, resolved to - * its roster index via partyGetMemberIndexByKey. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutscenePartyHealMemberLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/party/cutscenepartyremovemember.c b/src/dusk/rpg/cutscene/item/party/cutscenepartyremovemember.c deleted file mode 100644 index e636d75e..00000000 --- a/src/dusk/rpg/cutscene/item/party/cutscenepartyremovemember.c +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 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/battle/party.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutscenePartyRemoveMemberStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - partyRemoveMember(item->partyRemoveMember.memberIndex); -} - -bool_t cutscenePartyRemoveMemberUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutscenePartyRemoveMemberLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - char_t key[PARTY_KEY_LENGTH_MAX]; - if(!cutsceneJsonCopyString(itemObj, "key", NULL, key, PARTY_KEY_LENGTH_MAX)) { - errorThrow("Cutscene item (PARTY_REMOVE_MEMBER): missing/invalid 'key'"); - } - - const uint8_t memberIndex = partyGetMemberIndexByKey(key); - if(memberIndex == PARTY_MEMBER_NOT_FOUND) { - errorThrow( - "Cutscene item (PARTY_REMOVE_MEMBER): no member with key '%s'", key - ); - } - item->partyRemoveMember.memberIndex = memberIndex; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/party/cutscenepartyremovemember.h b/src/dusk/rpg/cutscene/item/party/cutscenepartyremovemember.h deleted file mode 100644 index 91809aea..00000000 --- a/src/dusk/rpg/cutscene/item/party/cutscenepartyremovemember.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - // Resolved from the JSON 'key' field at load time (see - // partyGetMemberIndexByKey) - not a raw JSON field itself. - uint8_t memberIndex; -} cutscenepartyremovemember_t; - -/** - * Starts a PARTY_REMOVE_MEMBER item, deactivating the configured member - * from the current save slot's active party order (see partyRemoveMember) - * - the roster member itself still exists, just no longer active. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutscenePartyRemoveMemberStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a PARTY_REMOVE_MEMBER item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutscenePartyRemoveMemberUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a PARTY_REMOVE_MEMBER cutscene item's JSON fields into item: - * 'key', a required string matching a party.jsonc member key, resolved to - * its roster index via partyGetMemberIndexByKey. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutscenePartyRemoveMemberLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/save/CMakeLists.txt b/src/dusk/rpg/cutscene/item/save/CMakeLists.txt deleted file mode 100644 index 7ca216ee..00000000 --- a/src/dusk/rpg/cutscene/item/save/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutscenesavedevicecheck.c - cutscenesaveloadallslots.c -) diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c deleted file mode 100644 index 76d3a3fa..00000000 --- a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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/cutscene/item/json/cutscenejsonfield.h" -#include "save/save.h" - -void cutsceneSaveDeviceCheckCallback(savedevice_t *device, void *user) { - const cutsceneitem_t *item = cutsceneSystemGetCurrentItem(); - cutsceneSystemGoTo( - device != NULL ? - item->saveDeviceCheck.successMarker : - item->saveDeviceCheck.failureMarker - ); -} - -void cutsceneSaveDeviceCheckStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - saveFindAvailableDevice(cutsceneSaveDeviceCheckCallback, NULL); -} - -bool_t cutsceneSaveDeviceCheckUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return false; -} - -errorret_t cutsceneSaveDeviceCheckLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if( - !cutsceneJsonCopyString( - itemObj, "successMarker", NULL, item->saveDeviceCheck.successMarker, - CUTSCENE_MARKER_NAME_MAX - ) || - !cutsceneJsonCopyString( - itemObj, "failureMarker", NULL, item->saveDeviceCheck.failureMarker, - CUTSCENE_MARKER_NAME_MAX - ) - ) { - errorThrow( - "Cutscene item (SAVE_DEVICE_CHECK): needs 'successMarker'/" - "'failureMarker'" - ); - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h b/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h deleted file mode 100644 index 3998dd4c..00000000 --- a/src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "rpg/cutscene/item/control/cutscenemarker.h" - -typedef struct { - char_t successMarker[CUTSCENE_MARKER_NAME_MAX]; - char_t failureMarker[CUTSCENE_MARKER_NAME_MAX]; -} cutscenesavedevicecheck_t; - -/** - * Starts a save-device-check item: (re)requests an available save - * device via saveFindAvailableDevice. Never completes on its own - see - * cutsceneSaveDeviceCheckUpdate. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneSaveDeviceCheckStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a save-device-check item. Like a CUTSCENE_MODAL_OPTIONS item, - * this never completes on its own - once saveFindAvailableDevice's - * callback fires, it jumps straight to the item's successMarker or - * failureMarker via cutsceneSystemGoTo. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns false always. - */ -bool_t cutsceneSaveDeviceCheckUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a SAVE_DEVICE_CHECK cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneSaveDeviceCheckLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c deleted file mode 100644 index 1b368723..00000000 --- a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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/cutscene/item/json/cutscenejsonfield.h" -#include "save/save.h" - -void cutsceneSaveLoadAllSlotsStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - errorret_t result = saveLoadAllSlots(); - if(errorIsNotOk(result)) { - errorCatch(errorPrint(result)); - cutsceneSystemGoTo(item->saveLoadAllSlots.failureMarker); - return; - } - - cutsceneSystemGoTo(item->saveLoadAllSlots.successMarker); -} - -bool_t cutsceneSaveLoadAllSlotsUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return false; -} - -errorret_t cutsceneSaveLoadAllSlotsLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if( - !cutsceneJsonCopyString( - itemObj, "successMarker", NULL, item->saveLoadAllSlots.successMarker, - CUTSCENE_MARKER_NAME_MAX - ) || - !cutsceneJsonCopyString( - itemObj, "failureMarker", NULL, item->saveLoadAllSlots.failureMarker, - CUTSCENE_MARKER_NAME_MAX - ) - ) { - errorThrow( - "Cutscene item (SAVE_LOAD_ALL_SLOTS): needs 'successMarker'/" - "'failureMarker'" - ); - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h b/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h deleted file mode 100644 index 18b32b9d..00000000 --- a/src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "rpg/cutscene/item/control/cutscenemarker.h" - -typedef struct { - char_t successMarker[CUTSCENE_MARKER_NAME_MAX]; - char_t failureMarker[CUTSCENE_MARKER_NAME_MAX]; -} cutscenesaveloadallslots_t; - -/** - * Starts a save-load-all-slots item: calls saveLoadAllSlots() and jumps - * straight to the item's successMarker or failureMarker via - * cutsceneSystemGoTo, depending on the result. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneSaveLoadAllSlotsStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a save-load-all-slots item. Never completes on its own - - * Start already jumps to whichever marker applies before this would - * ever run. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns false always. - */ -bool_t cutsceneSaveLoadAllSlotsUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a SAVE_LOAD_ALL_SLOTS cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneSaveLoadAllSlotsLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/story/CMakeLists.txt b/src/dusk/rpg/cutscene/item/story/CMakeLists.txt deleted file mode 100644 index c84faff0..00000000 --- a/src/dusk/rpg/cutscene/item/story/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutscenestorysetflag.c - cutscenestorywaitflag.c -) diff --git a/src/dusk/rpg/cutscene/item/story/cutscenestorysetflag.c b/src/dusk/rpg/cutscene/item/story/cutscenestorysetflag.c deleted file mode 100644 index 0939f7bc..00000000 --- a/src/dusk/rpg/cutscene/item/story/cutscenestorysetflag.c +++ /dev/null @@ -1,39 +0,0 @@ -/** - * 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/item/json/cutscenejsonstoryflag.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneStorySetFlagStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - storySetFlagValue(item->storySetFlag.flag, item->storySetFlag.value); -} - -bool_t cutsceneStorySetFlagUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneStorySetFlagLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - yyjson_val *flagVal = yyjson_obj_get(itemObj, "flag"); - if(!cutsceneJsonGetStoryFlag(flagVal, &item->storySetFlag.flag)) { - errorThrow("Cutscene item (STORY_SET_FLAG): invalid or missing 'flag'"); - } - - item->storySetFlag.value = - (storyflagval_t)cutsceneJsonGetInt(itemObj, "value", 1); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/story/cutscenestorysetflag.h b/src/dusk/rpg/cutscene/item/story/cutscenestorysetflag.h deleted file mode 100644 index 8fceb9d8..00000000 --- a/src/dusk/rpg/cutscene/item/story/cutscenestorysetflag.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "rpg/story/story.h" - -typedef struct { - storyflag_t flag; - storyflagval_t value; -} cutscenestorysetflag_t; - -/** - * Starts a story set-flag step, immediately writing the given value to the - * given story flag. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneStorySetFlagStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a story set-flag step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneStorySetFlagUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a STORY_SET_FLAG cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneStorySetFlagLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/story/cutscenestorywaitflag.c b/src/dusk/rpg/cutscene/item/story/cutscenestorywaitflag.c deleted file mode 100644 index df7f65e7..00000000 --- a/src/dusk/rpg/cutscene/item/story/cutscenestorywaitflag.c +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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/item/json/cutscenejsonstoryflag.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -bool_t cutsceneStoryWaitFlagUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return storyGetFlagValue(item->storyWaitFlag.flag) == item->storyWaitFlag.value; -} - -errorret_t cutsceneStoryWaitFlagLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - yyjson_val *flagVal = yyjson_obj_get(itemObj, "flag"); - if(!cutsceneJsonGetStoryFlag(flagVal, &item->storyWaitFlag.flag)) { - errorThrow("Cutscene item (STORY_WAIT_FLAG): invalid or missing 'flag'"); - } - - item->storyWaitFlag.value = - (storyflagval_t)cutsceneJsonGetInt(itemObj, "value", 1); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/story/cutscenestorywaitflag.h b/src/dusk/rpg/cutscene/item/story/cutscenestorywaitflag.h deleted file mode 100644 index d31eeaa0..00000000 --- a/src/dusk/rpg/cutscene/item/story/cutscenestorywaitflag.h +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "rpg/story/story.h" - -typedef struct { - storyflag_t flag; - storyflagval_t value; -} cutscenestorywaitflag_t; - -/** - * Updates a story wait-flag step, completing once the given story flag's - * current value matches the given value. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once the flag's current value matches, false otherwise. - */ -bool_t cutsceneStoryWaitFlagUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a STORY_WAIT_FLAG cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneStoryWaitFlagLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt b/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt deleted file mode 100644 index 5702ce7a..00000000 --- a/src/dusk/rpg/cutscene/item/ui/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - cutscenetext.c - cutscenetextmini.c - cutscenetextminihide.c - cutscenefade.c - cutsceneemoji.c - cutsceneshake.c - cutscenemodal.c - cutscenemodaloptionsmarkers.c - cutscenekeyboard.c - cutsceneuishow.c -) diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c deleted file mode 100644 index 43b4466f..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c +++ /dev/null @@ -1,49 +0,0 @@ -/** - * 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" -#include "rpg/cutscene/item/json/cutscenejsonentityindex.h" -#include "rpg/cutscene/item/json/cutscenejsonemoji.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.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; -} - -errorret_t cutsceneEmojiLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonGetEntityIndex( - yyjson_obj_get(itemObj, "entityIndex"), &item->emoji.entityIndex - )) { - errorThrow("Cutscene item (EMOJI): invalid 'entityIndex'"); - } - item->emoji.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); - uiemojitype_t emojiValue; - if(!cutsceneJsonLookupEmoji( - yyjson_get_str(yyjson_obj_get(itemObj, "emojiType")), &emojiValue - )) { - errorThrow("Cutscene item (EMOJI): invalid 'emojiType'"); - } - item->emoji.emojiType = emojiValue; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h b/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h deleted file mode 100644 index 39d5fc5d..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "ui/rpg/uiemoji.h" - -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 -); - -/** - * Parses an EMOJI cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneEmojiLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenefade.c b/src/dusk/rpg/cutscene/item/ui/cutscenefade.c deleted file mode 100644 index 3b796b10..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenefade.c +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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 "ui/overlay/uifullbox.h" -#include "rpg/cutscene/item/json/cutscenejsoneasing.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneFadeStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - uiFullboxTransition( - &UI_FULLBOX_OVER, - item->fade.from, - item->fade.to, - item->fade.duration, - item->fade.easing - ); -} - -bool_t cutsceneFadeUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return !( - UI_FULLBOX_OVER.duration > 0.0f && - UI_FULLBOX_OVER.time < UI_FULLBOX_OVER.duration - ); -} - -errorret_t cutsceneFadeLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - yyjson_val *from = yyjson_obj_get(itemObj, "from"); - yyjson_val *to = yyjson_obj_get(itemObj, "to"); - if(!yyjson_is_arr(from) || yyjson_arr_size(from) != 4 || - !yyjson_is_arr(to) || yyjson_arr_size(to) != 4) { - errorThrow("Cutscene item (FADE): 'from'/'to' must each be [r, g, b, a]"); - } - item->fade.from.r = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 0)); - item->fade.from.g = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 1)); - item->fade.from.b = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 2)); - item->fade.from.a = (uint8_t)yyjson_get_int(yyjson_arr_get(from, 3)); - item->fade.to.r = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 0)); - item->fade.to.g = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 1)); - item->fade.to.b = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 2)); - item->fade.to.a = (uint8_t)yyjson_get_int(yyjson_arr_get(to, 3)); - item->fade.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); - - const char_t *easingName = - yyjson_get_str(yyjson_obj_get(itemObj, "easing")); - easingtype_t easingValue = EASING_LINEAR; - if(easingName != NULL && - !cutsceneJsonLookupEasing(easingName, &easingValue)) { - errorThrow("Cutscene item (FADE): invalid 'easing'"); - } - item->fade.easing = easingValue; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenefade.h b/src/dusk/rpg/cutscene/item/ui/cutscenefade.h deleted file mode 100644 index 21e812b7..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenefade.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "display/color.h" -#include "animation/easing.h" -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - color_t from; - color_t to; - float_t duration; - easingtype_t easing; -} cutscenefade_t; - -/** - * Starts a fade item (begins the overlay transition). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneFadeStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a fade item. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once the overlay transition has completed. - */ -bool_t cutsceneFadeUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a FADE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneFadeLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.c b/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.c deleted file mode 100644 index 79dd0323..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.c +++ /dev/null @@ -1,27 +0,0 @@ -/** - * 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 "ui/dialog/keyboard/uikeyboard.h" - -void cutsceneKeyboardStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - uiKeyboardOpen(&item->keyboard.open); -} - -bool_t cutsceneKeyboardUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - if(uiKeyboardIsOpen()) return false; - - cutsceneSystemSetTextCache(UI_KEYBOARD.text); - return true; -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h b/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h deleted file mode 100644 index ae97ebb2..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "ui/dialog/keyboard/uikeyboard.h" - -typedef struct { - // Passed straight through to uiKeyboardOpen - see uikeyboardopen_t. - uikeyboardopen_t open; -} cutscenekeyboard_t; - -/** - * Starts a keyboard item (opens the on-screen keyboard with the item's - * open parameters, unmodified - see uiKeyboardOpen). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneKeyboardStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a keyboard item. Blocks the cutscene while the keyboard is - * open; once it closes (confirmed or cancelled), caches its resulting - * text via cutsceneSystemSetTextCache (see cutsceneSystemGetTextCache) - * before completing, regardless of whether confirm or cancel was - * picked - check UI_KEYBOARD.confirmed from the item's own onInput - * callback if that distinction matters. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once the keyboard has closed; false while still open. - */ -bool_t cutsceneKeyboardUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.c b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.c deleted file mode 100644 index bad23afd..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.c +++ /dev/null @@ -1,79 +0,0 @@ -/** - * 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 "assert/assert.h" -#include "ui/widget/uimodal.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneModalStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - const cutscenemodal_t *modal = &item->modal; - assertTrue( - modal->optionCount <= CUTSCENE_MODAL_OPTIONS_MAX, - "Too many options for cutscene modal" - ); - - uiModalOpen( - modal->title, - modal->message, - modal->options, - modal->optionCount, - modal->callback, - NULL, - NULL - ); -} - -bool_t cutsceneModalUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - if(item->modal.optionCount == 0 && !item->modal.blocking) return true; - return !uiModalIsOpen(); -} - -void cutsceneModalCloseStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - uiModalClose(NULL); -} - -bool_t cutsceneModalCloseUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneModalLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(yyjson_obj_get(itemObj, "options") != NULL) { - errorThrow( - "Cutscene item (MODAL): options are not supported here - use " - "MODAL_OPTIONS_MARKERS instead" - ); - } - cutsceneJsonCopyString( - itemObj, "title", "", item->modal.title, CUTSCENE_MODAL_TITLE_MAX_CHARS - ); - if(!cutsceneJsonCopyString( - itemObj, "message", NULL, item->modal.message, - CUTSCENE_MODAL_MESSAGE_MAX_CHARS - )) { - errorThrow("Cutscene item (MODAL): missing/too-long 'message'"); - } - item->modal.blocking = cutsceneJsonGetBool(itemObj, "blocking", false); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h b/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h deleted file mode 100644 index 78df7bc6..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodal.h +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -#define CUTSCENE_MODAL_TITLE_MAX_CHARS 64 -#define CUTSCENE_MODAL_MESSAGE_MAX_CHARS 256 -#define CUTSCENE_MODAL_OPTIONS_MAX 4 - -/** - * Callback invoked with the selected option once a cutscene modal - * item's dialog closes. Back/cancel input is disabled while it's open - * (see uiMenuSetDisableBack), so it must be dismissed by picking one. - * - * @param optionIndex Index into the options array that was selected. - * @param userData Unused - always NULL. Required by uiModalOpen's - * callback signature. - */ -typedef void (*cutscenemodaloptioncallback_t)( - const uint8_t optionIndex, void *userData -); - -typedef struct { - // Display text, or a locale message ID to translate - see - // uiModalLocalize. - char_t title[CUTSCENE_MODAL_TITLE_MAX_CHARS]; - char_t message[CUTSCENE_MODAL_MESSAGE_MAX_CHARS]; - - // NOT copied by this struct - these pointers are only read once, - // while uiModalOpen is opening the modal in cutsceneModalStart, so - // they only need to stay valid until then (e.g. string literals, as - // CUTSCENE_MODAL_OPTIONS produces). Each is displayed text or a - // locale message ID, same as title/message. - const char_t **options; - uint8_t optionCount; - - cutscenemodaloptioncallback_t callback; - - // Whether the cutscene should wait for this dialog to close before - // continuing. Always treated as true when optionCount > 0 (there's no - // other way to move on). Ignored/false by default for a message-only - // dialog, e.g. from CUTSCENE_MODAL - see cutsceneModalUpdate. - bool_t blocking; -} cutscenemodal_t; - -/** - * Starts a modal item (shows the modal dialog with the item's title, - * message, and options, wiring callback to fire with the selected - * option once it closes). optionCount may be 0 for a message-only - * dialog with no option buttons. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneModalStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a modal item. - * - * A message-only modal (optionCount == 0, e.g. from CUTSCENE_MODAL) - * completes immediately unless item->modal.blocking is set, in which - * case it waits for the dialog to close - either the player pressing - * accept/cancel (see uiModalMessageFocusSelected) or an explicit - * CUTSCENE_MODAL_CLOSE item elsewhere in the cutscene. - * - * A modal with options (optionCount > 0, e.g. from - * CUTSCENE_MODAL_OPTIONS) always blocks regardless of the blocking - * field - it's dismissed by the player picking one of them, and the - * option callback is responsible for moving the cutscene on from there - * (typically via cutsceneSystemGoTo or cutsceneRestart). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once the modal has closed, or immediately for a - * non-blocking message-only modal. - */ -bool_t cutsceneModalUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a MODAL cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneModalLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); - -/** - * Starts a modal-close item (closes the currently open modal, if any). - * Whatever should happen next belongs in the cutscene's own item - * sequence (e.g. a CUTSCENE_CALLBACK or CUTSCENE_PRINT placed right - * after this item), not on this item itself. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneModalCloseStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a modal-close item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneModalCloseUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -// MODAL_CLOSE has no fields of its own to parse - see cutsceneItemLoadNoop -// (cutsceneitembase.h), used directly as its .load callback. diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c deleted file mode 100644 index f0390a92..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c +++ /dev/null @@ -1,112 +0,0 @@ -/** - * 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 "ui/widget/uimodal.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneModalOptionsMarkersCallback( - const uint8_t optionIndex, - void *userData -) { - const cutscenemodaloptionsmarkers_t *options = - &cutsceneSystemGetCurrentItem()->modalOptionsMarkers; - - // uiModalOpen disables back/cancel for a dialog with options, so - // UI_MODAL_RESULT_NONE can't reach here via input - this fallback to - // the last option's marker only matters if something ever force-closes - // the modal directly (uiModalClose) before an option is picked. - const uint8_t index = - optionIndex < options->optionCount ? optionIndex : options->optionCount - 1; - cutsceneSystemGoTo(options->markers[index]); -} - -void cutsceneModalOptionsMarkersStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - const cutscenemodaloptionsmarkers_t *options = &item->modalOptionsMarkers; - - // uiModalOpen wants an array of string pointers; options->options is a - // 2D array of owned char buffers (see the struct's doc comment), so - // build the pointer array it expects here. - const char_t *optionPtrs[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX]; - for(uint8_t i = 0; i < options->optionCount; i++) { - optionPtrs[i] = options->options[i]; - } - - uiModalOpen( - options->title, - options->message, - optionPtrs, - options->optionCount, - cutsceneModalOptionsMarkersCallback, - NULL, - NULL - ); -} - -bool_t cutsceneModalOptionsMarkersUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return false; -} - -errorret_t cutsceneModalOptionsMarkersLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - cutsceneJsonCopyString( - itemObj, "title", "", item->modalOptionsMarkers.title, - CUTSCENE_MODAL_TITLE_MAX_CHARS - ); - if(!cutsceneJsonCopyString( - itemObj, "message", "", item->modalOptionsMarkers.message, - CUTSCENE_MODAL_MESSAGE_MAX_CHARS - )) { - errorThrow( - "Cutscene item (MODAL_OPTIONS_MARKERS): 'message' too long" - ); - } - - yyjson_val *options = yyjson_obj_get(itemObj, "options"); - size_t optionCount = yyjson_arr_size(options); - if(!yyjson_is_arr(options) || optionCount == 0 || - optionCount > CUTSCENE_MODAL_OPTIONS_MARKERS_MAX) { - errorThrow( - "Cutscene item (MODAL_OPTIONS_MARKERS): 'options' must have 1-%u " - "entries", (uint32_t)CUTSCENE_MODAL_OPTIONS_MARKERS_MAX - ); - } - item->modalOptionsMarkers.optionCount = (uint8_t)optionCount; - - yyjson_arr_iter iter = yyjson_arr_iter_with(options); - yyjson_val *optionVal; - size_t o = 0; - while((optionVal = yyjson_arr_iter_next(&iter)) != NULL) { - if( - !cutsceneJsonCopyString( - optionVal, "text", NULL, item->modalOptionsMarkers.options[o], - UI_MODAL_OPTION_TEXT_MAX - ) || - !cutsceneJsonCopyString( - optionVal, "marker", NULL, item->modalOptionsMarkers.markers[o], - CUTSCENE_MARKER_NAME_MAX - ) - ) { - errorThrow( - "Cutscene item (MODAL_OPTIONS_MARKERS): options[%u] needs " - "'text'/'marker'", (uint32_t)o - ); - } - o++; - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h b/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h deleted file mode 100644 index 9a46f178..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" -#include "rpg/cutscene/item/ui/cutscenemodal.h" -#include "rpg/cutscene/item/control/cutscenemarker.h" -#include "ui/widget/uimodal.h" - -// Two options is the most either JSON MODAL_OPTIONS_MARKERS item ever -// needs. -#define CUTSCENE_MODAL_OPTIONS_MARKERS_MAX 2 - -typedef struct { - // Display text, or a locale message ID to translate - see - // uiModalLocalize. - char_t title[CUTSCENE_MODAL_TITLE_MAX_CHARS]; - char_t message[CUTSCENE_MODAL_MESSAGE_MAX_CHARS]; - - // Each option's display text/locale message ID, and the marker to - // cutsceneSystemGoTo when that option is selected. Copied by value at parse - // time (see cutsceneModalOptionsMarkersLoad), so they stay valid - // regardless of the backing JSON doc's lifetime. - char_t options[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX][UI_MODAL_OPTION_TEXT_MAX]; - char_t markers[CUTSCENE_MODAL_OPTIONS_MARKERS_MAX][CUTSCENE_MARKER_NAME_MAX]; - uint8_t optionCount; -} cutscenemodaloptionsmarkers_t; - -/** - * Starts a modal-options-markers item (shows the modal dialog with the - * item's title, message, and options). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneModalOptionsMarkersStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a modal-options-markers item. Never completes on its own - - * the cutscene blocks here indefinitely. Back/cancel input is disabled - * while it's open (see uiMenuSetDisableBack), so it must be dismissed - * by picking an option, which jumps straight to that option's marker - * via cutsceneSystemGoTo. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns false always. - */ -bool_t cutsceneModalOptionsMarkersUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a MODAL_OPTIONS_MARKERS cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneModalOptionsMarkersLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.c b/src/dusk/rpg/cutscene/item/ui/cutsceneshake.c deleted file mode 100644 index 674286f2..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.c +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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/rpgcamera.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneShakeStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - rpgCameraShake(item->shake.amount, item->shake.duration); -} - -bool_t cutsceneShakeUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneShakeLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - item->shake.amount = (uint8_t)cutsceneJsonGetInt(itemObj, "amount", 0); - item->shake.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h b/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h deleted file mode 100644 index 5ac06453..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneshake.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - uint8_t amount; - float_t duration; -} cutsceneshake_t; - -/** - * Starts a camera shake item (kicks off the shake on the RPG camera). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneShakeStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a camera shake item. The shake itself runs asynchronously on - * the RPG camera, so this always completes immediately. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneShakeUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a SHAKE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneShakeLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetext.c b/src/dusk/rpg/cutscene/item/ui/cutscenetext.c deleted file mode 100644 index 8dc5c70a..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetext.c +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/cutscene/item/cutsceneitem.h" -#include "ui/rpg/textbox/uitextboxmain.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneTextStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - uiTextboxMainSetText(item->text.text); -} - -bool_t cutsceneTextUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return !uiTextboxMainIsActive(); -} - -errorret_t cutsceneTextLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonCopyString( - itemObj, "text", NULL, item->text.text, CUTSCENE_TEXT_MAX_CHARS - )) { - errorThrow("Cutscene item (TEXT): missing/too-long 'text'"); - } - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetext.h b/src/dusk/rpg/cutscene/item/ui/cutscenetext.h deleted file mode 100644 index 6bf2bbe4..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetext.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -#define CUTSCENE_TEXT_MAX_CHARS 256 - -typedef struct { - char_t text[CUTSCENE_TEXT_MAX_CHARS]; -} cutscenetext_t; - -/** - * Starts a text item (shows the textbox with the item's text). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneTextStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a text item. - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true once the textbox has been dismissed. - */ -bool_t cutsceneTextUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a TEXT cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneTextLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); \ No newline at end of file diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.c b/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.c deleted file mode 100644 index 08bd27e9..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.c +++ /dev/null @@ -1,58 +0,0 @@ -/** - * 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 "ui/rpg/textbox/uitextboxminilist.h" -#include "rpg/cutscene/item/json/cutscenejsonfield.h" - -void cutsceneTextMiniStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - uint8_t index = uiTextboxMiniListGetNext(); - uiTextboxMiniShow( - &UI_TEXTBOX_MINI_LIST[index], - item->textMini.text, - item->textMini.position, - item->textMini.duration, - NULL, - NULL - ); - CUTSCENE_SYSTEM.refs.textMiniLastCreated = index; -} - -bool_t cutsceneTextMiniUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneTextMiniLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - if(!cutsceneJsonCopyString( - itemObj, "text", NULL, item->textMini.text, CUTSCENE_TEXT_MINI_MAX_CHARS - )) { - errorThrow("Cutscene item (TEXT_MINI): missing/too-long 'text'"); - } - yyjson_val *position = yyjson_obj_get(itemObj, "position"); - if(!yyjson_is_arr(position) || yyjson_arr_size(position) != 3) { - errorThrow("Cutscene item (TEXT_MINI): 'position' must be [x, y, z]"); - } - item->textMini.position[0] = - (float_t)yyjson_get_num(yyjson_arr_get(position, 0)); - item->textMini.position[1] = - (float_t)yyjson_get_num(yyjson_arr_get(position, 1)); - item->textMini.position[2] = - (float_t)yyjson_get_num(yyjson_arr_get(position, 2)); - item->textMini.duration = cutsceneJsonGetFloat(itemObj, "duration", 0.0f); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h b/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h deleted file mode 100644 index ad8d0731..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -#define CUTSCENE_TEXT_MINI_MAX_CHARS 128 - -typedef struct { - char_t text[CUTSCENE_TEXT_MINI_MAX_CHARS]; - vec3 position; - float_t duration; -} cutscenetextmini_t; - -/** - * Starts a mini text item (shows a mini textbox at the given world - * position for the given duration, then completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneTextMiniStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a mini text item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneTextMiniUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a TEXT_MINI cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneTextMiniLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.c b/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.c deleted file mode 100644 index b4df6066..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.c +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 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 "ui/rpg/textbox/uitextboxminilist.h" - -void cutsceneTextMiniHideStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - uint8_t index = cutsceneSystemGetTextMiniId(item->textMiniHide.index); - uiTextboxMiniClose(&UI_TEXTBOX_MINI_LIST[index]); -} - -bool_t cutsceneTextMiniHideUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneTextMiniHideLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - item->textMiniHide.index = - (uint8_t)yyjson_get_int(yyjson_obj_get(itemObj, "index")); - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h b/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h deleted file mode 100644 index 68f8f55d..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -typedef struct { - uint8_t index; -} cutscenetextminihide_t; - -/** - * Starts a mini text hide step (closes the mini textbox immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneTextMiniHideStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a mini text hide step (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneTextMiniHideUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a TEXT_MINI_HIDE cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneTextMiniHideLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.c b/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.c deleted file mode 100644 index 3ec69432..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.c +++ /dev/null @@ -1,43 +0,0 @@ -/** - * 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 "ui/screen/mainmenu/uimainmenu.h" -#include "rpg/cutscene/item/json/cutscenejsonuiscreentype.h" - -void cutsceneUIShowStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - switch(item->uiShow.screen) { - case UI_SCREEN_TYPE_MAIN_MENU: - uiMainMenuOpen(); - break; - } -} - -bool_t cutsceneUIShowUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -) { - return true; -} - -errorret_t cutsceneUIShowLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -) { - uiscreentype_t screenValue; - if(!cutsceneJsonLookupUiScreenType( - yyjson_get_str(yyjson_obj_get(itemObj, "name")), &screenValue - )) { - errorThrow("Cutscene item (UI_SHOW): invalid 'name'"); - } - item->uiShow.screen = screenValue; - - errorOk(); -} diff --git a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h b/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h deleted file mode 100644 index 17f5ea8d..00000000 --- a/src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/cutscene/item/cutsceneitembase.h" - -// Full-screen UI panels a cutscene can declaratively open via UI_SHOW - not -// modals/dialogs (those already have their own dedicated item types, e.g. -// MODAL/MODAL_OPTIONS_MARKERS/KEYBOARD), just whole-screen views like the -// main menu. Add a case here (and to cutsceneUIShowStart) as more screens -// need to be openable this way. -typedef enum { - UI_SCREEN_TYPE_MAIN_MENU, -} uiscreentype_t; - -typedef struct { - uiscreentype_t screen; -} cutsceneuishow_t; - -/** - * Starts a UI_SHOW item, opening the requested full-screen UI panel. - * - * @param item The cutscene item. - * @param data Runtime data storage. - */ -void cutsceneUIShowStart( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Updates a UI_SHOW item (always completes immediately). - * - * @param item The cutscene item. - * @param data Runtime data storage. - * @returns true always. - */ -bool_t cutsceneUIShowUpdate( - const cutsceneitem_t *item, - cutsceneitemdata_t *data -); - -/** - * Parses a UI_SHOW cutscene item's JSON fields into item. - * - * @param itemObj The item's JSON object. - * @param item Destination item, already zeroed by the caller. - * @return Error code indicating success or failure of the parse. - */ -errorret_t cutsceneUIShowLoad( - yyjson_val *itemObj, - cutsceneitem_t *item -); diff --git a/src/dusk/rpg/entity/CMakeLists.txt b/src/dusk/rpg/entity/CMakeLists.txt deleted file mode 100644 index da55a228..00000000 --- a/src/dusk/rpg/entity/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - entity.c - entitydir.c - entitypathstep.c - player.c -) - -add_subdirectory(anim) -add_subdirectory(interact) -add_subdirectory(npc) -add_subdirectory(item) -add_subdirectory(global) \ No newline at end of file diff --git a/src/dusk/rpg/entity/anim/CMakeLists.txt b/src/dusk/rpg/entity/anim/CMakeLists.txt deleted file mode 100644 index 54c1015e..00000000 --- a/src/dusk/rpg/entity/anim/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - entityanim.c - entityanimidle.c - entityanimturn.c - entityanimwalk.c - entityanimrun.c -) diff --git a/src/dusk/rpg/entity/anim/entityanim.c b/src/dusk/rpg/entity/anim/entityanim.c deleted file mode 100644 index fa98628f..00000000 --- a/src/dusk/rpg/entity/anim/entityanim.c +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/entity/entity.h" -#include "rpg/overworld/map.h" -#include "rpg/overworld/tile.h" -#include "time/time.h" -#include "entityanimwalk.h" - -const entityanimcallback_t ENTITY_ANIM_CALLBACKS[ENTITY_ANIM_COUNT] = { - [ENTITY_ANIM_IDLE] = { entityAnimIdleUpdate }, - [ENTITY_ANIM_TURN] = { entityAnimTurnUpdate }, - [ENTITY_ANIM_WALK] = { entityAnimWalkUpdate }, - [ENTITY_ANIM_RUN] = { entityAnimRunUpdate }, -}; - -float_t entityAnimTileZOffset(const worldpos_t pos) { - return tileShapeIsRamp(mapGetTile(pos).shape) ? 0.5f : 0.0f; -} - -void entityAnimUpdate(entity_t *entity) { - if(entity->animation != ENTITY_ANIM_IDLE) { - entity->animTime -= TIME.delta; - if(entity->animTime <= 0) { - if( - entity->animation == ENTITY_ANIM_WALK || - entity->animation == ENTITY_ANIM_RUN - ) { - entity->walkEndCooldown = ENTITY_ANIM_WALK_TURN_COOLDOWN; - } - entity->animation = ENTITY_ANIM_IDLE; - entity->animTime = 0; - } - } - if(entity->walkEndCooldown > 0) { - entity->walkEndCooldown -= TIME.delta; - if(entity->walkEndCooldown < 0) entity->walkEndCooldown = 0; - } - ENTITY_ANIM_CALLBACKS[entity->animation].update(entity); -} diff --git a/src/dusk/rpg/entity/anim/entityanim.h b/src/dusk/rpg/entity/anim/entityanim.h deleted file mode 100644 index ff4cde20..00000000 --- a/src/dusk/rpg/entity/anim/entityanim.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "entityanimidle.h" -#include "entityanimturn.h" -#include "entityanimwalk.h" -#include "entityanimrun.h" - -typedef struct entity_s entity_t; - -typedef enum { - ENTITY_ANIM_IDLE, - ENTITY_ANIM_TURN, - ENTITY_ANIM_WALK, - ENTITY_ANIM_RUN, - ENTITY_ANIM_COUNT -} entityanim_t; - -typedef struct { - /** Updates the render position for this animation state. */ - void (*update)(entity_t *entity); -} entityanimcallback_t; - -extern const entityanimcallback_t ENTITY_ANIM_CALLBACKS[ENTITY_ANIM_COUNT]; - -/** - * Updates the entity animation timer and render position. - * - * @param entity Pointer to the entity to update. - */ -void entityAnimUpdate(entity_t *entity); - -/** - * Returns 0.5 if the tile at pos is a ramp, 0.0 otherwise. - * Used to lift entity render position to mid-ramp height. - * - * @param pos World position to sample. - * @returns float_t The Z offset to apply. - */ -float_t entityAnimTileZOffset(const worldpos_t pos); diff --git a/src/dusk/rpg/entity/anim/entityanimidle.c b/src/dusk/rpg/entity/anim/entityanimidle.c deleted file mode 100644 index fb9f5d4a..00000000 --- a/src/dusk/rpg/entity/anim/entityanimidle.c +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/entity/entity.h" -#include "entityanim.h" - -void entityAnimIdleUpdate(entity_t *entity) { - entity->renderPosition[0] = (float_t)entity->position.x; - entity->renderPosition[1] = (float_t)entity->position.y; - entity->renderPosition[2] = ( - (float_t)entity->position.z + entityAnimTileZOffset(entity->position) - ) * WORLD_LAYER_HEIGHT; -} diff --git a/src/dusk/rpg/entity/anim/entityanimidle.h b/src/dusk/rpg/entity/anim/entityanimidle.h deleted file mode 100644 index b7bd9a53..00000000 --- a/src/dusk/rpg/entity/anim/entityanimidle.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct entity_s entity_t; - -/** - * Updates render position for the idle animation state. - * - * @param entity Pointer to the entity to update. - */ -void entityAnimIdleUpdate(entity_t *entity); diff --git a/src/dusk/rpg/entity/anim/entityanimrun.c b/src/dusk/rpg/entity/anim/entityanimrun.c deleted file mode 100644 index 27a32c2c..00000000 --- a/src/dusk/rpg/entity/anim/entityanimrun.c +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/entity/entity.h" -#include "entityanim.h" - -void entityAnimRunUpdate(entity_t *entity) { - float_t t = 1.0f - (entity->animTime / ENTITY_ANIM_RUN_DURATION); - float_t zFrom = (float_t)entity->lastPosition.z - + entityAnimTileZOffset(entity->lastPosition); - float_t zTo = (float_t)entity->position.z - + entityAnimTileZOffset(entity->position); - entity->renderPosition[0] = (float_t)entity->lastPosition.x + t * ( - (float_t)entity->position.x - (float_t)entity->lastPosition.x - ); - entity->renderPosition[1] = (float_t)entity->lastPosition.y + t * ( - (float_t)entity->position.y - (float_t)entity->lastPosition.y - ); - entity->renderPosition[2] = (zFrom + t * (zTo - zFrom)) * WORLD_LAYER_HEIGHT; -} diff --git a/src/dusk/rpg/entity/anim/entityanimrun.h b/src/dusk/rpg/entity/anim/entityanimrun.h deleted file mode 100644 index ecace434..00000000 --- a/src/dusk/rpg/entity/anim/entityanimrun.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "time/time.h" - -typedef struct entity_s entity_t; - -#define ENTITY_ANIM_RUN_DURATION TIME_TICKS_TO_TIME(6) - -/** - * Updates render position for the run animation state. - * - * @param entity Pointer to the entity to update. - */ -void entityAnimRunUpdate(entity_t *entity); diff --git a/src/dusk/rpg/entity/anim/entityanimturn.c b/src/dusk/rpg/entity/anim/entityanimturn.c deleted file mode 100644 index d31ad814..00000000 --- a/src/dusk/rpg/entity/anim/entityanimturn.c +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/entity/entity.h" -#include "entityanim.h" - -void entityAnimTurnUpdate(entity_t *entity) { - entity->renderPosition[0] = (float_t)entity->position.x; - entity->renderPosition[1] = (float_t)entity->position.y; - entity->renderPosition[2] = ( - (float_t)entity->position.z + entityAnimTileZOffset(entity->position) - ) * WORLD_LAYER_HEIGHT; -} diff --git a/src/dusk/rpg/entity/anim/entityanimturn.h b/src/dusk/rpg/entity/anim/entityanimturn.h deleted file mode 100644 index 33368d52..00000000 --- a/src/dusk/rpg/entity/anim/entityanimturn.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "time/time.h" - -typedef struct entity_s entity_t; - -#define ENTITY_ANIM_TURN_DURATION TIME_TICKS_TO_TIME(4) - -/** - * Updates render position for the turn animation state. - * - * @param entity Pointer to the entity to update. - */ -void entityAnimTurnUpdate(entity_t *entity); diff --git a/src/dusk/rpg/entity/anim/entityanimwalk.c b/src/dusk/rpg/entity/anim/entityanimwalk.c deleted file mode 100644 index e8aa2833..00000000 --- a/src/dusk/rpg/entity/anim/entityanimwalk.c +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/entity/entity.h" -#include "entityanim.h" - -void entityAnimWalkUpdate(entity_t *entity) { - float_t t = 1.0f - (entity->animTime / ENTITY_ANIM_WALK_DURATION); - float_t zFrom = (float_t)entity->lastPosition.z - + entityAnimTileZOffset(entity->lastPosition); - float_t zTo = (float_t)entity->position.z - + entityAnimTileZOffset(entity->position); - entity->renderPosition[0] = (float_t)entity->lastPosition.x + t * ( - (float_t)entity->position.x - (float_t)entity->lastPosition.x - ); - entity->renderPosition[1] = (float_t)entity->lastPosition.y + t * ( - (float_t)entity->position.y - (float_t)entity->lastPosition.y - ); - entity->renderPosition[2] = (zFrom + t * (zTo - zFrom)) * WORLD_LAYER_HEIGHT; -} diff --git a/src/dusk/rpg/entity/anim/entityanimwalk.h b/src/dusk/rpg/entity/anim/entityanimwalk.h deleted file mode 100644 index 5ab12585..00000000 --- a/src/dusk/rpg/entity/anim/entityanimwalk.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "time/time.h" - -typedef struct entity_s entity_t; - -#define ENTITY_ANIM_WALK_DURATION TIME_TICKS_TO_TIME(12) -#define ENTITY_ANIM_WALK_TURN_COOLDOWN TIME_TICKS_TO_TIME(4) - -/** - * Updates render position for the walk animation state. - * - * @param entity Pointer to the entity to update. - */ -void entityAnimWalkUpdate(entity_t *entity); diff --git a/src/dusk/rpg/entity/entity.c b/src/dusk/rpg/entity/entity.c deleted file mode 100644 index dabe2f67..00000000 --- a/src/dusk/rpg/entity/entity.c +++ /dev/null @@ -1,333 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entity.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "time/time.h" -#include "util/math.h" -#include "console/console.h" -#include "rpg/overworld/map.h" -#include "rpg/overworld/maparea.h" -#include "rpg/overworld/chunk.h" -#include "rpg/overworld/tile.h" - -entity_t ENTITIES[ENTITY_COUNT]; - -void entityInit(entity_t *entity, const entitytype_t type) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type"); - assertTrue(type != ENTITY_TYPE_NULL, "Cannot have NULL entity type"); - assertTrue( - entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT, - "Entity pointer is out of bounds" - ); - - memoryZero(entity, sizeof(entity_t)); - entity->id = (uint8_t)(entity - ENTITIES); - entity->globalId = ENTITY_GLOBAL_ID_NULL; - entity->type = type; - entity->chunkIndex = -1; - - if(ENTITY_CALLBACKS[type].init != NULL) ENTITY_CALLBACKS[type].init(entity); -} - -void entityUpdate(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type"); - assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type"); - - // What state is the entity in? - entityAnimUpdate(entity); - - // Movement code. - if(ENTITY_CALLBACKS[entity->type].movement != NULL) { - ENTITY_CALLBACKS[entity->type].movement(entity); - } -} - -bool_t entityCanTurn(entity_t *entity) { - return entity->animation == ENTITY_ANIM_IDLE && - entity->walkEndCooldown <= 0; -} - -bool_t entityCanWalk(entity_t *entity) { - return entity->animation == ENTITY_ANIM_IDLE; -} - -bool_t entityCanRun(entity_t *entity) { - return entity->animation == ENTITY_ANIM_IDLE; -} - -bool_t entityCanUnload(entity_t *entity) { - return entity->globalId < ENTITY_GLOBAL_ID_START; -} - -void entityTurn(entity_t *entity, const entitydir_t direction) { - if(!entityCanTurn(entity)) return; - entity->direction = direction; - entity->animation = ENTITY_ANIM_TURN; - entity->animTime = ENTITY_ANIM_TURN_DURATION; -} - -bool_t entityWalkCheckRampUp( - const tile_t tileCurrent, - const entitydir_t direction, - const worldpos_t newPos, - tile_t *tileNew -) { - if(!tileShapeIsRamp(tileCurrent.shape)) return false; - - bool_t facingRamp = - // Can only walk UP the direction the ramp faces. - (direction+TILE_SHAPE_RAMP_NORTH) == tileCurrent.shape || - // If diagonal ramp, can go up one of two ways only. Inner ramps - // share the same allowed directions as their outer counterparts. - ( - ( - tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHEAST || - tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHEAST_INNER - ) && - (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) - ) || - ( - ( - tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHWEST || - tileCurrent.shape == TILE_SHAPE_RAMP_SOUTHWEST_INNER - ) && - (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) - ) || - ( - ( - tileCurrent.shape == TILE_SHAPE_RAMP_NORTHEAST || - tileCurrent.shape == TILE_SHAPE_RAMP_NORTHEAST_INNER - ) && - (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) - ) || - ( - ( - tileCurrent.shape == TILE_SHAPE_RAMP_NORTHWEST || - tileCurrent.shape == TILE_SHAPE_RAMP_NORTHWEST_INNER - ) && - (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) - ); - if(!facingRamp) return false; - - tile_t tileNewSaved = *tileNew; - *tileNew = TILE_NULL; - worldpos_t abovePos = newPos; - abovePos.z += 1; - tile_t tileAbove = mapGetTile(abovePos); - - if( - tileAbove.shape != TILE_SHAPE_NULL && tileShapeIsWalkable(tileAbove.shape) - ) { - return true; - } - - *tileNew = tileNewSaved; - return false; -} - -bool_t entityWalkCheckFall( - const tile_t tileNew, const worldpos_t newPos, const entitydir_t direction -) { - if(tileNew.shape != TILE_SHAPE_NULL || newPos.z <= 0) return false; - - worldpos_t belowPos = newPos; - belowPos.z -= 1; - tile_t tileBelow = mapGetTile(belowPos); - if(tileBelow.shape == TILE_SHAPE_NULL || !tileShapeIsRamp(tileBelow.shape)) { - return false; - } - - return ( - // This handles regular cardinal ramps - (entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_NORTH) == - tileBelow.shape || - // This handles diagonal ramps. Inner ramps share the same - // allowed directions as their outer counterparts. - ( - ( - tileBelow.shape == TILE_SHAPE_RAMP_SOUTHEAST || - tileBelow.shape == TILE_SHAPE_RAMP_SOUTHEAST_INNER - ) && - (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST) - ) || - ( - ( - tileBelow.shape == TILE_SHAPE_RAMP_SOUTHWEST || - tileBelow.shape == TILE_SHAPE_RAMP_SOUTHWEST_INNER - ) && - (direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST) - ) || - ( - ( - tileBelow.shape == TILE_SHAPE_RAMP_NORTHEAST || - tileBelow.shape == TILE_SHAPE_RAMP_NORTHEAST_INNER - ) && - (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST) - ) || - ( - ( - tileBelow.shape == TILE_SHAPE_RAMP_NORTHWEST || - tileBelow.shape == TILE_SHAPE_RAMP_NORTHWEST_INNER - ) && - (direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST) - ) - ); -} - -bool_t entityWalkIsBlockedByEntity(entity_t *entity, const worldpos_t newPos) { - entity_t *other = ENTITIES; - do { - if(other == entity) continue; - if(other->type == ENTITY_TYPE_NULL) continue; - if(!worldPosIsEqual(other->position, newPos)) continue; - return true; - } while(++other, other < &ENTITIES[ENTITY_COUNT]); - - return false; -} - -void entityWalk(entity_t *entity, const entitydir_t direction) { - if(!entityCanWalk(entity)) return; - // TODO: Animation, delay, etc. - entity->direction = direction; - - // Where are we moving? - worldpos_t newPos = entity->position; - worldunits_t relX, relY; - { - entityDirGetRelative(direction, &relX, &relY); - newPos.x += relX; - newPos.y += relY; - } - - // Get tile under foot - tile_t tileCurrent = mapGetTile(entity->position); - tile_t tileNew = mapGetTile(newPos); - - bool_t raise = - entityWalkCheckRampUp(tileCurrent, direction, newPos, &tileNew); - bool_t fall = !raise && entityWalkCheckFall(tileNew, newPos, direction); - - // Can we walk here? - if(!raise && !fall && !tileShapeIsWalkable(tileNew.shape)) return;// Blocked - - // Raise/fall must be applied before checking for blocking entities, - // otherwise the check compares against the wrong z-level. - if(raise) { - newPos.z += 1; - } else if(fall) { - newPos.z -= 1; - } - - // Entity in way? - if(entityWalkIsBlockedByEntity(entity, newPos)) return;// Blocked - - entity->lastPosition = entity->position; - entity->position = newPos; - entity->animation = ENTITY_ANIM_WALK; - entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking - entityUpdateChunk(entity); - mapAreaCheckEntity(entity); -} - -void entityRun(entity_t *entity, const entitydir_t direction) { - if(!entityCanRun(entity)) return; - entityWalk(entity, direction); - if(entity->animation == ENTITY_ANIM_WALK) { - entity->animation = ENTITY_ANIM_RUN; - entity->animTime = ENTITY_ANIM_RUN_DURATION; - } -} - -entity_t * entityGetAt(const worldpos_t position) { - entity_t *ent = ENTITIES; - do { - if(ent->type == ENTITY_TYPE_NULL) continue; - if(!worldPosIsEqual(ent->position, position)) continue; - return ent; - } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); - - return NULL; -} - -entity_t * entityGetByGlobalId(const entityglobalid_t globalId) { - entity_t *ent = ENTITIES; - do { - if(ent->type == ENTITY_TYPE_NULL) continue; - if(ent->globalId != globalId) continue; - return ent; - } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); - - return NULL; -} - -uint8_t entityGetAvailable() { - entity_t *ent = ENTITIES; - do { - if(ent->type == ENTITY_TYPE_NULL) return ent - ENTITIES; - } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); - - return 0xFF; -} - -void entityPositionSet(entity_t *entity, const worldpos_t pos) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - entity->lastPosition = pos; - entity->position = pos; - entity->animation = ENTITY_ANIM_IDLE; - entity->animTime = 0; - entity->walkEndCooldown = 0; - entityUpdateChunk(entity); -} - -void entitySetChunk(entity_t *entity, const chunkindex_t chunkIndex) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - - chunk_t *old = mapGetChunk(entity->chunkIndex); - if(old != NULL) { - for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) { - if(old->entities[i] != entity->id) continue; - old->entities[i] = 0xFF; - } - } - - // Only claim the new chunk once actually inserted into one of its slots - - // otherwise entity->chunkIndex would point at a chunk that doesn't know - // about this entity, so it would never be torn down on unload. - entity->chunkIndex = CHUNK_INDEX_INVALID; - - chunk_t *next = mapGetChunk(chunkIndex); - if(next != NULL) { - for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) { - if(next->entities[i] != 0xFF) continue; - next->entities[i] = entity->id; - entity->chunkIndex = chunkIndex; - break; - } - - if(entity->chunkIndex != chunkIndex) { - consolePrint( - "entitySetChunk: chunk %d has no free entity slots, entity %u " - "left untracked", - chunkIndex, entity->id - ); - } - } -} - -void entityUpdateChunk(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - - chunkpos_t cp; - worldPosToChunkPos(&entity->position, &cp); - chunkindex_t ci = mapGetChunkIndexAt(cp); - entitySetChunk(entity, ci); -} \ No newline at end of file diff --git a/src/dusk/rpg/entity/entity.h b/src/dusk/rpg/entity/entity.h deleted file mode 100644 index 2ef8b64c..00000000 --- a/src/dusk/rpg/entity/entity.h +++ /dev/null @@ -1,215 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "entitydir.h" -#include "anim/entityanim.h" -#include "interact/entityinteract.h" -#include "entitytype.h" -#include "npc/npc.h" -#include "rpg/overworld/tile.h" - -typedef struct map_s map_t; - -typedef uint16_t entityglobalid_t; - -#define ENTITY_GLOBAL_ID_NULL 0 -#define ENTITY_GLOBAL_ID_START 1 -#define ENTITY_GLOBAL_ID_PLAYER 1 - -typedef struct entity_s { - uint8_t id; - entityglobalid_t globalId; - entitytype_t type; - entitytypedata_t data; - - // Movement - entitydir_t direction; - worldpos_t position; - worldpos_t lastPosition; - vec3 renderPosition; - - entityanim_t animation; - float_t animTime; - float_t walkEndCooldown; - - entityinteract_t interact; - - chunkindex_t chunkIndex; -} entity_t; - -extern entity_t ENTITIES[ENTITY_COUNT]; - -/** - * Initializes an entity structure. - * - * @param entity Pointer to the entity structure to initialize. - * @param type The type of the entity. - */ -void entityInit(entity_t *entity, const entitytype_t type); - -/** - * Updates an entity. - * - * @param entity Pointer to the entity structure to update. - */ -void entityUpdate(entity_t *entity); - -/** - * Returns true if the entity is in a state where it can turn. - * - * @param entity Pointer to the entity to check. - * @returns True if the entity can turn. - */ -bool_t entityCanTurn(entity_t *entity); - -/** - * Returns true if the entity is in a state where it can walk. - * - * @param entity Pointer to the entity to check. - * @returns True if the entity can walk. - */ -bool_t entityCanWalk(entity_t *entity); - -/** - * Returns true if the entity is in a state where it can run. - * - * @param entity Pointer to the entity to check. - * @returns True if the entity can run. - */ -bool_t entityCanRun(entity_t *entity); - -/** - * Returns true if the entity is allowed to be unloaded. By default this is - * true for entities whose global ID falls within the randomly assigned - * range below ENTITY_GLOBAL_ID_START. - * - * @param entity Pointer to the entity to check. - * @returns True if the entity can be unloaded. - */ -bool_t entityCanUnload(entity_t *entity); - -/** - * Turn an entity to face a new direction. - * - * @param entity Pointer to the entity to turn. - * @param direction The direction to face. - */ -void entityTurn(entity_t *entity, const entitydir_t direction); - -/** - * Checks whether an entity standing on a ramp tile can walk up it towards - * newPos. If so, tileNew is cleared to TILE_NULL so the caller treats the - * move as a raise rather than a normal walk onto whatever tile is there; - * if the tile above isn't walkable, tileNew is restored to its original - * value. - * - * @param tileCurrent The tile the entity is currently standing on. - * @param direction The direction the entity is moving in. - * @param newPos The world position the entity is moving to. - * @param tileNew In/out: the tile at newPos, possibly cleared to TILE_NULL. - * @returns True if the entity should be raised up onto the ramp. - */ -bool_t entityWalkCheckRampUp( - const tile_t tileCurrent, - const entitydir_t direction, - const worldpos_t newPos, - tile_t *tileNew -); - -/** - * Checks whether an entity moving onto an empty tile should fall down onto - * a ramp one z-level below that faces back towards where it came from. - * - * @param tileNew The tile at the position the entity is moving to. - * @param newPos The world position the entity is moving to. - * @param direction The direction the entity is moving in. - * @returns True if the entity should fall to the tile below newPos. - */ -bool_t entityWalkCheckFall( - const tile_t tileNew, const worldpos_t newPos, const entitydir_t direction -); - -/** - * Checks whether any other loaded entity already occupies a world position. - * - * @param entity The entity that is moving (excluded from the check). - * @param newPos The world position to check for occupancy. - * @returns True if another entity occupies newPos. - */ -bool_t entityWalkIsBlockedByEntity(entity_t *entity, const worldpos_t newPos); - -/** - * Make an entity walk in a direction. - * - * @param entity Pointer to the entity to make walk. - * @param direction The direction to walk in. - */ -void entityWalk(entity_t *entity, const entitydir_t direction); - -/** - * Make an entity run in a direction. - * - * @param entity Pointer to the entity to make run. - * @param direction The direction to run in. - */ -void entityRun(entity_t *entity, const entitydir_t direction); - -/** - * Gets the entity at a specific world position. - * - * @param map Pointer to the map to check. - * @param pos The world position to check. - * @return Pointer to the entity at the position, or NULL if none. - */ -entity_t *entityGetAt(const worldpos_t pos); - -/** - * Gets the entity with the given global ID, if one is currently loaded. - * - * @param globalId The global ID to search for. - * @return Pointer to the matching entity, or NULL if none is loaded. - */ -entity_t *entityGetByGlobalId(const entityglobalid_t globalId); - -/** - * Gets an available entity index. - * - * @return The index of an available entity, or 0xFF if none are available. - */ -uint8_t entityGetAvailable(); - -/** - * Assigns an entity to a chunk, removing it from its current chunk first. - * Pass CHUNK_INDEX_INVALID as chunkIndex to detach the entity from any - * chunk. If the target chunk has no free entity slots, the entity is - * left detached (chunkIndex CHUNK_INDEX_INVALID) rather than assigned to - * a chunk that isn't actually tracking it - entityUpdateChunk will keep - * retrying on subsequent moves. - * - * @param entity Pointer to the entity. - * @param chunkIndex Index of the chunk to assign to, or - * CHUNK_INDEX_INVALID for none. - */ -void entitySetChunk(entity_t *entity, const chunkindex_t chunkIndex); - -/** - * Resolves the chunk that an entity's current position falls into and - * assigns the entity to it via entitySetChunk. Leaves the entity's chunk - * unchanged if its position doesn't fall within any loaded chunk. - * - * @param entity Pointer to the entity to update. - */ -void entityUpdateChunk(entity_t *entity); - -/** - * Instantly moves an entity to a world position, resetting movement state. - * - * @param entity Pointer to the entity to move. - * @param pos The world position to place the entity at. - */ -void entityPositionSet(entity_t *entity, const worldpos_t pos); \ No newline at end of file diff --git a/src/dusk/rpg/entity/entitydir.c b/src/dusk/rpg/entity/entitydir.c deleted file mode 100644 index bf75e7c7..00000000 --- a/src/dusk/rpg/entity/entitydir.c +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entitydir.h" -#include "assert/assert.h" - -entitydir_t entityDirGetOpposite(const entitydir_t dir) { - switch(dir) { - case ENTITY_DIR_NORTH: return ENTITY_DIR_SOUTH; - case ENTITY_DIR_SOUTH: return ENTITY_DIR_NORTH; - case ENTITY_DIR_EAST: return ENTITY_DIR_WEST; - case ENTITY_DIR_WEST: return ENTITY_DIR_EAST; - default: return dir; - } -} - -void entityDirGetRelative( - const entitydir_t from, - worldunits_t *outX, - worldunits_t *outY -) { - assertValidEntityDir(from, "Invalid direction provided"); - assertNotNull(outX, "Output X pointer cannot be NULL"); - assertNotNull(outY, "Output Y pointer cannot be NULL"); - - switch(from) { - case ENTITY_DIR_NORTH: - *outX = 0; - *outY = 1; - break; - - case ENTITY_DIR_EAST: - *outX = 1; - *outY = 0; - break; - - case ENTITY_DIR_SOUTH: - *outX = 0; - *outY = -1; - break; - - case ENTITY_DIR_WEST: - *outX = -1; - *outY = 0; - break; - } -} \ No newline at end of file diff --git a/src/dusk/rpg/entity/entitydir.h b/src/dusk/rpg/entity/entitydir.h deleted file mode 100644 index 2eefab88..00000000 --- a/src/dusk/rpg/entity/entitydir.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/worldpos.h" - -typedef enum { - ENTITY_DIR_NORTH, - ENTITY_DIR_EAST, - ENTITY_DIR_SOUTH, - ENTITY_DIR_WEST, - ENTITY_DIR_UP = ENTITY_DIR_NORTH, - ENTITY_DIR_DOWN = ENTITY_DIR_SOUTH, - ENTITY_DIR_LEFT = ENTITY_DIR_WEST, - ENTITY_DIR_RIGHT = ENTITY_DIR_EAST, -} entitydir_t; - -/** - * Gets the opposite direction of a given direction. - * - * @param dir The direction to get the opposite of. - * @return entitydir_t The opposite direction. - */ -entitydir_t entityDirGetOpposite(const entitydir_t dir); - -/** - * Asserts a given direction is valid. - * - * @param dir The direction to validate. - * @param msg The message to display if the assertion fails. - */ -#define assertValidEntityDir(dir, msg) \ - assertTrue( \ - (dir) == ENTITY_DIR_NORTH || \ - (dir) == ENTITY_DIR_EAST || \ - (dir) == ENTITY_DIR_SOUTH || \ - (dir) == ENTITY_DIR_WEST, \ - msg \ - ) - -/** - * Gets the relative x and y offsets for a given direction. - * - * @param dir The direction to get offsets for. - * @param relX Pointer to store the relative x offset. - * @param relY Pointer to store the relative y offset. - */ -void entityDirGetRelative( - const entitydir_t dir, worldunits_t *relX, worldunits_t *relY -); \ No newline at end of file diff --git a/src/dusk/rpg/entity/entitypathstep.c b/src/dusk/rpg/entity/entitypathstep.c deleted file mode 100644 index bd474415..00000000 --- a/src/dusk/rpg/entity/entitypathstep.c +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entitypathstep.h" -#include "entitydir.h" -#include "assert/assert.h" - -bool_t entityPathStep( - entity_t *entity, - const worldpos_t target, - bool_t walkAround -) { - assertNotNull(entity, "Entity must not be NULL"); - assertTrue( - entity->type != ENTITY_TYPE_NULL, - "Cannot path step a NULL entity type" - ); - assertTrue( - entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT, - "Entity pointer is out of bounds" - ); - - if(worldPosIsEqual(entity->position, target) && entityCanWalk(entity)) { - return true; - } - if(!entityCanWalk(entity)) return false; - - entitydir_t dir; - if(entity->position.x != target.x) { - dir = entity->position.x < target.x ? ENTITY_DIR_EAST : ENTITY_DIR_WEST; - } else if(entity->position.y != target.y) { - dir = entity->position.y < target.y ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH; - } else { - dir = entity->position.z < target.z ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH; - } - - entityWalk(entity, dir); - - if(walkAround && entity->animation == ENTITY_ANIM_IDLE) { - // Primary direction blocked - try perpendicular axes. - entitydir_t alt, altOpp; - if(dir == ENTITY_DIR_EAST || dir == ENTITY_DIR_WEST) { - alt = entity->position.y <= target.y - ? ENTITY_DIR_NORTH : ENTITY_DIR_SOUTH; - } else { - alt = entity->position.x <= target.x - ? ENTITY_DIR_EAST : ENTITY_DIR_WEST; - } - altOpp = entityDirGetOpposite(alt); - entityWalk(entity, alt); - if(entity->animation == ENTITY_ANIM_IDLE) entityWalk(entity, altOpp); - } - - return false; -} diff --git a/src/dusk/rpg/entity/entitypathstep.h b/src/dusk/rpg/entity/entitypathstep.h deleted file mode 100644 index 1608cae8..00000000 --- a/src/dusk/rpg/entity/entitypathstep.h +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "entity.h" - -/** - * Attempts to walk the entity one tile toward target. Prefers resolving X - * first, then Y, then Z. Does nothing if the entity cannot currently walk. - * When walkAround is true and the preferred direction is blocked by an entity, - * tries perpendicular directions to navigate around it. - * - * @param entity Pointer to the entity to move. - * @param target The world position to move toward. - * @param walkAround Whether to try perpendicular directions when blocked. - * @returns true if the entity is already at target, false otherwise. - */ -bool_t entityPathStep( - entity_t *entity, - const worldpos_t target, - bool_t walkAround -); diff --git a/src/dusk/rpg/entity/entitytype.h b/src/dusk/rpg/entity/entitytype.h deleted file mode 100644 index 6b7d2769..00000000 --- a/src/dusk/rpg/entity/entitytype.h +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/entity/player.h" -#include "npc/npc.h" -#include "item/entityitem.h" - -typedef enum { - ENTITY_TYPE_NULL, - - ENTITY_TYPE_PLAYER, - ENTITY_TYPE_NPC, - ENTITY_TYPE_ITEM, - - ENTITY_TYPE_COUNT -} entitytype_enum_t; - -typedef uint8_t entitytype_t; - -typedef union { - player_t player; - npc_t npc; - entityitem_t item; -} entitytypedata_t; - -typedef struct { - /** - * Initialization callback for the entity type. - * @param entity Pointer to the entity to initialize. - */ - void (*init)(entity_t *entity); - - /** - * Movement callback for the entity type. Gated by cutscene input. - * @param entity Pointer to the entity to move. - */ - void (*movement)(entity_t *entity); -} entitycallback_t; - -static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = { - [ENTITY_TYPE_NULL] = { NULL }, - - [ENTITY_TYPE_PLAYER] = { - .init = playerInit, - .movement = playerInput - }, - - [ENTITY_TYPE_NPC] = { - .init = npcInit, - .movement = npcMovement, - }, - - [ENTITY_TYPE_ITEM] = { - .init = entityItemInit, - .movement = entityItemMovement, - } -}; \ No newline at end of file diff --git a/src/dusk/rpg/entity/global/CMakeLists.txt b/src/dusk/rpg/entity/global/CMakeLists.txt deleted file mode 100644 index 9b35b01d..00000000 --- a/src/dusk/rpg/entity/global/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC -) \ No newline at end of file diff --git a/src/dusk/rpg/entity/global/entityglobal.h b/src/dusk/rpg/entity/global/entityglobal.h deleted file mode 100644 index 10d51d60..00000000 --- a/src/dusk/rpg/entity/global/entityglobal.h +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "entityglobaldefs.h" -#include "entitygloballist.h" - -#define ENTITY_GLOBAL_LIST_COUNT ( \ - sizeof(ENTITY_GLOBAL_LIST) / \ - sizeof(ENTITY_GLOBAL_LIST[0]) \ -) - -//EOF diff --git a/src/dusk/rpg/entity/global/entityglobaldefs.h b/src/dusk/rpg/entity/global/entityglobaldefs.h deleted file mode 100644 index 73913e45..00000000 --- a/src/dusk/rpg/entity/global/entityglobaldefs.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "rpg/overworld/worldpos.h" -#include "rpg/entity/entity.h" - -typedef struct entity_s entity_t; - -typedef struct { - entity_t *entity; - worldpos_t position; -} entityglobalcreate_t; - -/** - * Callback invoked to initialize a global entity. - * - * @param create Pointer to the entity/position being initialized. - * @returns An error code. - */ -typedef void (*entityglobalinitcallback_t)( - entityglobalcreate_t *create -); - -typedef struct { - entitytype_t type; - entityglobalinitcallback_t callback; -} entityglobaldef_t; - -#define ENTITY_GLOBAL(id, entType, callbackFn) \ - [id] = { .type = entType, .callback = callbackFn } - -#define ENTITY_GLOBAL_CALLBACK(id) \ - static void ENTTIYT_GLOBAL_CALLBACK_##id(entityglobalcreate_t *create) - -#define ENTITY_GLOBAL_REF(id) \ - ENTTIYT_GLOBAL_CALLBACK_##id - -//EOF \ No newline at end of file diff --git a/src/dusk/rpg/entity/global/entitygloballist.h b/src/dusk/rpg/entity/global/entitygloballist.h deleted file mode 100644 index c2a83e79..00000000 --- a/src/dusk/rpg/entity/global/entitygloballist.h +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "entityglobaldefs.h" -#include "rpg/cutscene/item/cutsceneitem.h" - -ENTITY_GLOBAL_CALLBACK(3) { - create->entity->data.npc.moveType = NPC_MOVE_TYPE_PATH; - npcPathAddNode(&create->entity->data.npc, (worldpos_t){ 4, 4, 0 }); - npcPathAddNode(&create->entity->data.npc, (worldpos_t){ 10, 10, 1 }); - npcPathAddNode(&create->entity->data.npc, (worldpos_t){ 4, 4, 0 }); - npcPathAddNode(&create->entity->data.npc, (worldpos_t){ 10, 10, 1 }); -} - -ENTITY_GLOBAL_CALLBACK(4) { - create->entity->interact.type = ENTITY_INTERACT_CUTSCENE_NAME; - create->entity->interact.data.cutsceneName = "cutscenes/battletest.jsonc"; -} - -static const entityglobaldef_t ENTITY_GLOBAL_LIST[] = { - ENTITY_GLOBAL(ENTITY_GLOBAL_ID_NULL, ENTITY_TYPE_NULL, NULL), - ENTITY_GLOBAL(ENTITY_GLOBAL_ID_PLAYER, ENTITY_TYPE_PLAYER, NULL), - - ENTITY_GLOBAL(3, ENTITY_TYPE_NPC, ENTITY_GLOBAL_REF(3)), - ENTITY_GLOBAL(4, ENTITY_TYPE_NPC, ENTITY_GLOBAL_REF(4)), -}; - -//EOF diff --git a/src/dusk/rpg/entity/interact/CMakeLists.txt b/src/dusk/rpg/entity/interact/CMakeLists.txt deleted file mode 100644 index f7e07e35..00000000 --- a/src/dusk/rpg/entity/interact/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - entityinteract.c -) diff --git a/src/dusk/rpg/entity/interact/entityinteract.c b/src/dusk/rpg/entity/interact/entityinteract.c deleted file mode 100644 index 063466c0..00000000 --- a/src/dusk/rpg/entity/interact/entityinteract.c +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/entity/entity.h" -#include "assert/assert.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "ui/rpg/textbox/uitextboxmain.h" - -void entityInteractWith(entity_t *player, entity_t *target) { - assertNotNull(player, "Player entity pointer cannot be NULL"); - assertNotNull(target, "Target entity pointer cannot be NULL"); - - switch(target->interact.type) { - case ENTITY_INTERACT_CUTSCENE: - assertNotNull( - target->interact.data.cutscene, - "Interact cutscene pointer cannot be NULL" - ); - cutsceneSystemStartCutsceneWith( - target->interact.data.cutscene, - player, - target - ); - break; - - case ENTITY_INTERACT_CUTSCENE_NAME: { - cutscene_t *cutscene = - cutsceneCutsceneResolve(target->interact.data.cutsceneName); - if(cutscene != NULL) { - cutsceneSystemStartCutsceneWith(cutscene, player, target); - } - break; - } - - case ENTITY_INTERACT_PRINT: - uiTextboxMainSetText(target->interact.data.message); - - // If NPC turn to face player. - if(target->type == ENTITY_TYPE_NPC) { - target->data.npc.interactState = NPC_INTERACT_STATE_CONVERSING; - target->animation = ENTITY_ANIM_IDLE; - entityTurn(target, entityDirGetOpposite(player->direction)); - } - - // entityTurn(player, player->direction); // Redundant (for now) - break; - - case ENTITY_INTERACT_CALLBACK: - assertNotNull( - target->interact.data.callback, - "Interact callback pointer cannot be NULL" - ); - target->interact.data.callback(player, target); - break; - - case ENTITY_INTERACT_NULL: - break; - - default: - assertUnreachable("Unknown entity interact type"); - break; - } -} \ No newline at end of file diff --git a/src/dusk/rpg/entity/interact/entityinteract.h b/src/dusk/rpg/entity/interact/entityinteract.h deleted file mode 100644 index 69f5a4ff..00000000 --- a/src/dusk/rpg/entity/interact/entityinteract.h +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct entity_s entity_t; -typedef struct cutscene_s cutscene_t; - -/** - * Describes the type of interaction an entity supports. - */ -typedef enum { - ENTITY_INTERACT_NULL = 0, - - ENTITY_INTERACT_CUTSCENE, - ENTITY_INTERACT_CUTSCENE_NAME, - ENTITY_INTERACT_PRINT, - ENTITY_INTERACT_CALLBACK, - - ENTITY_INTERACT_COUNT -} entityinteracttype_t; - -/** - * Per-type data for an entity's interact component. - */ -typedef union { - // A compile-time cutscene_t* (a C-authored, test-only cutscene) - safe - // to hold onto indefinitely, since it's compiled directly into its own - // static arrays, never the shared CUTSCENE_SYSTEM.loadedItems buffer (see - // cutscenesystem.h). Non-const since cutsceneSystemStartCutsceneWith may - // splice CUTSCENE_ITEM_TYPE_INSERT items into it at runtime. - cutscene_t *cutscene; - // A bare cutscene name (as passed to cutsceneCutsceneResolve), resolved - // lazily at interact time rather than once up front - only one - // JSON-authored cutscene is ever resident at a time (see - // CUTSCENE_LOADED_ITEMS_MAX's doc comment), so resolving early and - // holding onto the result here would go stale the moment any other - // cutscene loads in the meantime. - const char_t *cutsceneName; - const char_t *message; - void (*callback)(entity_t *player, entity_t *target); -} entityinteractdata_t; - -/** - * Interact component attached to any entity that can be interacted with. - * Set type to ENTITY_INTERACT_NULL to mark the entity as non-interactable. - */ -typedef struct { - entityinteracttype_t type; - entityinteractdata_t data; -} entityinteract_t; - -/** - * Attempts to interact with the target entity on behalf of the player. - * Dispatches via the interact component; falls back to the entity type - * callback if no component is set. - * - * @param player Pointer to the player entity. - * @param target Pointer to the entity to interact with. - */ -void entityInteractWith(entity_t *player, entity_t *target); diff --git a/src/dusk/rpg/entity/item/entityitem.c b/src/dusk/rpg/entity/item/entityitem.c deleted file mode 100644 index 5d39ce09..00000000 --- a/src/dusk/rpg/entity/item/entityitem.c +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entityitem.h" -#include "rpg/entity/entity.h" -#include "assert/assert.h" -#include "rpg/item/itemgive.h" -#include "ui/rpg/textbox/uitextboxmain.h" - -void entityItemInit(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - entity->interact.type = ENTITY_INTERACT_CALLBACK; - entity->interact.data.callback = entityItemInteract; -} - -void entityItemSet( - entity_t *entity, - const itemid_t item, - const uint8_t quantity -) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - entity->data.item.item = item; - entity->data.item.quantity = quantity; -} - -void entityItemInteract(entity_t *player, entity_t *target) { - assertNotNull(player, "Player entity pointer cannot be NULL"); - assertNotNull(target, "Target entity pointer cannot be NULL"); - - itemGive(target->data.item.item, target->data.item.quantity); - target->data.item.collected = true; -} - -void entityItemMovement(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - if(!entity->data.item.collected) return; - if(uiTextboxMainIsActive()) return; - - entitySetChunk(entity, CHUNK_INDEX_INVALID); - entity->type = ENTITY_TYPE_NULL; -} diff --git a/src/dusk/rpg/entity/item/entityitem.h b/src/dusk/rpg/entity/item/entityitem.h deleted file mode 100644 index 1485f1fa..00000000 --- a/src/dusk/rpg/entity/item/entityitem.h +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/item/item.h" - -typedef struct entity_s entity_t; - -typedef struct { - itemid_t item; - uint8_t quantity; - bool_t collected; -} entityitem_t; - -/** - * Initializes an item entity. Wires up the interact component so that - * interacting with the entity gives its item and removes it. - * - * @param entity Pointer to the entity structure to initialize. - */ -void entityItemInit(entity_t *entity); - -/** - * Sets the item and quantity represented by an item entity. - * - * @param entity Pointer to the entity structure. - * @param item The item ID to give when picked up. - * @param quantity The quantity to give when picked up. - */ -void entityItemSet( - entity_t *entity, - const itemid_t item, - const uint8_t quantity -); - -/** - * Interact callback for item entities. Gives the entity's item to the - * player and shows the pickup message. The entity itself is removed once - * the message has been dismissed, via entityItemMovement. - * - * @param player Pointer to the player entity. - * @param target Pointer to the item entity being picked up. - */ -void entityItemInteract(entity_t *player, entity_t *target); - -/** - * Update callback for item entities. Removes the entity once its pickup - * message has been dismissed. No-op if the item hasn't been collected. - * - * @param entity Pointer to the entity structure to update. - */ -void entityItemMovement(entity_t *entity); diff --git a/src/dusk/rpg/entity/npc/npc.c b/src/dusk/rpg/entity/npc/npc.c deleted file mode 100644 index 4773e572..00000000 --- a/src/dusk/rpg/entity/npc/npc.c +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg/entity/entity.h" -#include "assert/assert.h" -#include "rpg/cutscene/cutscenesystem.h" - -const npcmovecallback_t NPC_MOVE_CALLBACKS[NPC_MOVE_TYPE_COUNT] = { - [NPC_MOVE_TYPE_NULL] = { 0 }, - - [NPC_MOVE_TYPE_RANDOM_TURN] = { - npcRandomTurnInit, - npcRandomTurnMovement - }, - - [NPC_MOVE_TYPE_RANDOM_WALK] = { - npcRandomWalkInit, - npcRandomWalkMovement - }, - - [NPC_MOVE_TYPE_RANDOM_TURN_AND_WALK] = { - npcRandomTurnAndWalkInit, - npcRandomTurnAndWalkMovement - }, - - [NPC_MOVE_TYPE_PATH] = { - npcPathInit, - npcPathMovement - }, -}; - -void npcInit(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); -} - -void npcSetMoveType(entity_t *entity, const npcmovetype_t moveType) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - npc_t *npc = &entity->data.npc; - npc->moveType = moveType; - if(NPC_MOVE_CALLBACKS[moveType].init != NULL) { - NPC_MOVE_CALLBACKS[moveType].init(npc); - } -} - -void npcMovement(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_NPC) return; - - npc_t *npc = &entity->data.npc; - if(npc->interactState != NPC_INTERACT_STATE_NONE) return; - - const npcmovecallback_t *cb = &NPC_MOVE_CALLBACKS[npc->moveType]; - if(cb->movement != NULL) cb->movement(entity); -} \ No newline at end of file diff --git a/src/dusk/rpg/entity/npc/npc.h b/src/dusk/rpg/entity/npc/npc.h deleted file mode 100644 index be16ee9b..00000000 --- a/src/dusk/rpg/entity/npc/npc.h +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "npcturn.h" -#include "npcwalk.h" -#include "npcpath.h" - -typedef struct entity_s entity_t; - -typedef enum { - NPC_INTERACT_STATE_NONE, - NPC_INTERACT_STATE_CONVERSING, - NPC_INTERACT_STATE_COUNT -} npcinteractstate_t; - -typedef enum { - NPC_MOVE_TYPE_NULL, - NPC_MOVE_TYPE_RANDOM_TURN, - NPC_MOVE_TYPE_RANDOM_WALK, - NPC_MOVE_TYPE_RANDOM_TURN_AND_WALK, - NPC_MOVE_TYPE_PATH, - NPC_MOVE_TYPE_COUNT -} npcmovetype_t; - -typedef union { - npcrandomturn_t randomTurn; - npcrandomwalk_t randomWalk; - npcrandomturnandwalk_t randomTurnAndWalk; - npcpath_t path; -} npcmovedata_t; - -typedef struct npc_s { - npcinteractstate_t interactState; - npcmovetype_t moveType; - npcmovedata_t moveData; -} npc_t; - -typedef struct { - /** Called once when the move type is set. */ - void (*init)(npc_t *npc); - /** Called each movement tick. */ - void (*movement)(entity_t *entity); -} npcmovecallback_t; - -extern const npcmovecallback_t NPC_MOVE_CALLBACKS[NPC_MOVE_TYPE_COUNT]; - -/** - * Initializes an NPC entity. - * - * @param entity Pointer to the entity structure to initialize. - */ -void npcInit(entity_t *entity); - -/** - * Sets the movement type for an NPC entity. - * - * @param entity Pointer to the entity structure. - * @param moveType The movement type to set. - */ -void npcSetMoveType(entity_t *entity, const npcmovetype_t moveType); - -/** - * Movement callback for an NPC entity. Gated by cutscene input. - * - * @param entity Pointer to the entity structure to update. - */ -void npcMovement(entity_t *entity); - -/** - * Free movement callback for an NPC entity. Runs always-run move types - * regardless of cutscene state. - * - * @param entity Pointer to the entity structure to update. - */ -void npcFreeMovement(entity_t *entity); \ No newline at end of file diff --git a/src/dusk/rpg/entity/npc/npcpath.c b/src/dusk/rpg/entity/npc/npcpath.c deleted file mode 100644 index 9d6ea47d..00000000 --- a/src/dusk/rpg/entity/npc/npcpath.c +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "npc.h" -#include "rpg/entity/entity.h" -#include "rpg/entity/entitypathstep.h" -#include "rpg/overworld/worldpos.h" -#include "assert/assert.h" - -void npcPathInit(npc_t *npc) { - npcpath_t *path = &npc->moveData.path; - path->count = 0; - path->index = 0; -} - -void npcPathAddNode(npc_t *npc, const worldpos_t pos) { - assertNotNull(npc, "NPC must not be NULL"); - assertTrue( - npc->moveData.path.count < NPC_PATH_COUNT_MAX, - "NPC path is full" - ); - npc->moveData.path.positions[npc->moveData.path.count++] = pos; -} - -void npcPathMovement(entity_t *entity) { - npcpath_t *path = &entity->data.npc.moveData.path; - if(path->count == 0) return; - if(!entityCanWalk(entity)) return; - - // Advance past any waypoints already reached (including the current one). - worldpos_t *target = &path->positions[path->index]; - if(entityPathStep(entity, *target, false)) { - path->index = (path->index + 1) % path->count; - target = &path->positions[path->index]; - // New target is the same tile - nothing to do this tick - if(worldPosIsEqual(entity->position, *target)) return; - entityPathStep(entity, *target, false); - } -} diff --git a/src/dusk/rpg/entity/npc/npcpath.h b/src/dusk/rpg/entity/npc/npcpath.h deleted file mode 100644 index d6469168..00000000 --- a/src/dusk/rpg/entity/npc/npcpath.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "rpg/overworld/worldpos.h" - -typedef struct npc_s npc_t; -typedef struct entity_s entity_t; - -/** Maximum number of waypoints in an NPC path. */ -#define NPC_PATH_COUNT_MAX 8 - -typedef struct { - worldpos_t positions[NPC_PATH_COUNT_MAX]; - uint8_t count; - uint8_t index; -} npcpath_t; - -/** - * Initializes the path movement data for an NPC. - * - * @param npc Pointer to the NPC to initialize. - */ -void npcPathInit(npc_t *npc); - -/** - * Appends a waypoint to the NPC's path. Has no effect if the path is full. - * - * @param npc Pointer to the NPC. - * @param pos The world position to append. - */ -void npcPathAddNode(npc_t *npc, const worldpos_t pos); - -/** - * Movement tick for an NPC following a path. - * - * @param entity Pointer to the entity to update. - */ -void npcPathMovement(entity_t *entity); diff --git a/src/dusk/rpg/entity/npc/npcturn.c b/src/dusk/rpg/entity/npc/npcturn.c deleted file mode 100644 index 63654ab3..00000000 --- a/src/dusk/rpg/entity/npc/npcturn.c +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "npc.h" -#include "rpg/entity/entity.h" -#include "util/random.h" -#include "time/time.h" -#include - -void npcRandomTurnInit(npc_t *npc) { - npcrandomturn_t *turn = &npc->moveData.randomTurn; - turn->frequencyMin = NPC_RANDOM_TURN_FREQUENCY_MIN_DEFAULT; - turn->frequencyMax = NPC_RANDOM_TURN_FREQUENCY_MAX_DEFAULT; - turn->timer = randomFloat(turn->frequencyMin, turn->frequencyMax); -} - -void npcRandomTurnMovement(entity_t *entity) { - npcrandomturn_t *turn = &entity->data.npc.moveData.randomTurn; - turn->timer -= TIME.delta; - if(turn->timer > 0.0f) return; - turn->timer = randomFloat(turn->frequencyMin, turn->frequencyMax); - if(entityCanTurn(entity)) entityTurn(entity, (entitydir_t)(rand() % 4)); -} diff --git a/src/dusk/rpg/entity/npc/npcturn.h b/src/dusk/rpg/entity/npc/npcturn.h deleted file mode 100644 index 3568bbdb..00000000 --- a/src/dusk/rpg/entity/npc/npcturn.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct npc_s npc_t; -typedef struct entity_s entity_t; - -/** Default min/max seconds between NPC random-turn ticks. */ -#define NPC_RANDOM_TURN_FREQUENCY_MIN_DEFAULT 2.0f -#define NPC_RANDOM_TURN_FREQUENCY_MAX_DEFAULT 4.0f - -typedef struct { - float_t frequencyMin; - float_t frequencyMax; - float_t timer; -} npcrandomturn_t; - -/** - * Initializes the random-turn movement data for an NPC. - * - * @param npc Pointer to the NPC to initialize. - */ -void npcRandomTurnInit(npc_t *npc); - -/** - * Movement tick for an NPC using random-turn movement. - * - * @param entity Pointer to the entity to update. - */ -void npcRandomTurnMovement(entity_t *entity); diff --git a/src/dusk/rpg/entity/npc/npcwalk.c b/src/dusk/rpg/entity/npc/npcwalk.c deleted file mode 100644 index dc8794ec..00000000 --- a/src/dusk/rpg/entity/npc/npcwalk.c +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "npc.h" -#include "rpg/entity/entity.h" -#include "util/random.h" -#include "time/time.h" -#include - -void npcRandomWalkInit(npc_t *npc) { - npcrandomwalk_t *walk = &npc->moveData.randomWalk; - walk->frequencyMin = NPC_RANDOM_WALK_FREQUENCY_MIN_DEFAULT; - walk->frequencyMax = NPC_RANDOM_WALK_FREQUENCY_MAX_DEFAULT; - walk->timer = randomFloat(walk->frequencyMin, walk->frequencyMax); -} - -void npcRandomWalkMovement(entity_t *entity) { - npcrandomwalk_t *walk = &entity->data.npc.moveData.randomWalk; - walk->timer -= TIME.delta; - if(walk->timer > 0.0f) return; - walk->timer = randomFloat(walk->frequencyMin, walk->frequencyMax); - if(entityCanWalk(entity)) entityWalk(entity, (entitydir_t)(rand() % 4)); -} - -void npcRandomTurnAndWalkInit(npc_t *npc) { - npcRandomTurnInit(npc); - npcrandomturnandwalk_t *tw = &npc->moveData.randomTurnAndWalk; - tw->walk.frequencyMin = NPC_RANDOM_WALK_FREQUENCY_MIN_DEFAULT; - tw->walk.frequencyMax = NPC_RANDOM_WALK_FREQUENCY_MAX_DEFAULT; - tw->walk.timer = randomFloat(tw->walk.frequencyMin, tw->walk.frequencyMax); -} - -void npcRandomTurnAndWalkMovement(entity_t *entity) { - npcrandomturnandwalk_t *tw = &entity->data.npc.moveData.randomTurnAndWalk; - - tw->turn.timer -= TIME.delta; - if(tw->turn.timer <= 0.0f) { - tw->turn.timer = randomFloat(tw->turn.frequencyMin, tw->turn.frequencyMax); - if(entityCanTurn(entity)) entityTurn(entity, (entitydir_t)(rand() % 4)); - } - - tw->walk.timer -= TIME.delta; - if(tw->walk.timer <= 0.0f) { - tw->walk.timer = randomFloat(tw->walk.frequencyMin, tw->walk.frequencyMax); - if(entityCanWalk(entity)) entityWalk(entity, (entitydir_t)(rand() % 4)); - } -} - diff --git a/src/dusk/rpg/entity/npc/npcwalk.h b/src/dusk/rpg/entity/npc/npcwalk.h deleted file mode 100644 index 4a65c4f8..00000000 --- a/src/dusk/rpg/entity/npc/npcwalk.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "npcturn.h" - -/** Default min/max seconds between NPC random-walk ticks. */ -#define NPC_RANDOM_WALK_FREQUENCY_MIN_DEFAULT 2.0f -#define NPC_RANDOM_WALK_FREQUENCY_MAX_DEFAULT 5.0f - -typedef struct { - float_t frequencyMin; - float_t frequencyMax; - float_t timer; -} npcrandomwalk_t; - -typedef struct { - npcrandomturn_t turn; - npcrandomwalk_t walk; -} npcrandomturnandwalk_t; - -/** - * Initializes the random-walk movement data for an NPC. - * - * @param npc Pointer to the NPC to initialize. - */ -void npcRandomWalkInit(npc_t *npc); - -/** - * Movement tick for an NPC using random-walk movement. - * - * @param entity Pointer to the entity to update. - */ -void npcRandomWalkMovement(entity_t *entity); - -/** - * Initializes the random-turn-and-walk movement data for an NPC. - * - * @param npc Pointer to the NPC to initialize. - */ -void npcRandomTurnAndWalkInit(npc_t *npc); - -/** - * Movement tick for an NPC using random-turn-and-walk movement. - * - * @param entity Pointer to the entity to update. - */ -void npcRandomTurnAndWalkMovement(entity_t *entity); - diff --git a/src/dusk/rpg/entity/player.c b/src/dusk/rpg/entity/player.c deleted file mode 100644 index c3ff2119..00000000 --- a/src/dusk/rpg/entity/player.c +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "entity.h" -#include "assert/assert.h" -#include "rpg/rpgcamera.h" -#include "util/memory.h" -#include "time/time.h" -#include "ui/focus/uifocus.h" -#include "ui/screen/game/uigamemenu.h" -#include "rpg/cutscene/cutscenesystem.h" - -void playerInit(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - entity->globalId = ENTITY_GLOBAL_ID_PLAYER; -} - -bool_t playerCanInteract(entity_t *entity) { - return entity->animation == ENTITY_ANIM_IDLE; -} - -void playerInput(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - if(CUTSCENE_SYSTEM.pause & CUTSCENE_PAUSE_PLAYER) return; - - // Toggle game menu on pause - if(uiGameMenuIsOpen() && inputPressed(INPUT_ACTION_PAUSE)) { - uiGameMenuClose(); - } else if( - inputPressed(INPUT_ACTION_PAUSE) && - entity->animation == ENTITY_ANIM_IDLE - ) { - uiGameMenuOpen(); - } - - // Can player act? - if(UI_FOCUS.count > 0) return; - - // Determine direction player may be trying to face/go. - const playerinputdirmap_t *dirMap; - - // First pass, we want to prefer a button relative to what the player is - // already facing, e.g. if they hold DOWN and start moving down, then tap - // right we want to prefer down over right. - - // Determine current dirmap based on facing direction. - dirMap = PLAYER_INPUT_DIR_MAP; - do { - if(dirMap->direction != entity->direction) continue; - break; - } while((++dirMap)->action != 0xFF); - - // Are we holding that button? - if(!inputIsDown(dirMap->action)) { - // No, so we need to check all directions. - dirMap = PLAYER_INPUT_DIR_MAP; - do { - if(!inputIsDown(dirMap->action)) continue; - break; - } while((++dirMap)->action != 0xFF); - } - - // Trying to direct? - if(dirMap->action != 0xFF) { - // Is the player attempting to turn? That is, is the player idle and has the - // turn lockout passed? - if(entityCanTurn(entity)) { - if(entity->direction != dirMap->direction) { - entityTurn(entity, dirMap->direction); - return; - } - } - - bool_t running = inputIsDown(INPUT_ACTION_CANCEL); - if(running && entityCanRun(entity)) { - entityRun(entity, dirMap->direction); - } else if(entityCanWalk(entity)) { - entityWalk(entity, dirMap->direction); - } - } - - // Interaction - if(inputPressed(INPUT_ACTION_ACCEPT) && playerCanInteract(entity)) { - worldunit_t x, y, z; - { - worldunits_t relX, relY; - entityDirGetRelative(entity->direction, &relX, &relY); - x = entity->position.x + relX; - y = entity->position.y + relY; - z = entity->position.z; - } - - entity_t *target = entityGetAt((worldpos_t){ x, y, z }); - if(target == NULL) return; - entityInteractWith(entity, target); - } -} \ No newline at end of file diff --git a/src/dusk/rpg/entity/player.h b/src/dusk/rpg/entity/player.h deleted file mode 100644 index 13e369d1..00000000 --- a/src/dusk/rpg/entity/player.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "input/input.h" -#include "entitydir.h" - -typedef struct entity_s entity_t; - -typedef struct { - void *nothing; -} player_t; - -typedef struct { - inputaction_t action; - entitydir_t direction; -} playerinputdirmap_t; - -static const playerinputdirmap_t PLAYER_INPUT_DIR_MAP[] = { - { INPUT_ACTION_UP, ENTITY_DIR_NORTH }, - { INPUT_ACTION_DOWN, ENTITY_DIR_SOUTH }, - { INPUT_ACTION_LEFT, ENTITY_DIR_WEST }, - { INPUT_ACTION_RIGHT, ENTITY_DIR_EAST }, - - { 0xFF, 0xFF } -}; - -/** - * Initializes a player entity. - * - * @param entity Pointer to the entity structure to initialize. - */ -void playerInit(entity_t *entity); - -/** - * Returns true if the player entity is in a state where it can interact. - * - * @param entity Pointer to the player entity to check. - * @returns True if the entity can interact. - */ -bool_t playerCanInteract(entity_t *entity); - -/** - * Handles movement logic for the player entity. - * - * @param entity Pointer to the player entity structure. - */ -void playerInput(entity_t *entity); \ No newline at end of file diff --git a/src/dusk/rpg/item/CMakeLists.txt b/src/dusk/rpg/item/CMakeLists.txt deleted file mode 100644 index 29e79fb0..00000000 --- a/src/dusk/rpg/item/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - item.c - inventory.c - backpack.c - itemgive.c -) \ No newline at end of file diff --git a/src/dusk/rpg/item/backpack.c b/src/dusk/rpg/item/backpack.c deleted file mode 100644 index b668d0dd..00000000 --- a/src/dusk/rpg/item/backpack.c +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "backpack.h" -#include "assert/assert.h" - -backpack_t BACKPACK; - -void backpackInit() { - for(itemtype_t i = ITEM_TYPE_NULL + 1; i < ITEM.typeCount; i++) { - inventoryInit( - &BACKPACK.inventories[i], - BACKPACK.storage[i], - ITEM_DEF_COUNT_MAX - ); - } -} - -inventory_t *backpackGetInventory(const itemtype_t type) { - assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null"); - assertTrue(type < ITEM.typeCount, "Item type out of range"); - return &BACKPACK.inventories[type]; -} - -void backpackAdd(const itemid_t item, const uint8_t quantity) { - inventoryAdd(backpackGetInventory(itemGetTypeById(item)), item, quantity); -} - -void backpackRemove(const itemid_t item) { - inventoryRemove(backpackGetInventory(itemGetTypeById(item)), item); -} - -void backpackSet(const itemid_t item, const uint8_t quantity) { - inventorySet(backpackGetInventory(itemGetTypeById(item)), item, quantity); -} - -uint8_t backpackGetCount(const itemid_t item) { - return inventoryGetCount(backpackGetInventory(itemGetTypeById(item)), item); -} - -bool_t backpackItemExists(const itemid_t item) { - return inventoryItemExists(backpackGetInventory(itemGetTypeById(item)), item); -} - -bool_t backpackIsFull(const itemtype_t type) { - assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null"); - assertTrue(type < ITEM.typeCount, "Item type out of range"); - return inventoryIsFull(backpackGetInventory(type)); -} - -bool_t backpackItemFull(const itemid_t item) { - return inventoryItemFull(backpackGetInventory(itemGetTypeById(item)), item); -} - -void backpackSort( - const itemtype_t type, - const inventorysort_t sortBy, - const bool_t reverse -) { - assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null"); - assertTrue(type < ITEM.typeCount, "Item type out of range"); - inventorySort(backpackGetInventory(type), sortBy, reverse); -} diff --git a/src/dusk/rpg/item/backpack.h b/src/dusk/rpg/item/backpack.h deleted file mode 100644 index a8ab4419..00000000 --- a/src/dusk/rpg/item/backpack.h +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "inventory.h" - -typedef struct { - inventorystack_t storage[ITEM_TYPE_DEF_COUNT_MAX][ITEM_DEF_COUNT_MAX]; - inventory_t inventories[ITEM_TYPE_DEF_COUNT_MAX]; -} backpack_t; - -extern backpack_t BACKPACK; - -/** - * Initializes the backpack inventory for the player. - */ -void backpackInit(); - -/** - * Returns the inventory for a given item type. - * - * @param type The item type. - * @returns Pointer to the inventory for that type. - */ -inventory_t *backpackGetInventory(const itemtype_t type); - -/** - * Adds a quantity of an item to the backpack. - * - * @param item The item ID to add. - * @param quantity The quantity to add. - */ -void backpackAdd(const itemid_t item, const uint8_t quantity); - -/** - * Removes an item from the backpack. - * - * @param item The item ID to remove. - */ -void backpackRemove(const itemid_t item); - -/** - * Sets the quantity of an item in the backpack. - * - * @param item The item ID to set. - * @param quantity The quantity to set. - */ -void backpackSet(const itemid_t item, const uint8_t quantity); - -/** - * Gets the quantity of an item in the backpack. - * - * @param item The item ID to check. - * @returns The quantity held. - */ -uint8_t backpackGetCount(const itemid_t item); - -/** - * Checks if an item exists in the backpack (quantity > 0). - * - * @param item The item ID to check. - * @returns true if the item exists. - */ -bool_t backpackItemExists(const itemid_t item); - -/** - * Checks if the inventory for a given type is full. - * - * @param type The item type to check. - * @returns true if the type's inventory is full. - */ -bool_t backpackIsFull(const itemtype_t type); - -/** - * Checks if an item's stack is full in the backpack. - * - * @param item The item ID to check. - * @returns true if the item stack is full. - */ -bool_t backpackItemFull(const itemid_t item); - -/** - * Sorts the inventory for a given item type. - * - * @param type The item type whose inventory to sort. - * @param sortBy The sorting criteria. - * @param reverse Whether to sort in reverse order. - */ -void backpackSort( - const itemtype_t type, - const inventorysort_t sortBy, - const bool_t reverse -); \ No newline at end of file diff --git a/src/dusk/rpg/item/inventory.c b/src/dusk/rpg/item/inventory.c deleted file mode 100644 index 909b7296..00000000 --- a/src/dusk/rpg/item/inventory.c +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "inventory.h" -#include "util/memory.h" -#include "util/sort.h" -#include "assert/assert.h" - -void inventoryInit( - inventory_t* inventory, - inventorystack_t* storage, - uint8_t storageSize -) { - assertNotNull(inventory, "Inventory pointer is NULL."); - assertNotNull(storage, "Storage pointer is NULL."); - assertTrue(storageSize > 0, "Storage size must be greater than zero."); - - inventory->storage = storage; - inventory->storageSize = storageSize; - - memoryZero(storage, sizeof(inventorystack_t) * storageSize); -} - -bool_t inventoryItemExists(const inventory_t *inventory, const itemid_t item) { - assertNotNull(inventory, "Inventory pointer is NULL."); - assertNotNull(inventory->storage, "Storage pointer is NULL."); - assertTrue(inventory->storageSize > 0, "Storage too small."); - assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL."); - - inventorystack_t *stack = inventory->storage; - inventorystack_t *end = stack + inventory->storageSize; - do { - if(stack->item == ITEM_ID_NULL) break; - if(stack->item != item) continue; - assertTrue(stack->quantity > 0, "Item has quantity zero."); - return true; - } while(++stack < end); - - return false; -} - -void inventorySet( - inventory_t *inventory, - const itemid_t item, - const uint8_t quantity -) { - assertNotNull(inventory, "Inventory pointer is NULL."); - assertNotNull(inventory->storage, "Storage pointer is NULL."); - assertTrue(inventory->storageSize > 0, "Storage too small."); - assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL."); - - // If quantity 0, remove. - if(quantity == 0) return inventoryRemove(inventory, item); - - // Search for existing stack. - inventorystack_t *stack = inventory->storage; - inventorystack_t *end = stack + inventory->storageSize; - do { - // Not in inventory yet, add as new stack. - if(stack->item == ITEM_ID_NULL) { - stack->item = item; - stack->quantity = quantity; - return; - } - - // Not the stack we're looking for. - if(stack->item != item) continue; - - // Update existing stack. - stack->quantity = quantity; - return; - } while(++stack < end); - - // No space in the inventory. - assertUnreachable("Inventory is full, cannot set more items."); -} - -void inventoryAdd( - inventory_t *inventory, - const itemid_t item, - const uint8_t quantity -) { - uint8_t current = inventoryGetCount(inventory, item); - uint16_t newQuantity = (uint16_t)current + (uint16_t)quantity; - - assertTrue( - newQuantity <= UINT8_MAX, - "Cannot add item, would overflow maximum quantity." - ); - - inventorySet(inventory, item, (uint8_t)newQuantity); -} - -void inventoryRemove(inventory_t *inventory, const itemid_t item) { - assertNotNull(inventory, "Inventory pointer is NULL."); - assertNotNull(inventory->storage, "Storage pointer is NULL."); - assertTrue(inventory->storageSize > 0, "Storage too small."); - assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL."); - - inventorystack_t *stack = inventory->storage; - inventorystack_t *end = stack + inventory->storageSize; - - // Search for existing stack. - do { - // End of inventory, item not present. - if(stack->item == ITEM_ID_NULL) break; - - // Not matching stack. - if(stack->item != item) continue; - - // Match found, shift everything else down - memoryMove( - stack, - stack + 1, - (end - (stack + 1)) * sizeof(inventorystack_t) - ); - - // Clear last stack. - inventorystack_t *last = end - 1; - last->item = ITEM_ID_NULL; - - break; - } while(++stack < end); -} - -uint8_t inventoryGetCount(const inventory_t *inventory, const itemid_t item) { - assertNotNull(inventory, "Inventory pointer is NULL."); - assertNotNull(inventory->storage, "Storage pointer is NULL."); - assertTrue(inventory->storageSize > 0, "Storage too small."); - assertTrue(item != ITEM_ID_NULL, "Item ID cannot be ITEM_ID_NULL."); - - inventorystack_t *stack = inventory->storage; - inventorystack_t *end = stack + inventory->storageSize; - do { - // End of inventory, item not present. - if(stack->item == ITEM_ID_NULL) break; - - // Not matching stack. - if(stack->item != item) continue; - - // Match found, return quantity. - return stack->quantity; - } while(++stack < end); - - return 0; -} - -bool_t inventoryIsFull(const inventory_t *inventory) { - assertNotNull(inventory, "Inventory pointer is NULL."); - assertNotNull(inventory->storage, "Storage pointer is NULL."); - assertTrue(inventory->storageSize > 0, "Storage too small."); - - inventorystack_t *stack = inventory->storage; - inventorystack_t *end = stack + inventory->storageSize; - do { - // Found empty stack, not full. - if(stack->item == ITEM_ID_NULL) return false; - } while(++stack < end); - - return true; -} - -bool_t inventoryItemFull(const inventory_t *inventory, const itemid_t item) { - return inventoryGetCount(inventory, item) == ITEM_STACK_QUANTITY_MAX; -} - -// Sorters -int_t inventorySortById(const void *a, const void *b) { - const inventorystack_t *stackA = (const inventorystack_t*)a; - const inventorystack_t *stackB = (const inventorystack_t*)b; - if(stackA->item < stackB->item) return -1; - if(stackA->item > stackB->item) return 1; - return 0; -} - -int_t inventorySortByIdReverse(const void *a, const void *b) { - const inventorystack_t *stackA = (const inventorystack_t*)a; - const inventorystack_t *stackB = (const inventorystack_t*)b; - if(stackA->item < stackB->item) return 1; - if(stackA->item > stackB->item) return -1; - return 0; -} - -int_t inventorySortByType(const void *a, const void *b) { - const inventorystack_t *stackA = (const inventorystack_t*)a; - const inventorystack_t *stackB = (const inventorystack_t*)b; - const itemtype_t typeA = itemGetTypeById(stackA->item); - const itemtype_t typeB = itemGetTypeById(stackB->item); - if(typeA < typeB) return -1; - if(typeA > typeB) return 1; - return 0; -} - -int_t inventorySortByTypeReverse(const void *a, const void *b) { - const inventorystack_t *stackA = (const inventorystack_t*)a; - const inventorystack_t *stackB = (const inventorystack_t*)b; - const itemtype_t typeA = itemGetTypeById(stackA->item); - const itemtype_t typeB = itemGetTypeById(stackB->item); - if(typeA < typeB) return 1; - if(typeA > typeB) return -1; - return 0; -} - -void inventorySort( - inventory_t *inventory, - const inventorysort_t sortBy, - const bool_t reverse -) { - assertNotNull(inventory, "Inventory pointer is NULL."); - assertNotNull(inventory->storage, "Storage pointer is NULL."); - assertTrue(inventory->storageSize > 0, "Storage too small."); - assertTrue(sortBy < INVENTORY_SORT_COUNT, "Invalid sort type."); - - // Get count of used stacks - size_t count = 0; - inventorystack_t *stack = inventory->storage; - inventorystack_t *end = stack + inventory->storageSize; - do { - if(stack->item == ITEM_ID_NULL) break; - count++; - } while(++stack < end); - - if(count == 0) return; // Nothing to sort - - // Comparator - sortcompare_t comparator = NULL; - switch(sortBy) { - case INVENTORY_SORT_BY_ID: { - comparator = reverse ? inventorySortByIdReverse : inventorySortById; - break; - }; - - case INVENTORY_SORT_BY_TYPE: { - comparator = reverse ? inventorySortByTypeReverse : inventorySortByType; - break; - }; - - default: - assertUnreachable("Invalid sort type."); - break; - } - - assertNotNull(comparator, "Comparator function is NULL."); - - sort((void*)inventory->storage, count, sizeof(inventorystack_t), comparator); -} \ No newline at end of file diff --git a/src/dusk/rpg/item/inventory.h b/src/dusk/rpg/item/inventory.h deleted file mode 100644 index ba559dfe..00000000 --- a/src/dusk/rpg/item/inventory.h +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/item/item.h" - -#define ITEM_STACK_QUANTITY_MAX 99 - -typedef enum { - INVENTORY_SORT_BY_ID, - INVENTORY_SORT_BY_TYPE, - - INVENTORY_SORT_COUNT -} inventorysort_t; - -typedef struct { - itemid_t item; - uint8_t quantity; -} inventorystack_t; - -typedef struct { - inventorystack_t *storage; - uint8_t storageSize; -} inventory_t; - -/** - * Initializes an inventory. - * - * @param inventory The inventory to initialize. - * @param storage The storage array for the inventory. - * @param storageSize The size of the storage array. - */ -void inventoryInit( - inventory_t* inventory, - inventorystack_t* storage, - uint8_t storageSize -); - -/** - * Checks if a specific item exists in the inventory (and has quantity > 0). - * - * @param inventory The inventory to check. - * @param item The item ID to check. - * @return true if the item exists, false otherwise. - */ -bool_t inventoryItemExists(const inventory_t *inventory, const itemid_t item); - -/** - * Sets the quantity of a specific item in the inventory. - * - * @param inventory The inventory to modify. - * @param item The item ID to set. - * @param quantity The quantity to set. - */ -void inventorySet( - inventory_t *inventory, - const itemid_t item, - const uint8_t quantity -); - -/** - * Adds a specific quantity of an item to the inventory. - * - * @param inventory The inventory to modify. - * @param item The item ID to add. - * @param quantity The quantity to add. - */ -void inventoryAdd( - inventory_t *inventory, - const itemid_t item, - const uint8_t quantity -); - -/** - * Removes an item from the inventory. - * - * @param inventory The inventory to modify. - * @param item The item ID to remove. - */ -void inventoryRemove(inventory_t *inventory, const itemid_t item); - -/** - * Gets the count of a specific item in the inventory. - * - * @param inventory The inventory to check. - * @param item The item ID to check. - * @return The count of the item in the inventory. - */ -uint8_t inventoryGetCount(const inventory_t *inventory, const itemid_t item); - -/** - * Checks if the inventory is full. - * - * @param inventory The inventory to check. - * @return true if full, false otherwise. - */ -bool_t inventoryIsFull(const inventory_t *inventory); - -/** - * Checks if a specific item stack is full in the inventory. - * - * @param inventory The inventory to check. - * @param item The item ID to check. - * @return true if the item stack is full, false otherwise. - */ -bool_t inventoryItemFull(const inventory_t *inventory, const itemid_t item); - -/** - * Sorts the inventory based on the specified criteria. - * - * @param inventory The inventory to sort. - * @param sortBy The sorting criteria. - * @param reverse Whether to sort in reverse order. - */ -void inventorySort( - inventory_t *inventory, - const inventorysort_t sortBy, - const bool_t reverse -); \ No newline at end of file diff --git a/src/dusk/rpg/item/item.c b/src/dusk/rpg/item/item.c deleted file mode 100644 index 4be5f980..00000000 --- a/src/dusk/rpg/item/item.c +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "item.h" -#include "assert/assert.h" -#include "locale/localemanager.h" -#include "asset/asset.h" -#include "asset/loader/locale/assetlocaleloader.h" -#include "util/memory.h" -#include "util/string.h" -#include "yyjson.h" - -item_t ITEM; - -itemtype_t itemTypeGetOrCreate(const char_t *id) { - for(itemtype_t type = ITEM_TYPE_NULL + 1; type < ITEM.typeCount; type++) { - if(stringEquals(ITEM.types[type].id, id)) return type; - } - - assertTrue( - ITEM.typeCount < ITEM_TYPE_DEF_COUNT_MAX, "Too many item types defined." - ); - itemtype_t type = ITEM.typeCount++; - stringCopy(ITEM.types[type].id, id, ITEM_TYPE_NAME_MAX); - return type; -} - -errorret_t itemInit(void) { - memoryZero(&ITEM, sizeof(ITEM)); - ITEM.typeCount = ITEM_TYPE_NULL + 1; - ITEM.defCount = ITEM_ID_NULL + 1; - - assetentry_t *entry = - assetLock("items/items.jsonc", ASSET_LOADER_TYPE_JSON, NULL); - errorret_t ret = assetRequireLoaded(entry); - if(errorIsNotOk(ret)) { - assetUnlockEntry(entry); - errorChain(ret); - } - - yyjson_val *root = yyjson_doc_get_root(entry->data.json); - yyjson_val *items = yyjson_obj_get(root, "items"); - - yyjson_obj_iter iter = yyjson_obj_iter_with(items); - yyjson_val *key; - while((key = yyjson_obj_iter_next(&iter))) { - assertTrue(ITEM.defCount < ITEM_DEF_COUNT_MAX, "Too many item ids defined."); - - yyjson_val *val = yyjson_obj_iter_get_val(key); - const char_t *typeId = yyjson_get_str(yyjson_obj_get(val, "type")); - - itemid_t id = ITEM.defCount++; - stringCopy(ITEM.defs[id].id, yyjson_get_str(key), ITEM_ID_STRING_MAX); - ITEM.defs[id].type = itemTypeGetOrCreate(typeId); - } - - assetUnlockEntry(entry); - errorOk(); -} - -const itemdef_t *itemGetDefById(const itemid_t id) { - assertTrue(id > ITEM_ID_NULL, "Item ID must not be null"); - assertTrue(id < ITEM.defCount, "Item ID out of range"); - - return &ITEM.defs[id]; -} - -itemtype_t itemGetTypeById(const itemid_t id) { - return itemGetDefById(id)->type; -} - -itemid_t itemGetIdByName(const char_t *name) { - assertNotNull(name, "name must not be NULL"); - - for(itemid_t id = ITEM_ID_NULL + 1; id < ITEM.defCount; id++) { - if(stringEquals(ITEM.defs[id].id, name)) return id; - } - - return ITEM_ID_NULL; -} - -errorret_t itemGetName( - const itemid_t item, - char_t *buffer, - const size_t bufferSize -) { - const itemdef_t *def = itemGetDefById(item); - - char_t key[ITEM_ID_STRING_MAX + 16]; - stringFormat(key, sizeof(key), "item.%s.name", def->id); - errorChain(localeManagerGetString(key, buffer, bufferSize, 0)); - - errorOk(); -} diff --git a/src/dusk/rpg/item/item.h b/src/dusk/rpg/item/item.h deleted file mode 100644 index ccb42841..00000000 --- a/src/dusk/rpg/item/item.h +++ /dev/null @@ -1,91 +0,0 @@ -/** - * 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 ITEM_TYPE_NAME_MAX 12 -#define ITEM_TYPE_DEF_COUNT_MAX 32 -#define ITEM_ID_STRING_MAX 12 -#define ITEM_DEF_COUNT_MAX 64 - -typedef uint8_t itemtype_t; -typedef uint16_t itemid_t; -#define ITEM_TYPE_NULL ((itemtype_t)0) -#define ITEM_ID_NULL ((itemid_t)0) - -typedef struct { - char_t id[ITEM_TYPE_NAME_MAX]; -} itemtypedef_t; - -typedef struct { - itemtype_t type; - char_t id[ITEM_ID_STRING_MAX]; -} itemdef_t; - -typedef struct { - itemtypedef_t types[ITEM_TYPE_DEF_COUNT_MAX]; - itemtype_t typeCount; - itemdef_t defs[ITEM_DEF_COUNT_MAX]; - itemid_t defCount; -} item_t; - -extern item_t ITEM; - -/** - * Initializes the item system. - * - * @return Any error that occurs. - */ -errorret_t itemInit(void); - -/** - * Finds the itemtype_t matching id among the types registered so far, - * registering a new one if this is the first time it's been seen. - * - * @param id The type's identifier string (see itemtypedef_t.id). - * @return The itemtype_t for id. - */ -itemtype_t itemTypeGetOrCreate(const char_t *id); - -/** - * Gets the itemdef_t for an item id. - * - * @param id The item ID to look up. Must not be ITEM_ID_NULL. - * @return The itemdef_t for id. - */ -const itemdef_t *itemGetDefById(const itemid_t id); - -/** - * Gets the itemtype_t for an item id. - * - * @param id The item ID to look up. Must not be ITEM_ID_NULL. - * @return The itemtype_t for id. - */ -itemtype_t itemGetTypeById(const itemid_t id); - -/** - * Finds the itemid_t whose id string matches name. - * - * @param name The item's identifier string (see itemdef_t.id). - * @return The matching itemid_t, or ITEM_ID_NULL if none matched. - */ -itemid_t itemGetIdByName(const char_t *name); - -/** - * Gets the localized display name for an item. - * - * @param item The item ID to look up. Must not be ITEM_ID_NULL. - * @param buffer Buffer to write the localized name into. - * @param bufferSize Size of the buffer. - * @return Any error that occurs. - */ -errorret_t itemGetName( - const itemid_t item, - char_t *buffer, - const size_t bufferSize -); diff --git a/src/dusk/rpg/item/itemgive.c b/src/dusk/rpg/item/itemgive.c deleted file mode 100644 index c7416dc2..00000000 --- a/src/dusk/rpg/item/itemgive.c +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "itemgive.h" -#include "rpg/item/backpack.h" -#include "rpg/item/item.h" -#include "ui/rpg/textbox/uitextboxmain.h" -#include "util/string.h" -#include "error/error.h" - -#define ITEM_GIVE_NAME_MAX_CHARS 32 - -void itemGive(const itemid_t item, const uint8_t quantity) { - backpackAdd(item, quantity); - - char_t name[ITEM_GIVE_NAME_MAX_CHARS]; - errorCatch(itemGetName(item, name, ITEM_GIVE_NAME_MAX_CHARS)); - - char_t msg[ITEM_GIVE_MESSAGE_MAX_CHARS]; - stringFormat( - msg, - ITEM_GIVE_MESSAGE_MAX_CHARS - 1, - "Received %s x%u", - name, - (uint32_t)quantity - ); - uiTextboxMainSetText(msg); -} diff --git a/src/dusk/rpg/item/itemgive.h b/src/dusk/rpg/item/itemgive.h deleted file mode 100644 index 5fa02b91..00000000 --- a/src/dusk/rpg/item/itemgive.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/item/item.h" - -#define ITEM_GIVE_MESSAGE_MAX_CHARS 256 - -/** - * Adds a quantity of an item to the player's backpack and shows a - * "Received x" message in the main textbox. - * - * @param item The item ID to give. - * @param quantity The quantity to give. - */ -void itemGive(const itemid_t item, const uint8_t quantity); diff --git a/src/dusk/rpg/overworld/CMakeLists.txt b/src/dusk/rpg/overworld/CMakeLists.txt deleted file mode 100644 index 24c8e3f5..00000000 --- a/src/dusk/rpg/overworld/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - chunk.c - map.c - maparea.c - worldpos.c - tile.c - tileshape.c -) - -add_subdirectory(global) - diff --git a/src/dusk/rpg/overworld/chunk.c b/src/dusk/rpg/overworld/chunk.c deleted file mode 100644 index 18a5316e..00000000 --- a/src/dusk/rpg/overworld/chunk.c +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "chunk.h" - -uint32_t chunkGetTileIndex(const chunkpos_t position) { - return (position.y * CHUNK_WIDTH) + position.x; -} - -bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b) { - return (a.x == b.x) && (a.y == b.y) && (a.z == b.z); -} \ No newline at end of file diff --git a/src/dusk/rpg/overworld/chunk.h b/src/dusk/rpg/overworld/chunk.h deleted file mode 100644 index 48180256..00000000 --- a/src/dusk/rpg/overworld/chunk.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/tile.h" -#include "worldpos.h" - -#define CHUNK_MESH_COUNT_MAX 10 -#define CHUNK_MESH_NAME_MAX 64 -#define CHUNK_ENTITY_COUNT_MAX 10 -#define CHUNK_ENTITY_SPAWN_COUNT_MAX 8 -#define CHUNK_AREA_COUNT_MAX 4 - -typedef struct assetentry_s assetentry_t; - -typedef struct chunk_s { - chunkpos_t position; - tile_t tiles[CHUNK_TILE_COUNT]; - - assetentry_t *dcfEntry; - - uint8_t meshCount; - char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; - vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; - mat4 meshModels[CHUNK_MESH_COUNT_MAX]; - assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX]; - - uint8_t entities[CHUNK_ENTITY_COUNT_MAX]; - - // Map area IDs (into MAP_AREAS) spawned from this chunk's file data. - // Removed via mapAreaRemove when this chunk unloads, and re-added if it - // streams back in - unlike entities (tracked by current position via - // entities[] above), areas have no position-based ownership mechanism of - // their own, so the owning chunk must track and tear them down directly. - uint8_t areas[CHUNK_AREA_COUNT_MAX]; -} chunk_t; - -/** - * Gets the tile index for a tile position within a chunk. - * - * @param position The position within the chunk. - * @return The tile index within the chunk. - */ -uint32_t chunkGetTileIndex(const chunkpos_t position); - -/** - * Checks if two chunk positions are equal. - * - * @param a The first chunk position. - * @param b The second chunk position. - * @return true if equal, false otherwise. - */ -bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b); diff --git a/src/dusk/rpg/overworld/global/mapareaglobal.h b/src/dusk/rpg/overworld/global/mapareaglobal.h deleted file mode 100644 index 6f31dcd4..00000000 --- a/src/dusk/rpg/overworld/global/mapareaglobal.h +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "mapareaglobaldefs.h" -#include "mapareagloballist.h" - -#define MAP_AREA_CALLBACK_LIST_COUNT ( \ - sizeof(MAP_AREA_CALLBACK_LIST) / \ - sizeof(MAP_AREA_CALLBACK_LIST[0]) \ -) - -//EOF diff --git a/src/dusk/rpg/overworld/global/mapareaglobaldefs.h b/src/dusk/rpg/overworld/global/mapareaglobaldefs.h deleted file mode 100644 index 2f6d49b3..00000000 --- a/src/dusk/rpg/overworld/global/mapareaglobaldefs.h +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/maparea.h" - -#define MAP_AREA_CALLBACK(id) \ - static void MAP_AREA_CALLBACK_##id(entity_t *entity, const uint8_t trigger) - -#define MAP_AREA_CALLBACK_REF(id) \ - MAP_AREA_CALLBACK_##id - -//EOF diff --git a/src/dusk/rpg/overworld/global/mapareagloballist.h b/src/dusk/rpg/overworld/global/mapareagloballist.h deleted file mode 100644 index 9768582f..00000000 --- a/src/dusk/rpg/overworld/global/mapareagloballist.h +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "mapareaglobaldefs.h" -#include "console/console.h" - -MAP_AREA_CALLBACK(1) { - consolePrint("mapAreaGlobalCallback 1: trigger=%u", trigger); -} - -// Index 0 is reserved (not a valid callback ID) - see mapAreaAddGlobal. -static const mapareacallback_t MAP_AREA_CALLBACK_LIST[] = { - NULL, - MAP_AREA_CALLBACK_REF(1), -}; - -//EOF diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c deleted file mode 100644 index 78252261..00000000 --- a/src/dusk/rpg/overworld/map.c +++ /dev/null @@ -1,579 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "map.h" -#include "util/memory.h" -#include "assert/assert.h" -#include "asset/asset.h" -#include "asset/loader/assetloader.h" -#include "console/console.h" -#include "util/string.h" -#include "rpg/entity/global/entityglobal.h" -#include "rpg/entity/item/entityitem.h" -#include "rpg/overworld/maparea.h" -#include "save/save.h" -#include "time/time.h" - -map_t MAP; - -void mapChunkLoadingSlotClear(chunk_t *chunk) { - for(uint32_t i = 0; i < MAP_CHUNK_LOAD_CONCURRENCY; i++) { - if(MAP.loadingChunks[i] != chunk) continue; - MAP.loadingChunks[i] = NULL; - return; - } -} - -errorret_t mapInit() { - memoryZero(&MAP, sizeof(map_t)); - errorOk(); -} - -bool_t mapIsLoaded() { - return MAP.name[0] != '\0'; -} - -errorret_t mapSetMap(const char_t *map) { - assertNotNull(map, "mapSetMap: map name cannot be NULL"); - assertStrLenMax(map, MAP_NAME_MAX, "Map name too long"); - - if(mapIsLoaded()) { - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - mapChunkUnload(&MAP.chunks[i]); - } - } - - memoryZero(&MAP, sizeof(map_t)); - stringCopy(MAP.name, map, MAP_NAME_MAX); - - // Keep the current save slot's cached map name in sync so whichever map - // is active gets persisted next time the slot is actually saved. - stringCopy(SAVE.slot.cachedData.mapName, MAP.name, MAP_NAME_MAX); - - chunkindex_t i = 0; - for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) { - for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) { - for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) { - chunk_t *chunk = &MAP.chunks[i++]; - chunk->position = (chunkpos_t){ - (chunkunit_t)x, (chunkunit_t)y, (chunkunit_t)z - }; - errorChain(mapChunkLoad(chunk)); - } - } - } - - mapRebuildChunkOrder(); - errorOk(); -} - -errorret_t mapPositionSet(const chunkpos_t newPos) { - if(!mapIsLoaded()) errorThrow("No map loaded"); - if(chunkPositionIsEqual(newPos, MAP.chunkPosition)) errorOk(); - - // Separate loaded chunks into "keep" and "free" buckets. - chunkindex_t chunksFreed[MAP_CHUNK_COUNT]; - uint32_t freedCount = 0; - - // Use a boolean grid so the inner load loop can check O(1). - bool_t posLoaded[MAP_CHUNK_WIDTH][MAP_CHUNK_HEIGHT][MAP_CHUNK_DEPTH]; - memoryZero(posLoaded, sizeof(posLoaded)); - - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - chunk_t *chunk = &MAP.chunks[i]; - chunkunit_t rx = chunk->position.x - newPos.x; - chunkunit_t ry = chunk->position.y - newPos.y; - chunkunit_t rz = chunk->position.z - newPos.z; - if( - rx >= 0 && rx < MAP_CHUNK_WIDTH && - ry >= 0 && ry < MAP_CHUNK_HEIGHT && - rz >= 0 && rz < MAP_CHUNK_DEPTH - ) { - posLoaded[rx][ry][rz] = true; - } else { - mapChunkUnload(chunk); - chunksFreed[freedCount++] = i; - } - } - - for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) { - for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) { - for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) { - if(posLoaded[x][y][z]) continue; - assertTrue(freedCount > 0, "No free chunk slot available."); - chunk_t *chunk = &MAP.chunks[chunksFreed[--freedCount]]; - chunk->position = (chunkpos_t){ - newPos.x + (chunkunit_t)x, - newPos.y + (chunkunit_t)y, - newPos.z + (chunkunit_t)z - }; - errorChain(mapChunkLoad(chunk)); - } - } - } - - MAP.chunkPosition = newPos; - mapRebuildChunkOrder(); - - // chunkOrder was just reshuffled, so entity->chunkIndex (an index into - // chunkOrder, not a direct chunk_t pointer) may now resolve to a - // completely different chunk than the one that actually still tracks - // this entity in its entities[] slot - a chunk that stays loaded keeps - // its entities[] contents untouched by the reorder, only its slot - // number within chunkOrder moves. Recompute the cached index directly - // from each entity's (unmoved) position rather than going through - // entitySetChunk, which would look up the stale old index, fail to - // find/clear the entity's real registration, and insert a duplicate - // entry alongside it. Entities already handled by mapChunkUnload above - // (detached to CHUNK_INDEX_INVALID, or despawned to ENTITY_TYPE_NULL) - // are skipped. - for(uint8_t i = 0; i < ENTITY_COUNT; i++) { - entity_t *entity = &ENTITIES[i]; - if(entity->type == ENTITY_TYPE_NULL) continue; - if(entity->chunkIndex == CHUNK_INDEX_INVALID) continue; - - chunkpos_t cp; - worldPosToChunkPos(&entity->position, &cp); - entity->chunkIndex = mapGetChunkIndexAt(cp); - } - - errorOk(); -} - -errorret_t mapUpdate() { - // TEST ONLY - see MAP_CHUNK_LOAD_DELAY_TEST. - if(MAP.chunkLoadCooldown > 0.0f) { - MAP.chunkLoadCooldown -= TIME.delta; - if(MAP.chunkLoadCooldown <= 0.0f) { - MAP.chunkLoadCooldown = 0.0f; - mapChunkLoadNext(); - } - } - - errorOk(); -} - -errorret_t mapDispose() { - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - mapChunkUnload(&MAP.chunks[i]); - } - MAP.name[0] = '\0'; - errorOk(); -} - -void mapChunkUnload(chunk_t *chunk) { - mapChunkLoadQueueRemove(chunk); - mapChunkLoadingSlotClear(chunk); - - for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) { - if(chunk->entities[i] == 0xFF) continue; - entity_t *entity = &ENTITIES[chunk->entities[i]]; - if(!entityCanUnload(entity)) { - entitySetChunk(entity, CHUNK_INDEX_INVALID); - } else { - entity->type = ENTITY_TYPE_NULL; - } - } - - memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); - - for(uint8_t i = 0; i < CHUNK_AREA_COUNT_MAX; i++) { - if(chunk->areas[i] == 0xFF) continue; - mapAreaRemove(chunk->areas[i]); - } - memorySet(chunk->areas, 0xFF, sizeof(chunk->areas)); - - if(chunk->dcfEntry != NULL) { - chunk->dcfEntry->onLoaded = NULL; - chunk->dcfEntry->onLoadedUser = NULL; - chunk->dcfEntry->onError = NULL; - chunk->dcfEntry->onErrorUser = NULL; - assetUnlockEntry(chunk->dcfEntry); - chunk->dcfEntry = NULL; - } - - // modelEntries are borrowed pointers, not independently locked - the - // chunk asset entry (released above) is what actually holds the ref on - // each model, so nothing to unlock here, just drop our own copies. - for(uint8_t m = 0; m < chunk->meshCount; m++) { - chunk->modelEntries[m] = NULL; - } - chunk->meshCount = 0; -} - -errorret_t mapChunkLoad(chunk_t *chunk) { - if(!mapIsLoaded()) errorThrow("No map loaded"); - - mapChunkLoadQueueRemove(chunk); - mapChunkLoadingSlotClear(chunk); - - if(chunk->dcfEntry != NULL) { - chunk->dcfEntry->onLoaded = NULL; - chunk->dcfEntry->onLoadedUser = NULL; - chunk->dcfEntry->onError = NULL; - chunk->dcfEntry->onErrorUser = NULL; - assetUnlockEntry(chunk->dcfEntry); - chunk->dcfEntry = NULL; - } - - memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); - - // Normally already empty (mapChunkUnload clears these before a chunk is - // handed back for reuse), but cleared defensively here too so a reload - // never leaks a MAP_AREAS slot referenced by a stale owned area ID. - for(uint8_t i = 0; i < CHUNK_AREA_COUNT_MAX; i++) { - if(chunk->areas[i] == 0xFF) continue; - mapAreaRemove(chunk->areas[i]); - } - memorySet(chunk->areas, 0xFF, sizeof(chunk->areas)); - - chunk->meshCount = 0; - - char_t name[MAP_FILE_PATH_MAX]; - stringFormat( - name, sizeof(name), - "maps/%s/chunks/chunk_%d_%d_%d.json", - MAP.name, - (int32_t)chunk->position.x, - (int32_t)chunk->position.y, - (int32_t)chunk->position.z - ); - - if(!assetFileExists(name)) { - for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) { - // chunk->tiles[i] = (tile_t){ .shape = TILE_SHAPE_GROUND, .z = 0 }; - chunk->tiles[i] = (tile_t){ .shape = TILE_SHAPE_GROUND }; - } - errorOk(); - } - - assertTrue( - MAP.loadQueueCount < MAP_CHUNK_COUNT, - "Chunk load queue overflow" - ); - MAP.loadQueue[MAP.loadQueueCount++] = chunk; - mapChunkLoadNext(); - errorOk(); -} - -void mapChunkLoadNext() { - // TEST ONLY - see MAP_CHUNK_LOAD_DELAY_TEST. Skip starting anything new - // until the cooldown set by the last load we started has expired; - // mapUpdate() ticks it down and retries this once it hits zero. - if(MAP.chunkLoadCooldown > 0.0f) return; - - for(uint32_t slot = 0; slot < MAP_CHUNK_LOAD_CONCURRENCY; slot++) { - if(MAP.loadingChunks[slot] != NULL) continue; - if(MAP.loadQueueCount == 0) return; - - chunk_t *chunk = MAP.loadQueue[0]; - for(uint32_t i = 1; i < MAP.loadQueueCount; i++) { - MAP.loadQueue[i - 1] = MAP.loadQueue[i]; - } - MAP.loadQueueCount--; - MAP.loadingChunks[slot] = chunk; - MAP.chunkLoadCooldown = MAP_CHUNK_LOAD_DELAY_TEST; - - char_t name[MAP_FILE_PATH_MAX]; - stringFormat( - name, sizeof(name), - "maps/%s/chunks/chunk_%d_%d_%d.json", - MAP.name, - (int32_t)chunk->position.x, - (int32_t)chunk->position.y, - (int32_t)chunk->position.z - ); - - assetentry_t *entry = assetLock(name, ASSET_LOADER_TYPE_CHUNK, NULL); - assertNotNull(entry, "Failed to get chunk asset entry"); - chunk->dcfEntry = entry; - - // The entry may already be resident from an earlier load that hasn't - // been reaped yet - in that case onLoaded/onError already fired once - // and never will again, so handle the terminal state directly instead - // of waiting on a subscription that would never trigger. Both of these - // recurse back into mapChunkLoadNext once they clear this slot, so the - // outer loop just continues on to try filling the next one. - if(entry->state == ASSET_ENTRY_STATE_LOADED) { - mapChunkLoaded(entry, chunk); - continue; - } - if(entry->state == ASSET_ENTRY_STATE_ERROR) { - mapChunkLoadError(entry, chunk); - continue; - } - - assertNull(entry->onLoaded, "Entry already has an onLoaded subscriber."); - assertNull(entry->onError, "Entry already has an onError subscriber."); - entry->onLoaded = mapChunkLoaded; - entry->onLoadedUser = chunk; - entry->onError = mapChunkLoadError; - entry->onErrorUser = chunk; - } -} - -void mapChunkLoadQueueRemove(chunk_t *chunk) { - for(uint32_t i = 0; i < MAP.loadQueueCount; i++) { - if(MAP.loadQueue[i] != chunk) continue; - for(uint32_t j = i + 1; j < MAP.loadQueueCount; j++) { - MAP.loadQueue[j - 1] = MAP.loadQueue[j]; - } - MAP.loadQueueCount--; - return; - } -} - - -chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) { - if(!mapIsLoaded()) return CHUNK_INDEX_INVALID; - - chunkpos_t relPos = { - position.x - MAP.chunkPosition.x, - position.y - MAP.chunkPosition.y, - position.z - MAP.chunkPosition.z - }; - - if( - relPos.x < 0 || relPos.y < 0 || relPos.z < 0 || - relPos.x >= MAP_CHUNK_WIDTH || - relPos.y >= MAP_CHUNK_HEIGHT || - relPos.z >= MAP_CHUNK_DEPTH - ) { - return CHUNK_INDEX_INVALID; - } - - return chunkPosToIndex(&relPos); -} - -chunk_t *mapGetChunk(const chunkindex_t index) { - if(index == CHUNK_INDEX_INVALID || index >= MAP_CHUNK_COUNT) return NULL; - if(!mapIsLoaded()) return NULL; - return MAP.chunkOrder[index]; -} - -tile_t mapGetTile(const worldpos_t position) { - if(!mapIsLoaded()) return TILE_NULL; - - chunkpos_t chunkPos; - worldPosToChunkPos(&position, &chunkPos); - chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos); - if(chunkIndex == CHUNK_INDEX_INVALID) return TILE_NULL; - - chunk_t *chunk = mapGetChunk(chunkIndex); - assertNotNull(chunk, "Chunk pointer cannot be NULL"); - chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position); - tile_t tile = chunk->tiles[tileIndex]; - if(tile.z != worldPosToChunkLocalZ(&position)) return TILE_NULL; - return tile; -} - -bool_t mapGetWalkableZNear( - const worldunit_t x, - const worldunit_t y, - const worldunit_t nearZ, - worldunit_t *outZ -) { - assertNotNull(outZ, "Output Z pointer cannot be NULL"); - - const worldunit_t candidates[] = { - nearZ, (worldunit_t)(nearZ + 1), (worldunit_t)(nearZ - 1) - }; - for(uint8_t i = 0; i < 3; i++) { - const worldpos_t pos = { x, y, candidates[i] }; - if(!tileShapeIsWalkable(mapGetTile(pos).shape)) continue; - *outZ = candidates[i]; - return true; - } - - return false; -} - -entity_t * mapSpawnEntity( - const entityglobalid_t globalId, - const worldpos_t position -) { - assertTrue( - globalId > ENTITY_GLOBAL_ID_START, - "mapSpawnEntity requires a global ID greater than ENTITY_GLOBAL_ID_START" - ); - assertTrue( - globalId < ENTITY_GLOBAL_LIST_COUNT, - "Global ID is out of range for entity global init callbacks" - ); - - // Already spawned? Reuse the existing entity instead of making a - // duplicate - two entities must never share a global ID. - entity_t *existing = entityGetByGlobalId(globalId); - if(existing != NULL) return existing; - - // See if there is a callback for this entity first. - const entityglobaldef_t *def = &ENTITY_GLOBAL_LIST[globalId]; - assertNotNull(def, "No global entity definition for this ID"); - assertNotNull(def->callback, "No callback registered for this global ID"); - - // Get available entity. - uint8_t index = entityGetAvailable(); - assertTrue(index != 0xFF, "No available entity slots for mapSpawnEntity"); - - // Get the pointer and do the init. - entity_t *entity = &ENTITIES[index]; - entityInit(entity, def->type); - entity->globalId = globalId; - entityPositionSet(entity, position);// Also assigns the entity's chunk. - - // Invoke the callback to initialize the entity. - entityglobalcreate_t create = { - .entity = entity, - .position = position - }; - def->callback(&create); - return entity; -} - -void mapRebuildChunkOrder() { - memoryZero(MAP.chunkOrder, sizeof(MAP.chunkOrder)); - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - chunk_t *chunk = &MAP.chunks[i]; - const chunkpos_t rel = { - chunk->position.x - MAP.chunkPosition.x, - chunk->position.y - MAP.chunkPosition.y, - chunk->position.z - MAP.chunkPosition.z - }; - if( - rel.x < 0 || rel.x >= MAP_CHUNK_WIDTH || - rel.y < 0 || rel.y >= MAP_CHUNK_HEIGHT || - rel.z < 0 || rel.z >= MAP_CHUNK_DEPTH - ) continue; - MAP.chunkOrder[chunkPosToIndex(&rel)] = chunk; - } -} - -void mapChunkLoadError(assetentry_t *entry, void *user) { - assertNotNull(entry, "mapChunkLoadError: entry cannot be NULL"); - assertNotNull(user, "mapChunkLoadError: user cannot be NULL"); - chunk_t *chunk = (chunk_t *)user; - if(chunk->dcfEntry != entry) return; - consolePrint( - "Chunk load error: %d %d %d", - (int32_t)chunk->position.x, - (int32_t)chunk->position.y, - (int32_t)chunk->position.z - ); - entry->onLoaded = NULL; - entry->onLoadedUser = NULL; - entry->onError = NULL; - entry->onErrorUser = NULL; - assetUnlockEntry(chunk->dcfEntry); - chunk->dcfEntry = NULL; - memorySet(chunk->tiles, 0x00, sizeof(chunk->tiles)); - - mapChunkLoadingSlotClear(chunk); - mapChunkLoadNext(); -} - -void mapChunkLoaded(assetentry_t *entry, void *user) { - assertNotNull(entry, "mapChunkLoaded: entry cannot be NULL"); - assertNotNull(user, "mapChunkLoaded: user cannot be NULL"); - chunk_t *chunk = (chunk_t *)user; - if(chunk->dcfEntry != entry) return; - // consolePrint( - // "Chunk loaded: %d %d %d", - // (int32_t)chunk->position.x, - // (int32_t)chunk->position.y, - // (int32_t)chunk->position.z - // ); - uint8_t meshCount = entry->data.chunk.meshCount; - memoryCopy( - chunk->tiles, - entry->data.chunk.tiles, - sizeof(chunk->tiles) - ); - worldpos_t wp; - chunkPosToWorldPos(&chunk->position, &wp); - vec3 wpf = { - (float_t)wp.x, (float_t)wp.y, (float_t)wp.z * WORLD_LAYER_HEIGHT - }; - for(uint8_t m = 0; m < meshCount; m++) { - stringCopy( - chunk->modelNames[m], - entry->data.chunk.modelNames[m], - CHUNK_MESH_NAME_MAX - ); - glm_vec3_copy( - entry->data.chunk.meshOffsets[m], - chunk->meshOffsets[m] - ); - vec3 scaledOffset = { - chunk->meshOffsets[m][0], - chunk->meshOffsets[m][1], - chunk->meshOffsets[m][2] * WORLD_LAYER_HEIGHT - }; - vec3 pos; - glm_vec3_add(wpf, scaledOffset, pos); - glm_translate_make(chunk->meshModels[m], pos); - // Borrow the pointer rather than stealing it - the chunk asset entry - // keeps its own lock on each model (taken once while it loaded) and we - // keep the chunk asset entry itself locked (see below), so the models - // stay valid for as long as this chunk_t is using them. The entry may - // now be reused by a later mapChunkLoad for a different chunk_t once we - // eventually unlock it in mapChunkUnload, at which point its - // modelEntries must still be intact for that next reuse to copy from. - chunk->modelEntries[m] = entry->data.chunk.modelEntries[m]; - } - entry->onLoaded = NULL; - entry->onLoadedUser = NULL; - entry->onError = NULL; - entry->onErrorUser = NULL; - // Deliberately keep chunk->dcfEntry locked and set - it is what keeps the - // chunk asset entry (and therefore its model locks) alive for as long as - // this chunk_t is displaying it. Released in mapChunkUnload instead. - chunk->meshCount = meshCount; - - // Spawn entities declared by this chunk's file. Global entities are - // deduped by mapSpawnEntity itself (a persistent NPC that streams back - // in won't be duplicated); item entities have no persistent identity, so - // each reload spawns a fresh one - picking an item up and then leaving - // and re-entering its chunk will currently respawn it, since nothing - // tracks "already collected" across a chunk unload/reload yet. - for(uint8_t s = 0; s < entry->data.chunk.entitySpawnCount; s++) { - chunkentityspawn_t *spawn = &entry->data.chunk.entitySpawns[s]; - if(spawn->kind == CHUNK_ENTITY_SPAWN_KIND_GLOBAL) { - mapSpawnEntity((entityglobalid_t)spawn->globalId, spawn->position); - continue; - } - - uint8_t index = entityGetAvailable(); - assertTrue(index != 0xFF, "No available entity slots for chunk spawn"); - entity_t *itemEntity = &ENTITIES[index]; - entityInit(itemEntity, ENTITY_TYPE_ITEM); - entityItemSet( - itemEntity, (itemid_t)spawn->itemId, spawn->itemQuantity - ); - entityPositionSet(itemEntity, spawn->position); - } - - // Spawn map areas declared by this chunk's file, tracked as owned by - // this chunk so mapChunkUnload can tear them down again. - for(uint8_t s = 0; s < entry->data.chunk.areaSpawnCount; s++) { - chunkareaspawn_t *area = &entry->data.chunk.areaSpawns[s]; - uint8_t areaId = mapAreaAddGlobal( - area->min, area->max, area->callbackId, area->notify, area->trigger - ); - - uint8_t slot = 0xFF; - for(uint8_t i = 0; i < CHUNK_AREA_COUNT_MAX; i++) { - if(chunk->areas[i] != 0xFF) continue; - slot = i; - break; - } - assertTrue(slot != 0xFF, "Chunk has no free owned-area slots"); - chunk->areas[slot] = areaId; - } - - mapChunkLoadingSlotClear(chunk); - mapChunkLoadNext(); -} diff --git a/src/dusk/rpg/overworld/map.h b/src/dusk/rpg/overworld/map.h deleted file mode 100644 index 93b70057..00000000 --- a/src/dusk/rpg/overworld/map.h +++ /dev/null @@ -1,214 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "rpg/overworld/chunk.h" -#include "rpg/entity/entity.h" - -#define MAP_FILE_PATH_MAX 128 - -// Buffer size for a map's name, including the null terminator - so map -// names are at most 9 characters. -#define MAP_NAME_MAX 10 - -// Number of chunks that may be mid-load (asset locked & awaiting onLoaded/ -// onError) at the same time - everything past this waits in loadQueue. -#define MAP_CHUNK_LOAD_CONCURRENCY 1 - -// TEST ONLY - deliberately exaggerated delay (seconds) enforced between -// starting one chunk load and starting the next, to observe its effect on -// framerate. See MAP.chunkLoadCooldown / mapChunkLoadNext. -#define MAP_CHUNK_LOAD_DELAY_TEST 0.05f - -typedef struct map_s { - // Name of the currently loaded map, or an empty string if none is - // loaded (see mapIsLoaded). - char_t name[MAP_NAME_MAX]; - - chunk_t chunks[MAP_CHUNK_COUNT]; - chunk_t *chunkOrder[MAP_CHUNK_COUNT]; - chunkpos_t chunkPosition; - - chunk_t *loadQueue[MAP_CHUNK_COUNT]; - uint32_t loadQueueCount; - chunk_t *loadingChunks[MAP_CHUNK_LOAD_CONCURRENCY]; - - // TEST ONLY - seconds remaining before mapChunkLoadNext is allowed to - // start another chunk load. See MAP_CHUNK_LOAD_DELAY_TEST. - float_t chunkLoadCooldown; -} map_t; - -extern map_t MAP; - -/** - * Initializes the map subsystem. No map is loaded until mapSetMap is - * called. - * - * @return An error code. - */ -errorret_t mapInit(); - -/** - * Checks if a map is loaded. - * - * @return true if a map is loaded, false otherwise. - */ -bool_t mapIsLoaded(); - -/** - * Sets the active map by name, unloading the previously loaded map (if - * any) first, then starts loading the new map's chunks around chunk - * position (0, 0, 0). - * - * @param map The map's name, at most MAP_NAME_MAX - 1 characters. - * @return An error code. - */ -errorret_t mapSetMap(const char_t *map); - -/** - * Updates the map. - * - * @return An error code. - */ -errorret_t mapUpdate(); - -/** - * Disposes of the map. - * - * @return An error code. - */ -errorret_t mapDispose(); - -/** - * Sets the map position and updates chunks accordingly. - * - * @param newPos The new chunk position. - * @return An error code. - */ -errorret_t mapPositionSet(const chunkpos_t newPos); - -/** - * Clears chunk's mid-load slot, if it currently holds one. - * - * @param chunk The chunk whose mid-load slot to clear. - */ -void mapChunkLoadingSlotClear(chunk_t *chunk); - -/** - * Unloads a chunk. - * - * @param chunk The chunk to unload. - */ -void mapChunkUnload(chunk_t* chunk); - -/** - * Loads a chunk. Starts async loading without blocking. - * - * @param chunk The chunk to load. - * @return An error code. - */ -errorret_t mapChunkLoad(chunk_t* chunk); - -/** - * Starts loading queued chunks until MAP_CHUNK_LOAD_CONCURRENCY chunks are - * mid-load. Called after mapChunkLoad enqueues a chunk, and again after a - * mid-load chunk finishes (or is unloaded) to advance the queue. - */ -void mapChunkLoadNext(); - -/** - * Removes a chunk from the load queue if present. Used when a chunk is - * re-queued or unloaded before its turn to load has come up. - * - * @param chunk The chunk to remove from the load queue. - */ -void mapChunkLoadQueueRemove(chunk_t *chunk); - -/** - * Callback invoked when a chunk DCF asset fails to load. Fills the - * chunk tiles with TILE_SHAPE_GROUND as a fallback. - * Always invoked on the main thread. - * - * @param entry The failed assetentry_t. - * @param user The chunk_t that owns the entry. - */ -void mapChunkLoadError(assetentry_t *entry, void *user); - -/** - * Callback invoked when a chunk DCF asset finishes loading. - * Always invoked on the main thread. - * - * @param entry The loaded assetentry_t. - * @param user The chunk_t that owns the entry. - */ -void mapChunkLoaded(assetentry_t *entry, void *user); - -/** - * Rebuilds chunkOrder from the loaded chunks that fall within the - * current render window. Called whenever chunkPosition changes. - */ -void mapRebuildChunkOrder(); - -/** - * Gets the index of a chunk, within the world, at the given position. - * - * @param position The chunk position. - * @return The index of the chunk, or CHUNK_INDEX_INVALID if out of - * bounds. - */ -chunkindex_t mapGetChunkIndexAt(const chunkpos_t position); - -/** - * Gets a chunk by its index. - * - * @param chunkIndex The index of the chunk. - * @return A pointer to the chunk, or NULL if chunkIndex is - * CHUNK_INDEX_INVALID, out of range, or no map is currently loaded. - */ -chunk_t * mapGetChunk(const chunkindex_t chunkIndex); - -/** - * Gets the tile at the given world position. - * - * @param position The world position. - * @return The tile at that position, or TILE_NULL if the chunk is unloaded. - */ -tile_t mapGetTile(const worldpos_t position); - -/** - * Finds the closest walkable Z layer to nearZ at the given X/Y. Checks - * nearZ first, then nearZ + 1, then nearZ - 1, since ramps only ever - * change height by one Z layer between adjacent tiles. - * - * @param x The world X coordinate to check. - * @param y The world Y coordinate to check. - * @param nearZ The reference Z layer to search outward from. - * @param outZ Output pointer, set to the resolved Z layer on success. - * @return true if a walkable tile was found, false otherwise. - */ -bool_t mapGetWalkableZNear( - const worldunit_t x, - const worldunit_t y, - const worldunit_t nearZ, - worldunit_t *outZ -); - -/** - * Spawns a global (persistent) entity into the world at the given position. - * Asserts globalId is greater than ENTITY_GLOBAL_ID_START - use entityInit - * directly for ephemeral, non-global entities. - * - * @param globalId The global entity ID to assign, must be greater than - * ENTITY_GLOBAL_ID_START. - * @param position The world position to spawn the entity at. - * @return Pointer to the spawned entity. - */ -entity_t * mapSpawnEntity( - const entityglobalid_t globalId, - const worldpos_t position -); \ No newline at end of file diff --git a/src/dusk/rpg/overworld/maparea.c b/src/dusk/rpg/overworld/maparea.c deleted file mode 100644 index 6c1a798b..00000000 --- a/src/dusk/rpg/overworld/maparea.c +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "maparea.h" -#include "assert/assert.h" -#include "util/math.h" -#include "util/memory.h" -#include "rpg/overworld/map.h" -#include "rpg/overworld/global/mapareaglobal.h" - -maparea_t MAP_AREAS[MAP_AREA_COUNT_MAX]; - -void mapAreaInit( - maparea_t *area, - const worldpos_t min, - const worldpos_t max, - const mapareacallback_t callback, - const uint8_t notify, - const uint8_t trigger -) { - assertNotNull(area, "Map area pointer cannot be NULL"); - assertNotNull(callback, "Map area callback cannot be NULL"); - - area->min.x = mathMin(min.x, max.x); - area->min.y = mathMin(min.y, max.y); - area->min.z = mathMin(min.z, max.z); - - area->max.x = mathMax(min.x, max.x); - area->max.y = mathMax(min.y, max.y); - area->max.z = mathMax(min.z, max.z); - - area->callback = callback; - area->notify = notify; - area->trigger = trigger; - area->triggerCount = 0; - - memorySet(area->entities, 0, sizeof(area->entities)); -} - -bool_t mapAreaIsInside(const maparea_t *area, const worldpos_t position) { - assertNotNull(area, "Map area pointer cannot be NULL"); - - return ( - position.x >= area->min.x && position.x <= area->max.x && - position.y >= area->min.y && position.y <= area->max.y && - position.z >= area->min.z && position.z <= area->max.z - ); -} - -bool_t mapAreaIsChunkOverlappingOrInside( - const maparea_t *area, - const chunk_t *chunk -) { - assertNotNull(area, "Map area pointer cannot be NULL"); - assertNotNull(chunk, "Chunk pointer cannot be NULL"); - - worldpos_t chunkMin, chunkMax; - chunkPosToWorldPos(&chunk->position, &chunkMin); - chunkMax.x = chunkMin.x + CHUNK_WIDTH - 1; - chunkMax.y = chunkMin.y + CHUNK_HEIGHT - 1; - chunkMax.z = chunkMin.z + CHUNK_DEPTH - 1; - - return ( - chunkMin.x <= area->max.x && area->min.x <= chunkMax.x && - chunkMin.y <= area->max.y && area->min.y <= chunkMax.y && - chunkMin.z <= area->max.z && area->min.z <= chunkMax.z - ); -} - -bool_t mapAreaCanUnload(const maparea_t *area) { - assertNotNull(area, "Map area pointer cannot be NULL"); - - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - if(mapAreaIsChunkOverlappingOrInside(area, &MAP.chunks[i])) return false; - } - - return true; -} - -bool_t mapAreaShouldNotify(const maparea_t *area, const entity_t *entity) { - assertNotNull(area, "Map area pointer cannot be NULL"); - assertNotNull(entity, "Entity pointer cannot be NULL"); - - switch(entity->type) { - case ENTITY_TYPE_PLAYER: - return (area->notify & MAP_AREA_NOTIFY_PLAYER) != 0; - - case ENTITY_TYPE_NPC: - return (area->notify & MAP_AREA_NOTIFY_NPC) != 0; - - default: - return false; - } -} - -uint8_t mapAreaAdd( - const worldpos_t min, - const worldpos_t max, - const mapareacallback_t callback, - const uint8_t notify, - const uint8_t trigger -) { - for(uint8_t i = 0; i < MAP_AREA_COUNT_MAX; i++) { - if(MAP_AREAS[i].callback != NULL) continue; - mapAreaInit(&MAP_AREAS[i], min, max, callback, notify, trigger); - return i; - } - - assertUnreachable("No available map area slots"); - return 0xFF; -} - -void mapAreaRemove(const uint8_t id) { - assertTrue(id < MAP_AREA_COUNT_MAX, "Map area ID is out of range"); - MAP_AREAS[id].callback = NULL; -} - -void mapAreaCheckEntity(entity_t *entity) { - assertNotNull(entity, "Entity pointer cannot be NULL"); - - for(uint8_t i = 0; i < MAP_AREA_COUNT_MAX; i++) { - maparea_t *area = &MAP_AREAS[i]; - if(area->callback == NULL) continue; - if(!mapAreaShouldNotify(area, entity)) continue; - - bool_t wasInside = area->entities[entity->id] != 0; - bool_t isInside = mapAreaIsInside(area, entity->position); - uint8_t trigger = 0; - - if(isInside && !wasInside) { - area->entities[entity->id] = 1; - trigger = MAP_TRIGGER_ENTER; - } else if(isInside && wasInside) { - trigger = MAP_TRIGGER_STEP; - } else if(!isInside && wasInside) { - area->entities[entity->id] = 0; - trigger = MAP_TRIGGER_EXIT; - } - - if(trigger == 0 || !(area->trigger & trigger)) continue; - area->triggerCount++; - area->callback(entity, trigger); - } -} - -void mapAreaNoopCallback(entity_t *entity, const uint8_t trigger) { -} - -uint8_t mapAreaAddGlobal( - const worldpos_t min, - const worldpos_t max, - const uint16_t callbackId, - const uint8_t notify, - const uint8_t trigger -) { - assertTrue(callbackId > 0, "Map area callback ID 0 is reserved"); - assertTrue( - callbackId < MAP_AREA_CALLBACK_LIST_COUNT, - "Map area callback ID is out of range" - ); - return mapAreaAdd( - min, max, MAP_AREA_CALLBACK_LIST[callbackId], notify, trigger - ); -} diff --git a/src/dusk/rpg/overworld/maparea.h b/src/dusk/rpg/overworld/maparea.h deleted file mode 100644 index 8ffb3de9..00000000 --- a/src/dusk/rpg/overworld/maparea.h +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "worldpos.h" -#include "rpg/entity/entity.h" - -typedef struct chunk_s chunk_t; -typedef struct maparea_s maparea_t; - -#define MAP_AREA_COUNT_MAX 32 - -#define MAP_AREA_NOTIFY_PLAYER (1 << 0) -#define MAP_AREA_NOTIFY_NPC (1 << 1) -#define MAP_AREA_NOTIFY_ALL (MAP_AREA_NOTIFY_PLAYER | MAP_AREA_NOTIFY_NPC) - -#define MAP_TRIGGER_STEP (1 << 0) -#define MAP_TRIGGER_ENTER (1 << 1) -#define MAP_TRIGGER_EXIT (1 << 2) -#define MAP_TRIGGER_ALL \ - (MAP_TRIGGER_STEP | MAP_TRIGGER_ENTER | MAP_TRIGGER_EXIT) - -/** - * Callback invoked for a map area. - * - * @param entity Pointer to the entity associated with the callback. - * @param trigger Which MAP_TRIGGER_* condition invoked the callback. - */ -typedef void (*mapareacallback_t)(entity_t *entity, const uint8_t trigger); - -typedef struct maparea_s { - worldpos_t min; - worldpos_t max; - mapareacallback_t callback; - uint8_t notify; - uint8_t trigger; - uint8_t entities[ENTITY_COUNT]; - - // Incremented every time this area's callback is invoked. Lets other - // systems (e.g. cutscenes) detect "has this area fired since I last - // checked" without needing to be the callback themselves. - uint32_t triggerCount; -} maparea_t; - -extern maparea_t MAP_AREAS[MAP_AREA_COUNT_MAX]; - -/** - * Initializes a map area with the given bounds, callback, notify flags - * and trigger flags. - * - * @param area Pointer to the map area to initialize. - * @param min The minimum world position of the area. - * @param max The maximum world position of the area. - * @param callback The callback to invoke for this area. Must not be NULL. - * @param notify Bitwise MAP_AREA_NOTIFY_* flags for which entity types - * should trigger the callback. - * @param trigger Bitwise MAP_TRIGGER_* flags for which conditions should - * invoke the callback. - */ -void mapAreaInit( - maparea_t *area, - const worldpos_t min, - const worldpos_t max, - const mapareacallback_t callback, - const uint8_t notify, - const uint8_t trigger -); - -/** - * Checks whether a world position falls within a map area's bounds - * (inclusive on all axes). - * - * @param area Pointer to the map area to check. - * @param position The world position to check. - * @returns true if the position is inside the area. - */ -bool_t mapAreaIsInside(const maparea_t *area, const worldpos_t position); - -/** - * Checks whether a chunk's full footprint overlaps a map area's bounds, - * rather than just testing the chunk's origin corner. - * - * @param area Pointer to the map area to check. - * @param chunk Pointer to the chunk to check. - * @returns true if the chunk overlaps the area. - */ -bool_t mapAreaIsChunkOverlappingOrInside( - const maparea_t *area, - const chunk_t *chunk -); - -/** - * Checks whether a map area is safe to unload, i.e. no entity currently - * occupies a position within its bounds. - * - * @param area Pointer to the map area to check. - * @returns true if no entity is inside the area. - */ -bool_t mapAreaCanUnload(const maparea_t *area); - -/** - * Checks whether an entity's type is included in a map area's notify flags. - * - * @param area Pointer to the map area to check. - * @param entity Pointer to the entity to check. - * @returns true if the entity's type should trigger this area's callback. - */ -bool_t mapAreaShouldNotify(const maparea_t *area, const entity_t *entity); - -/** - * Adds a map area to the global MAP_AREAS list, in the first free slot. - * A slot is considered free if its callback is NULL. - * - * @param min The minimum world position of the area. - * @param max The maximum world position of the area. - * @param callback The callback to invoke for this area. Must not be NULL. - * @param notify Bitwise MAP_AREA_NOTIFY_* flags for which entity types - * should trigger the callback. - * @param trigger Bitwise MAP_TRIGGER_* flags for which conditions should - * invoke the callback. - * @returns The ID of the newly added map area. - */ -uint8_t mapAreaAdd( - const worldpos_t min, - const worldpos_t max, - const mapareacallback_t callback, - const uint8_t notify, - const uint8_t trigger -); - -/** - * Removes a map area from the global MAP_AREAS list, freeing its slot by - * clearing its callback to NULL. - * - * @param id The ID of the map area to remove. - */ -void mapAreaRemove(const uint8_t id); - -/** - * Checks every active map area against an entity's current position, - * for areas whose notify flags include the entity's type. Invokes the - * area's callback with MAP_TRIGGER_ENTER the frame the entity first - * becomes inside, MAP_TRIGGER_STEP on every subsequent frame it remains - * inside, and MAP_TRIGGER_EXIT the frame it leaves. - * - * @param entity Pointer to the entity to check. - */ -void mapAreaCheckEntity(entity_t *entity); - -/** - * A map area callback that does nothing. Useful for areas whose only - * purpose is to be waited on (see CUTSCENE_MAP_AREA_WAIT), since the - * wait is driven by triggerCount rather than callback logic. - * - * @param entity Pointer to the entity associated with the callback. - * @param trigger Which MAP_TRIGGER_* condition invoked the callback. - */ -void mapAreaNoopCallback(entity_t *entity, const uint8_t trigger); - -/** - * Adds a map area using a compiled-in callback referenced by ID (see - * MAP_AREA_CALLBACK_LIST in rpg/overworld/global/mapareagloballist.h), - * rather than a direct function pointer. This is what lets chunk file - * data - which can only reference compiled code by a small integer ID, - * not a function pointer - declare map areas. - * - * @param min The minimum world position of the area. - * @param max The maximum world position of the area. - * @param callbackId Index into MAP_AREA_CALLBACK_LIST. Must be greater - * than 0 (0 is reserved) and within range. - * @param notify Bitwise MAP_AREA_NOTIFY_* flags for which entity types - * should trigger the callback. - * @param trigger Bitwise MAP_TRIGGER_* flags for which conditions should - * invoke the callback. - * @returns The ID of the newly added map area. - */ -uint8_t mapAreaAddGlobal( - const worldpos_t min, - const worldpos_t max, - const uint16_t callbackId, - const uint8_t notify, - const uint8_t trigger -); \ No newline at end of file diff --git a/src/dusk/rpg/overworld/tile.c b/src/dusk/rpg/overworld/tile.c deleted file mode 100644 index 34e25cda..00000000 --- a/src/dusk/rpg/overworld/tile.c +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "tile.h" diff --git a/src/dusk/rpg/overworld/tile.h b/src/dusk/rpg/overworld/tile.h deleted file mode 100644 index 82f30850..00000000 --- a/src/dusk/rpg/overworld/tile.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "tileshape.h" - -typedef struct { - tileshape_t shape; - - // Local Z layer (0..CHUNK_DEPTH-1) this tile occupies within its chunk's - // depth slab. Used for entity navigation since chunk_t only stores one - // tile per X/Y column rather than one per X/Y/Z. - uint8_t z; -} tile_t; - -#define TILE_NULL ((tile_t){ .shape = TILE_SHAPE_NULL, .z = 0 }) \ No newline at end of file diff --git a/src/dusk/rpg/overworld/tileshape.c b/src/dusk/rpg/overworld/tileshape.c deleted file mode 100644 index 13124c6f..00000000 --- a/src/dusk/rpg/overworld/tileshape.c +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "tileshape.h" - -bool_t tileShapeIsWalkable(const tileshape_t shape) { - switch(shape) { - case TILE_SHAPE_NULL: - return false; - - default: - return true; - } -} - -bool_t tileShapeIsRamp(const tileshape_t shape) { - switch(shape) { - case TILE_SHAPE_RAMP_NORTH: - case TILE_SHAPE_RAMP_SOUTH: - case TILE_SHAPE_RAMP_EAST: - case TILE_SHAPE_RAMP_WEST: - case TILE_SHAPE_RAMP_NORTHEAST: - case TILE_SHAPE_RAMP_NORTHWEST: - case TILE_SHAPE_RAMP_SOUTHEAST: - case TILE_SHAPE_RAMP_SOUTHWEST: - case TILE_SHAPE_RAMP_NORTHEAST_INNER: - case TILE_SHAPE_RAMP_NORTHWEST_INNER: - case TILE_SHAPE_RAMP_SOUTHEAST_INNER: - case TILE_SHAPE_RAMP_SOUTHWEST_INNER: - return true; - - default: - return false; - } -} \ No newline at end of file diff --git a/src/dusk/rpg/overworld/tileshape.h b/src/dusk/rpg/overworld/tileshape.h deleted file mode 100644 index 1187ac70..00000000 --- a/src/dusk/rpg/overworld/tileshape.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/entity/entitydir.h" - -typedef enum { - TILE_SHAPE_NULL = 0, - - TILE_SHAPE_GROUND = 1, - TILE_SHAPE_RAMP_NORTH = 2, - TILE_SHAPE_RAMP_EAST = 3, - TILE_SHAPE_RAMP_SOUTH = 4, - TILE_SHAPE_RAMP_WEST = 5, - TILE_SHAPE_RAMP_NORTHEAST = 6, - TILE_SHAPE_RAMP_NORTHWEST = 7, - TILE_SHAPE_RAMP_SOUTHEAST = 8, - TILE_SHAPE_RAMP_SOUTHWEST = 9, - TILE_SHAPE_RAMP_NORTHEAST_INNER = 10, - TILE_SHAPE_RAMP_NORTHWEST_INNER = 11, - TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12, - TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13, - - TILE_SHAPE_COUNT -} tileshape_t; - -/** - * Returns whether or not the given tile shape is a ramp. - * - * @param shape The tile shape to check. - * @return bool_t True if ramp, false if not. - */ -bool_t tileShapeIsRamp(const tileshape_t shape); - -/** - * Returns whether or not the given tile shape is walkable. - * - * @param shape The tile shape to check. - * @return bool_t True if walkable, false if not. - */ -bool_t tileShapeIsWalkable(const tileshape_t shape); \ No newline at end of file diff --git a/src/dusk/rpg/overworld/worldpos.c b/src/dusk/rpg/overworld/worldpos.c deleted file mode 100644 index 6112bf9a..00000000 --- a/src/dusk/rpg/overworld/worldpos.c +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "worldpos.h" -#include "assert/assert.h" -#include "util/endian.h" -#include "util/memory.h" - -bool_t worldPosIsEqual(const worldpos_t a, const worldpos_t b) { - return a.x == b.x && a.y == b.y && a.z == b.z; -} - -void chunkPosToWorldPos(const chunkpos_t* chunkPos, worldpos_t* out) { - assertNotNull(chunkPos, "Chunk position pointer cannot be NULL"); - assertNotNull(out, "Output world position pointer cannot be NULL"); - - out->x = (worldunit_t)(chunkPos->x * CHUNK_WIDTH); - out->y = (worldunit_t)(chunkPos->y * CHUNK_HEIGHT); - out->z = (worldunit_t)(chunkPos->z * CHUNK_DEPTH); -} - -void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out) { - assertNotNull(worldPos, "World position pointer cannot be NULL"); - assertNotNull(out, "Output chunk position pointer cannot be NULL"); - - if(worldPos->x < 0) { - out->x = (chunkunit_t)((worldPos->x - (CHUNK_WIDTH - 1)) / CHUNK_WIDTH); - } else { - out->x = (chunkunit_t)(worldPos->x / CHUNK_WIDTH); - } - - if(worldPos->y < 0) { - out->y = (chunkunit_t)((worldPos->y - (CHUNK_HEIGHT - 1)) / CHUNK_HEIGHT); - } else { - out->y = (chunkunit_t)(worldPos->y / CHUNK_HEIGHT); - } - - if(worldPos->z < 0) { - out->z = (chunkunit_t)((worldPos->z - (CHUNK_DEPTH - 1)) / CHUNK_DEPTH); - } else { - out->z = (chunkunit_t)(worldPos->z / CHUNK_DEPTH); - } -} - -chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) { - assertNotNull(worldPos, "World position pointer cannot be NULL"); - - uint8_t localX, localY; - if(worldPos->x < 0) { - localX = (uint8_t)( - (CHUNK_WIDTH - 1) - ((-worldPos->x - 1) % CHUNK_WIDTH) - ); - } else { - localX = (uint8_t)(worldPos->x % CHUNK_WIDTH); - } - - if(worldPos->y < 0) { - localY = (uint8_t)( - (CHUNK_HEIGHT - 1) - ((-worldPos->y - 1) % CHUNK_HEIGHT) - ); - } else { - localY = (uint8_t)(worldPos->y % CHUNK_HEIGHT); - } - - chunktileindex_t chunkTileIndex = (chunktileindex_t)( - (localY * CHUNK_WIDTH) + localX - ); - assertTrue( - chunkTileIndex < CHUNK_TILE_COUNT, - "Calculated chunk tile index is out of bounds" - ); - return chunkTileIndex; -} - -uint8_t worldPosToChunkLocalZ(const worldpos_t* worldPos) { - assertNotNull(worldPos, "World position pointer cannot be NULL"); - - if(worldPos->z < 0) { - return (uint8_t)( - (CHUNK_DEPTH - 1) - ((-worldPos->z - 1) % CHUNK_DEPTH) - ); - } - return (uint8_t)(worldPos->z % CHUNK_DEPTH); -} - -chunkindex_t chunkPosToIndex(const chunkpos_t* pos) { - assertNotNull(pos, "Chunk position pointer cannot be NULL"); - - chunkindex_t chunkIndex = (chunkindex_t)( - (pos->z * MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT) + - (pos->y * MAP_CHUNK_WIDTH) + - pos->x - ); - - return chunkIndex; -} - -worldunit_t worldUnitReadLE(const uint8_t *data, size_t *offset) { - assertNotNull(data, "Data pointer cannot be NULL"); - assertNotNull(offset, "Offset pointer cannot be NULL"); - - int16_t value; - memoryCopy(&value, data + *offset, sizeof(int16_t)); - *offset += sizeof(int16_t); - return (worldunit_t)endianLittleToHost16((uint16_t)value); -} - -worldpos_t worldPosReadLE(const uint8_t *data, size_t *offset) { - assertNotNull(data, "Data pointer cannot be NULL"); - assertNotNull(offset, "Offset pointer cannot be NULL"); - - worldpos_t pos; - pos.x = worldUnitReadLE(data, offset); - pos.y = worldUnitReadLE(data, offset); - pos.z = worldUnitReadLE(data, offset); - return pos; -} \ No newline at end of file diff --git a/src/dusk/rpg/overworld/worldpos.h b/src/dusk/rpg/overworld/worldpos.h deleted file mode 100644 index 3169839c..00000000 --- a/src/dusk/rpg/overworld/worldpos.h +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "display/display.h" -#include "util/math.h" - -#define TILE_SIZE_PIXELS 24 - -// World-space Z distance of one Z-layer/story, in render/world-float -// space. A ramp rises this much over a horizontal run of 1 tile-width; -// chosen as 1/sqrt(2) so the ramp's slope, derived from a 45-45-90 -// triangle with hypotenuse 1, matches the length of a flat tile edge. -#define WORLD_LAYER_HEIGHT 0.70710678f - -#define CHUNK_WIDTH 16 -#define CHUNK_HEIGHT 16 -#define CHUNK_DEPTH 8 - -// Chunks store one tile per X/Y column, not one per X/Y/Z - each tile -// records its own local Z layer (see tile_t) instead. -#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT) - -#define MAP_CHUNK_WIDTH 3 -#define MAP_CHUNK_HEIGHT 3 -#define MAP_CHUNK_DEPTH 4 -#define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH) - -#define ENTITY_COUNT 32 - -typedef int16_t worldunit_t; -typedef int16_t chunkunit_t; -typedef int16_t chunkindex_t; -typedef uint32_t chunktileindex_t; - -// Sentinel chunkindex_t value meaning "no chunk" - returned by -// mapGetChunkIndexAt when a position falls outside every loaded chunk, -// and used throughout as the "not assigned to any chunk" state. -#define CHUNK_INDEX_INVALID ((chunkindex_t)-1) - -typedef int32_t worldunits_t; -typedef int32_t chunkunits_t; - -typedef struct worldpos2d_s { - worldunit_t x, y; -} worldpos2d_t; - -typedef struct worldpos_s { - worldunit_t x, y, z; -} worldpos_t; - -typedef struct chunkpos_t { - chunkunit_t x, y, z; -} chunkpos_t; - -/** - * Compares two world positions for equality. - * - * @param a The first world position. - * @param b The second world position. - * @return true if equal, false otherwise. - */ -bool_t worldPosIsEqual(const worldpos_t a, const worldpos_t b); - -/** - * Converts a world position to a chunk position. - * - * @param worldPos The world position. - * @param out The output chunk position. - */ -void chunkPosToWorldPos(const chunkpos_t* chunkPos, worldpos_t* out); - -/** - * Converts a chunk position to a world position. - * - * @param worldPos The world position. - * @param out The output chunk position. - */ -void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out); - -/** - * Converts a position in world-space to an index inside a chunk that the tile - * resides in. - * - * @param worldPos The world position. - * @return The tile index within the chunk. - */ -chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos); - -/** - * Converts a world-space Z coordinate to the local Z layer (0..CHUNK_DEPTH-1) - * within the chunk that owns it, for comparison against tile_t.z. - * - * @param worldPos The world position. - * @return The local Z layer within the owning chunk. - */ -uint8_t worldPosToChunkLocalZ(const worldpos_t* worldPos); - -/** - * Converts a chunk position to a world position. - * - * @param worldPos The world position. - * @param out The output chunk position. - */ -chunkindex_t chunkPosToIndex(const chunkpos_t* pos); - -/** - * Reads a little-endian worldunit_t from a potentially-unaligned offset - * into a binary asset buffer, advancing *offset past it. Shared by the - * chunk and cutscene asset loaders' binary formats, both of which encode - * world positions the same way. - * - * @param data The buffer to read from. - * @param offset In/out cursor into data, advanced past the value read. - * @return The decoded worldunit_t. - */ -worldunit_t worldUnitReadLE(const uint8_t *data, size_t *offset); - -/** - * Reads a little-endian worldpos_t (x, then y, then z) from a - * potentially-unaligned offset into a binary asset buffer, advancing - * *offset past it. - * - * @param data The buffer to read from. - * @param offset In/out cursor into data, advanced past the value read. - * @return The decoded worldpos_t. - */ -worldpos_t worldPosReadLE(const uint8_t *data, size_t *offset); \ No newline at end of file diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c deleted file mode 100644 index 291f35cf..00000000 --- a/src/dusk/rpg/rpg.c +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpg.h" -#include "entity/entity.h" -#include "rpg/overworld/map.h" -#include "rpg/overworld/maparea.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "rpg/item/item.h" -#include "rpg/item/backpack.h" -#include "rpg/battle/party.h" -#include "rpg/battle/move/battlemove.h" -#include "save/save.h" -#include "ui/rpg/textbox/uitextboxminilist.h" -#include "time/time.h" -#include "rpgcamera.h" -#include "util/memory.h" -#include "assert/assert.h" -#include "error/error.h" -#include "scene/scene.h" -#include "ui/rpg/uiemoji.h" -#include "rpg/story/story.h" - -errorret_t rpgInit(void) { - memoryZero(ENTITIES, sizeof(ENTITIES)); - memoryZero(MAP_AREAS, sizeof(MAP_AREAS)); - - errorChain(storyInit()); - errorChain(itemInit()); - errorChain(battleMoveInit()); - backpackInit(); - cutsceneSystemInit(); - errorChain(mapInit()); - rpgCameraInit(); - - // The player is the one entity that isn't sourced from map/chunk data - - // every other entity (NPCs, items) and map area comes from the loaded - // chunks' own spawn data (see rpg/overworld/map.c mapChunkLoaded). - uint8_t entIndex = entityGetAvailable(); - assertTrue(entIndex != 0xFF, "No available entity slots!."); - entity_t *ent = &ENTITIES[entIndex]; - entityInit(ent, ENTITY_TYPE_PLAYER); - entityPositionSet(ent, (worldpos_t){ 10, 2, 0 });// Also assigns the chunk. - RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY; - RPG_CAMERA.followEntity.followEntityId = ent->id; - - // Starting inventory. - backpackAdd(itemGetIdByName("potion"), 5); - backpackAdd(itemGetIdByName("potato"), 3); - backpackAdd(itemGetIdByName("apple"), 8); - - - // All Good! - errorOk(); -} - -errorret_t rpgUpdate(void) { - #ifdef DUSK_TIME_DYNAMIC - if(TIME.dynamicUpdate) { - errorOk(); - } - #endif - - // TODO: Do not update if the scene is not the map scene? - errorChain(mapUpdate()); - - // Update overworld ents - only while actually in the overworld. Entities - // (the player among them) keep existing across scene changes, but their - // input/movement/animation logic doesn't make sense to run mid-battle or - // before the initial scene has handed off to the overworld. - if(SCENE.current == SCENE_TYPE_OVERWORLD) { - entity_t *ent = &ENTITIES[0]; - do { - if(ent->type == ENTITY_TYPE_NULL) continue; - entityUpdate(ent); - } while(++ent < &ENTITIES[ENTITY_COUNT]); - } - - cutsceneSystemUpdate(); - errorChain(rpgCameraUpdate()); - errorOk(); -} - -errorret_t rpgDispose(void) { - cutsceneSystemDispose(); - errorChain(mapDispose()); - - errorOk(); -} \ No newline at end of file diff --git a/src/dusk/rpg/rpg.h b/src/dusk/rpg/rpg.h deleted file mode 100644 index 9396c326..00000000 --- a/src/dusk/rpg/rpg.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef struct { - int32_t nothing; -} rpg_t; - -/** - * Initialize the RPG subsystem. - * - * @return An error code and state. - */ -errorret_t rpgInit(void); - -/** - * Update the RPG subsystem. - * - * @return An error code. - */ -errorret_t rpgUpdate(void); - -/** - * Dispose of the RPG subsystem. - * - * @return An error code. - */ -errorret_t rpgDispose(void); \ No newline at end of file diff --git a/src/dusk/rpg/rpgcamera.c b/src/dusk/rpg/rpgcamera.c deleted file mode 100644 index d3742d88..00000000 --- a/src/dusk/rpg/rpgcamera.c +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "rpgcamera.h" -#include "util/memory.h" -#include "util/random.h" -#include "rpg/entity/entity.h" -#include "rpg/overworld/map.h" -#include "assert/assert.h" -#include "time/time.h" - -#include "display/screen/screen.h" - -static const float_t RPG_CAMERA_SHAKE_AMOUNTS[] = { - 0.0f, 0.5f, 1.0f, 2.0f, 3.0f -}; - -rpgcamera_t RPG_CAMERA; - -void rpgCameraInit(void) { - memoryZero(&RPG_CAMERA, sizeof(rpgcamera_t)); - RPG_CAMERA.projectionDirty = true; -} - -void rpgCameraShake(uint8_t amount, float_t duration) { - assertTrue(amount <= 4, "Camera shake amount must be between 0 and 4"); - RPG_CAMERA.shakeAmount = RPG_CAMERA_SHAKE_AMOUNTS[amount]; - RPG_CAMERA.shakeDuration = duration; - RPG_CAMERA.shakeTime = 0.0f; -} - -void rpgCameraGetPosition(vec3 out) { - switch(RPG_CAMERA.mode) { - case RPG_CAMERA_MODE_FREE: - glm_vec3_copy(RPG_CAMERA.free, out); - return; - - case RPG_CAMERA_MODE_FOLLOW_ENTITY: { - entity_t *entity = &ENTITIES[RPG_CAMERA.followEntity.followEntityId]; - if(entity->type == ENTITY_TYPE_NULL) { - glm_vec3_zero(out); - return; - } - glm_vec3_copy(entity->renderPosition, out); - return; - } - - default: - assertUnreachable("Invalid RPG camera mode"); - } -} - -void rpgCameraUpdateProjection(void) { - #ifdef DUSK_DISPLAY_WIDTH - if(!RPG_CAMERA.projectionDirty) return; - RPG_CAMERA.projectionDirty = false; - #endif - - glm_perspective( - glm_rad(RPG_CAMERA_FOV), - SCREEN.aspect, - 0.1f, - 100.0f, - RPG_CAMERA.projection - ); -} - -void rpgCameraUpdateEye(void) { - float_t fov = glm_rad(RPG_CAMERA_FOV); - float_t pixelsPerUnit = TILE_SIZE_PIXELS; - float_t worldH = (float_t)(SCREEN.height / SCREEN.scale3d) / pixelsPerUnit; - float_t z = (worldH * 0.5f) / tanf(fov * 0.5f); - float_t offset = -24.0f * (worldH / TILE_SIZE_PIXELS); - - vec3 target; - rpgCameraGetPosition(target); - glm_vec3_add(target, (vec3){ 0.5f, 0.5f, 0.5f }, target); - - if(RPG_CAMERA.shakeTime < RPG_CAMERA.shakeDuration) { - float_t t = 1.0f - (RPG_CAMERA.shakeTime / RPG_CAMERA.shakeDuration); - float_t magnitude = RPG_CAMERA.shakeAmount * t; - target[0] += randomFloat(-magnitude, magnitude); - target[2] += randomFloat(-magnitude, magnitude); - } - - glm_lookat( - (vec3){ target[0], target[1] + offset, target[2] + z }, - target, - (vec3){ 0, 1, 0 }, // up - RPG_CAMERA.eye - ); -} - -void rpgCameraToScreen(vec3 worldPos, vec2 out) { - mat4 viewProj; - glm_mat4_mul(RPG_CAMERA.projection, RPG_CAMERA.eye, viewProj); - - vec4 viewport = { - 0.0f, 0.0f, (float_t)SCREEN.width, (float_t)SCREEN.height - }; - vec3 window; - glm_project(worldPos, viewProj, viewport, window); - - out[0] = window[0]; - out[1] = (float_t)SCREEN.height - window[1]; -} - -errorret_t rpgCameraUpdate(void) { - if(RPG_CAMERA.shakeTime < RPG_CAMERA.shakeDuration) { - RPG_CAMERA.shakeTime += TIME.delta; - } - - if(!mapIsLoaded()) errorOk(); - - vec3 pos; - rpgCameraGetPosition(pos); - - chunkpos_t chunkPos = { - .x = (chunkunit_t)floorf(pos[0] / CHUNK_WIDTH), - .y = (chunkunit_t)floorf(pos[1] / CHUNK_HEIGHT), - .z = (chunkunit_t)floorf(pos[2] / WORLD_LAYER_HEIGHT / CHUNK_DEPTH) - }; - - errorChain(mapPositionSet((chunkpos_t){ - .x = chunkPos.x - (MAP_CHUNK_WIDTH / 2), - .y = chunkPos.y - (MAP_CHUNK_HEIGHT / 2), - .z = chunkPos.z - (MAP_CHUNK_DEPTH / 2) - })); - errorOk(); -} \ No newline at end of file diff --git a/src/dusk/rpg/rpgcamera.h b/src/dusk/rpg/rpgcamera.h deleted file mode 100644 index 12742ff7..00000000 --- a/src/dusk/rpg/rpgcamera.h +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "rpg/overworld/worldpos.h" -#include "error/error.h" - -#define RPG_CAMERA_FOV 35.0f - -typedef enum { - RPG_CAMERA_MODE_FREE, - RPG_CAMERA_MODE_FOLLOW_ENTITY, -} rpgcameramode_t; - -typedef struct { - rpgcameramode_t mode; - - union { - vec3 free; - - struct { - uint8_t followEntityId; - } followEntity; - }; - - mat4 eye; - mat4 projection; - bool_t projectionDirty; - - float_t shakeAmount; - float_t shakeDuration; - float_t shakeTime; -} rpgcamera_t; - -extern rpgcamera_t RPG_CAMERA; - -/** - * Initializes the RPG camera. - */ -void rpgCameraInit(void); - -/** - * Gets the RPG camera's position. - * - * @param out Output vec3 filled with the camera world position. - */ -void rpgCameraGetPosition(vec3 out); - -/** - * Updates the RPG camera. - * - * @return An error code. - */ -errorret_t rpgCameraUpdate(void); - -/** - * Recomputes the camera projection matrix and stores it in - * RPG_CAMERA.projection. On platforms with a fixed display size the - * matrix is computed once and cached; on dynamic-display platforms it - * is recomputed every call. - */ -void rpgCameraUpdateProjection(void); - -/** - * Recomputes the camera eye/view matrix from the camera's current mode - * and position, and stores it in RPG_CAMERA.eye. Unlike the projection - * matrix this is never cached, since the camera position can change - * every frame. - */ -void rpgCameraUpdateEye(void); - -/** - * Shakes the RPG camera, randomly offsetting its position by a - * decreasing amount over the given duration. - * - * @param amount Shake strength from 0 (no shake) to 4 (three tiles). - * 1 is half a tile, 2 is a full tile, 3 is two tiles, and 4 is three - * tiles. - * @param duration How long the shake lasts, in seconds. - */ -void rpgCameraShake(uint8_t amount, float_t duration); - -/** - * Converts a world-space position to screen-space pixel coordinates, - * using the camera's current eye and projection matrices. - * - * @param worldPos The world-space position to convert. - * @param out Output vec2 filled with the screen-space pixel position. - */ -void rpgCameraToScreen(vec3 worldPos, vec2 out); \ No newline at end of file diff --git a/src/dusk/rpg/story/CMakeLists.txt b/src/dusk/rpg/story/CMakeLists.txt deleted file mode 100644 index 8a945222..00000000 --- a/src/dusk/rpg/story/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - story.c -) diff --git a/src/dusk/rpg/story/story.c b/src/dusk/rpg/story/story.c deleted file mode 100644 index 007b1ff7..00000000 --- a/src/dusk/rpg/story/story.c +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "story.h" -#include "asset/asset.h" -#include "asset/loader/assetloader.h" -#include "assert/assert.h" -#include "save/save.h" -#include "util/memory.h" -#include "util/string.h" -#include "yyjson.h" - -storyflagmap_t STORY_FLAG_MAP[STORY_FLAG_COUNT_MAX]; -storyflag_t STORY_FLAG_COUNT; - -errorret_t storyInit(void) { - memoryZero(STORY_FLAG_MAP, sizeof(STORY_FLAG_MAP)); - STORY_FLAG_COUNT = 0; - - assetentry_t *entry = assetLock( - "story/storyflag.jsonc", ASSET_LOADER_TYPE_JSON, NULL - ); - errorret_t ret = assetRequireLoaded(entry); - if(errorIsNotOk(ret)) { - assetUnlockEntry(entry); - errorChain(ret); - } - - yyjson_val *root = yyjson_doc_get_root(entry->data.json); - yyjson_val *flags = yyjson_obj_get(root, "flags"); - - yyjson_obj_iter iter = yyjson_obj_iter_with(flags); - yyjson_val *key; - while((key = yyjson_obj_iter_next(&iter))) { - assertTrue( - STORY_FLAG_COUNT < STORY_FLAG_COUNT_MAX, "Too many story flags defined." - ); - - yyjson_val *val = yyjson_obj_iter_get_val(key); - yyjson_val *initial = yyjson_obj_get(val, "initial"); - - storyflag_t flag = STORY_FLAG_COUNT++; - stringCopy(STORY_FLAG_MAP[flag].name, yyjson_get_str(key), STORY_FLAG_NAME_MAX); - STORY_FLAG_MAP[flag].initial = yyjson_is_bool(initial) ? - (storyflagval_t)yyjson_get_bool(initial) : - (storyflagval_t)yyjson_get_int(initial); - } - - assetUnlockEntry(entry); - errorOk(); -} - -storyflag_t storyGetFlagByName(const char_t *name) { - assertNotNull(name, "name must not be NULL"); - - for(storyflag_t flag = 0; flag < STORY_FLAG_COUNT; flag++) { - if(stringEquals(STORY_FLAG_MAP[flag].name, name)) return flag; - } - - return STORY_FLAG_COUNT_MAX; -} - -const char_t * storyGetNameByFlag(storyflag_t flag) { - if(flag >= STORY_FLAG_COUNT) return NULL; - return STORY_FLAG_MAP[flag].name; -} - -storyflagval_t storyGetFlagValue(storyflag_t flag) { - if(flag >= STORY_FLAG_COUNT) return 0; - return SAVE.slot.storyFlags[flag]; -} - -void storySetFlagValue(storyflag_t flag, storyflagval_t value) { - assertTrue(flag < STORY_FLAG_COUNT, "Bad story flag"); - SAVE.slot.storyFlags[flag] = value; - SAVE.slotDirty = true; -} - -void storySetFlagValueByName(const char_t *name, storyflagval_t value) { - storyflag_t flag = storyGetFlagByName(name); - assertTrue(flag < STORY_FLAG_COUNT, "Unknown story flag"); - storySetFlagValue(flag, value); -} diff --git a/src/dusk/rpg/story/story.h b/src/dusk/rpg/story/story.h deleted file mode 100644 index 108fe0fe..00000000 --- a/src/dusk/rpg/story/story.h +++ /dev/null @@ -1,76 +0,0 @@ -/** - * 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 STORY_FLAG_COUNT_MAX 256 -#define STORY_FLAG_NAME_MAX 16 - -typedef uint16_t storyflag_t; -typedef uint8_t storyflagval_t; - -typedef struct { - char_t name[STORY_FLAG_NAME_MAX]; - storyflagval_t initial; -} storyflagmap_t; - -extern storyflagmap_t STORY_FLAG_MAP[STORY_FLAG_COUNT_MAX]; -extern storyflag_t STORY_FLAG_COUNT; - - -/** - * Initializes the story subsystem, loading assets/story/storyflag.jsonc. - * - * @return Any error that occurs. - */ -errorret_t storyInit(void); - -/** - * Gets the ID of a story flag by name. - * - * @param name The name of the story flag to get. - * @return The ID of the story flag, or STORY_FLAG_COUNT_MAX if not found. - */ -storyflag_t storyGetFlagByName(const char_t *name); - -/** - * Gets the name of a story flag by ID. - * - * @param flag The ID of the story flag to get. - * @return The name of the story flag, or NULL if not found. - */ -const char_t * storyGetNameByFlag(storyflag_t flag); - -/** - * Gets the value of a story flag. Reads directly from the active save - * slot (SAVE.slot.storyFlags) - flag values have no separate live copy. - * - * @param flag The ID of the story flag to get. - * @return The value of the story flag, or 0 if not found. - */ -storyflagval_t storyGetFlagValue(storyflag_t flag); - -/** - * Sets the value of a story flag. Writes directly to the active save - * slot (SAVE.slot.storyFlags) - flag values have no separate live copy. - * Marks the save slot dirty. - * - * @param flag The ID of the story flag to set. - * @param value The value to set the story flag to. - */ -void storySetFlagValue(storyflag_t flag, storyflagval_t value); - -/** - * Sets the value of a story flag by name. Writes directly to the active save - * slot (SAVE.slot.storyFlags) - flag values have no separate live copy. - * Marks the save slot dirty. - * - * @param name The name of the story flag to set. - * @param value The value to set the story flag to. - */ -void storySetFlagValueByName(const char_t *name, storyflagval_t value); diff --git a/src/dusk/scene/CMakeLists.txt b/src/dusk/scene/CMakeLists.txt deleted file mode 100644 index 8273e118..00000000 --- a/src/dusk/scene/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - scene.c - scenetype.c -) - -# Subdirs -add_subdirectory(initial) -add_subdirectory(overworld) -add_subdirectory(battle) \ No newline at end of file diff --git a/src/dusk/scene/battle/CMakeLists.txt b/src/dusk/scene/battle/CMakeLists.txt deleted file mode 100644 index a743129d..00000000 --- a/src/dusk/scene/battle/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - scenebattle.c -) diff --git a/src/dusk/scene/battle/scenebattle.c b/src/dusk/scene/battle/scenebattle.c deleted file mode 100644 index 190db10a..00000000 --- a/src/dusk/scene/battle/scenebattle.c +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "scenebattle.h" -#include "assert/assert.h" -#include "display/display.h" -#include "display/displaystate.h" -#include "display/shader/shader.h" -#include "display/shader/shaderunlit.h" -#include "display/spritebatch/spritebatch.h" -#include "display/screen/screen.h" -#include "display/color.h" -#include "scene/scene.h" - -errorret_t sceneBattleInit(scenedata_t *sceneData) { - errorOk(); -} - -errorret_t sceneBattleUpdate(scenedata_t *sceneData) { - battleUpdate(); - errorOk(); -} - -errorret_t sceneBattleRender(scenedata_t *sceneData) { - scenebattle_t *battle = &sceneData->battle; - - sceneBattleCameraUpdateProjection(battle); - sceneBattleCameraUpdateEye(battle); - - errorChain(shaderBind(&SHADER_UNLIT)); - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_MODEL, SCENE.screenIdentity - )); - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_PROJECTION, battle->projection - )); - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_VIEW, battle->eye - )); - - errorChain(displaySetState((displaystate_t){ - .flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL - })); - - for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) { - errorChain(sceneBattleDrawFighter(i)); - } - - errorOk(); -} - -errorret_t sceneBattleDispose(scenedata_t *sceneData) { - errorOk(); -} - -void sceneBattleCameraUpdateProjection(scenebattle_t *battle) { - glm_perspective( - glm_rad(SCENE_BATTLE_CAMERA_FOV), - SCREEN.aspect, - SCENE_BATTLE_CAMERA_NEAR, - SCENE_BATTLE_CAMERA_FAR, - battle->projection - ); -} - -void sceneBattleCameraUpdateEye(scenebattle_t *battle) { - glm_lookat( - (vec3){ 0.0f, 4.0f, 7.0f }, - (vec3){ 0.0f, 0.5f, 0.0f }, - (vec3){ 0.0f, 1.0f, 0.0f }, - battle->eye - ); -} - -void sceneBattleWorldToScreen( - scenebattle_t *battle, - vec3 renderPos, - vec2 out -) { - assertNotNull(battle, "battle cannot be NULL"); - assertNotNull(renderPos, "renderPos cannot be NULL"); - assertNotNull(out, "out cannot be NULL"); - - mat4 viewProj; - glm_mat4_mul(battle->projection, battle->eye, viewProj); - - vec4 viewport = { - 0.0f, 0.0f, (float_t)SCREEN.width, (float_t)SCREEN.height - }; - vec3 window; - glm_project(renderPos, viewProj, viewport, window); - - out[0] = window[0]; - out[1] = (float_t)SCREEN.height - window[1]; -} - -void sceneBattleGetFighterPosition(const uint8_t fighterIndex, vec3 out) { - assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "fighterIndex out of bounds"); - const battlefighter_t *fighter = BATTLE.fighters[fighterIndex]; - assertNotNull(fighter, "Fighter cannot be NULL"); - assertNotNull(out, "out cannot be NULL"); - - // Ordinal (and count) of this fighter among its living-slot teammates, - // in slot order, so each team lays out as an evenly spaced row. - uint8_t ordinal = 0; - uint8_t teamCount = 0; - for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) { - const battlefighter_t *candidate = BATTLE.fighters[i]; - if(candidate == NULL) continue; - if(candidate->status == BATTLE_FIGHTER_STATUS_NULL) continue; - if(candidate->team != fighter->team) continue; - if(i < fighterIndex) ordinal++; - teamCount++; - } - - const float_t x = - ((float_t)ordinal - ((float_t)(teamCount - 1) * 0.5f)) * - SCENE_BATTLE_FIGHTER_SPACING; - const float_t z = fighter->team == BATTLE_FIGHTER_TEAM_ALLY - ? SCENE_BATTLE_ALLY_Z : SCENE_BATTLE_ENEMY_Z; - - out[0] = x; - out[1] = 0.0f; - out[2] = z; -} - -errorret_t sceneBattleDrawFighter(const uint8_t fighterIndex) { - assertTrue(fighterIndex < BATTLE_FIGHTER_POS_COUNT, "fighterIndex out of bounds"); - const battlefighter_t *fighter = BATTLE.fighters[fighterIndex]; - if(fighter == NULL) errorOk(); - if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) errorOk(); - - vec3 position; - sceneBattleGetFighterPosition(fighterIndex, position); - - spritebatchsprite_t sprite; - glm_vec3_copy(position, sprite.min); - glm_vec3_copy(position, sprite.max); - glm_vec3_add(sprite.max, (vec3){ 1.0f, 1.0f, 0.0f }, sprite.max); - glm_vec2_copy((vec2){ 0.0f, 0.0f }, sprite.uvMin); - glm_vec2_copy((vec2){ 1.0f, 1.0f }, sprite.uvMax); - - color_t color; - if(fighter->status == BATTLE_FIGHTER_STATUS_DEAD) { - color = color4b(96, 96, 96, 255); - } else if(fighter->team == BATTLE_FIGHTER_TEAM_ALLY) { - color = COLOR_BLUE; - } else { - color = COLOR_RED; - } - - shadermaterial_t material = { - .unlit = { .color = color, .texture = NULL } - }; - errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material)); - errorChain(spriteBatchFlush()); - - errorOk(); -} diff --git a/src/dusk/scene/battle/scenebattle.h b/src/dusk/scene/battle/scenebattle.h deleted file mode 100644 index bfe0e98e..00000000 --- a/src/dusk/scene/battle/scenebattle.h +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "scene/scenebase.h" -#include "rpg/battle/battle.h" - -// Fixed mock camera, "to begin": looking down at the arena from above and -// in front. Not yet driven by anything (no shake, no per-fighter framing). -#define SCENE_BATTLE_CAMERA_FOV 45.0f -#define SCENE_BATTLE_CAMERA_NEAR 0.1f -#define SCENE_BATTLE_CAMERA_FAR 100.0f - -// Fighter row layout, in render-space units. Allies stand closer to the -// camera (+Z), enemies further away (-Z); each team is spread evenly -// along X, centered on the origin. -#define SCENE_BATTLE_FIGHTER_SPACING 2.0f -#define SCENE_BATTLE_ALLY_Z 2.5f -#define SCENE_BATTLE_ENEMY_Z (-2.5f) - -typedef struct { - mat4 eye; - mat4 projection; -} scenebattle_t; - -/** - * Initializes the battle scene. The battle itself (BATTLE global) is - * expected to already be started, e.g. by a StartBattle cutscene item. - * - * @param sceneData The scene data used for this scene. - * @return An error if the init failed, or errorOk() if it succeeded. - */ -errorret_t sceneBattleInit(scenedata_t *sceneData); - -/** - * Updates the battle scene, ticking the battle simulation. - * - * @param sceneData The scene data used for this scene. - * @return An error if the update failed, or errorOk() if it succeeded. - */ -errorret_t sceneBattleUpdate(scenedata_t *sceneData); - -/** - * Renders the battle scene. - * - * @param sceneData The scene data used for this scene. - * @return An error if the render failed, or errorOk() if it succeeded. - */ -errorret_t sceneBattleRender(scenedata_t *sceneData); - -/** - * Disposes the battle scene. - * - * @param sceneData The scene data used for this scene. - * @return An error if the dispose failed, or errorOk() if it succeeded. - */ -errorret_t sceneBattleDispose(scenedata_t *sceneData); - -/** - * Recomputes the battle camera's projection matrix from the current - * screen aspect ratio, storing it in battle->projection. - * - * @param battle The battle scene data to update. - */ -void sceneBattleCameraUpdateProjection(scenebattle_t *battle); - -/** - * Recomputes the battle camera's eye/view matrix, storing it in - * battle->eye. Fixed for now -- doesn't yet track anything. - * - * @param battle The battle scene data to update. - */ -void sceneBattleCameraUpdateEye(scenebattle_t *battle); - -/** - * Converts a render-space position to screen-space pixel coordinates, - * using the battle camera's current eye and projection matrices. - * - * @param battle The battle scene data holding the camera matrices. - * @param renderPos The render-space position to convert. - * @param out Output vec2 filled with the screen-space pixel position. - */ -void sceneBattleWorldToScreen( - scenebattle_t *battle, - vec3 renderPos, - vec2 out -); - -/** - * Computes the render-space position of a fighter's feet, laying out - * each team as an evenly spaced row facing the camera. - * - * @param fighterIndex Index into BATTLE.fighters. - * @param out Output vec3 filled with the fighter's render-space position. - */ -void sceneBattleGetFighterPosition(const uint8_t fighterIndex, vec3 out); - -/** - * Draws a single fighter as a colored 1x1 quad, skipping empty slots. - * Color differentiates team (ally/enemy) and status (dead fighters are - * drawn dimmed). - * - * @param fighterIndex Index into BATTLE.fighters. - * @return An error if drawing failed, or errorOk() if it succeeded. - */ -errorret_t sceneBattleDrawFighter(const uint8_t fighterIndex); diff --git a/src/dusk/scene/initial/CMakeLists.txt b/src/dusk/scene/initial/CMakeLists.txt deleted file mode 100644 index 97ded407..00000000 --- a/src/dusk/scene/initial/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - sceneinitial.c -) diff --git a/src/dusk/scene/initial/sceneinitial.c b/src/dusk/scene/initial/sceneinitial.c deleted file mode 100644 index 29fada88..00000000 --- a/src/dusk/scene/initial/sceneinitial.c +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "scene/scene.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "error/error.h" -#include "display/screen/screen.h" -#include "console/console.h" -#include "rpg/cutscene/cutscenesystem.h" - -errorret_t sceneInitialInit(scenedata_t *sceneData) { - assertNotNull(sceneData, "Scene data cannot be null"); - memoryZero(&sceneData->initial, sizeof(sceneinitial_t)); - - SCREEN.background = COLOR_BLACK; - cutsceneSystemLoad("cutscenes/initial.jsonc"); - - errorOk(); -} - -errorret_t sceneInitialUpdate(scenedata_t *sceneData) { - assertNotNull(sceneData, "Scene data cannot be null"); - errorOk(); -} - -errorret_t sceneInitialRender(scenedata_t *sceneData) { - assertNotNull(sceneData, "Scene data cannot be null"); - errorOk(); -} - -errorret_t sceneInitialDispose(scenedata_t *sceneData) { - assertNotNull(sceneData, "Scene data cannot be null"); - errorOk(); -} diff --git a/src/dusk/scene/initial/sceneinitial.h b/src/dusk/scene/initial/sceneinitial.h deleted file mode 100644 index 321f2da2..00000000 --- a/src/dusk/scene/initial/sceneinitial.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "scene/scenebase.h" - -typedef struct { - // uint32_t callbackState; - void *nothing; -} sceneinitial_t; - -/** - * Initialises the initial (boot) scene - kicks off the save - * availability/existence check that decides which prompt, if any, to - * show before proceeding to the main menu. - * - * @param sceneData The scene data used for this scene. - * @return An error if the init failed, or errorOk() if it succeeded. - */ -errorret_t sceneInitialInit(scenedata_t *sceneData); - -/** - * Updates the initial scene. Currently a no-op - all the work happens in - * save callbacks and the two modal UI elements' own button handling. - * - * @param sceneData The scene data used for this scene. - * @return An error if the update failed, or errorOk() if it succeeded. - */ -errorret_t sceneInitialUpdate(scenedata_t *sceneData); - -/** - * Renders the initial scene. Currently a no-op - the modals draw - * themselves via the global UI element pipeline. - * - * @param sceneData The scene data used for this scene. - * @return An error if the render failed, or errorOk() if it succeeded. - */ -errorret_t sceneInitialRender(scenedata_t *sceneData); - -/** - * Disposes the initial scene. - * - * @param sceneData The scene data used for this scene. - * @return An error if the dispose failed, or errorOk() if it succeeded. - */ -errorret_t sceneInitialDispose(scenedata_t *sceneData); diff --git a/src/dusk/scene/overworld/CMakeLists.txt b/src/dusk/scene/overworld/CMakeLists.txt deleted file mode 100644 index 6038dc07..00000000 --- a/src/dusk/scene/overworld/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - sceneoverworld.c -) \ No newline at end of file diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c deleted file mode 100644 index 6a7214b5..00000000 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ /dev/null @@ -1,264 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "scene/scene.h" -#include "console/console.h" -#include "assert/assert.h" - -#include "display/display.h" -#include "display/displaystate.h" -#include "display/shader/shader.h" -#include "display/screen/screen.h" -#include "display/shader/shaderunlit.h" -#include "display/spritebatch/spritebatch.h" -#include "display/texture/texture.h" - -#include "rpg/overworld/map.h" -#include "rpg/entity/entity.h" -#include "rpg/rpgcamera.h" - -#include "asset/loader/assetloader.h" -#include "asset/loader/assetentry.h" - -#include "util/memory.h" - -errorret_t sceneOverworldInit(scenedata_t *sceneData) { - assertNotNull(sceneData, "Scene data cannot be null"); - errorOk(); -} - -errorret_t sceneOverworldUpdate(scenedata_t *sceneData) { - assertNotNull(sceneData, "Scene data cannot be null"); - errorOk(); -} - -errorret_t sceneOverworldRender(scenedata_t *sceneData) { - assertNotNull(sceneData, "Scene data cannot be null"); - - sceneoverworld_t *overworld = &sceneData->overworld; - sceneOverworldCullUpdate(overworld); - - - // Overworld camera - errorChain(shaderBind(&SHADER_UNLIT)); - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_MODEL, SCENE.screenIdentity - )); - - rpgCameraUpdateProjection(); - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_PROJECTION, RPG_CAMERA.projection - )); - - rpgCameraUpdateEye(); - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_VIEW, RPG_CAMERA.eye - )); - - // Chunk tiles - errorChain(displaySetState((displaystate_t){ - .flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL - })); - errorChain(sceneOverworldDrawChunksBase(overworld)); - - // Entities - errorChain(displaySetState((displaystate_t){ - .flags = DISPLAY_STATE_FLAG_CULL - })); - - for(uint8_t i = 0; i < ENTITY_COUNT; i++) { - entity_t *ent = &ENTITIES[i]; - if(ent->type == ENTITY_TYPE_NULL) continue; - errorChain(sceneOverworldDrawEntity(overworld, ent)); - } - - // Chunk props - errorChain(displaySetState((displaystate_t){ - .flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL - })); - errorChain(sceneOverworldDrawChunksProps(overworld)); - - // Weather effects - // errorChain(shaderSetMatrix( - // &SHADER_UNLIT, SHADER_UNLIT_PROJECTION, SCENE.screenProj - // )); - // errorChain(shaderSetMatrix( - // &SHADER_UNLIT, SHADER_UNLIT_VIEW, SCENE.screenView - // )); - // errorChain(displaySetState((displaystate_t){ - // .flags = DISPLAY_STATE_FLAG_BLEND - // })); - - // spritebatchsprite_t skyboxSprite; - // memoryZero(&skyboxSprite, sizeof(spritebatchsprite_t)); - // skyboxSprite.max[0] = (float_t)(SCREEN.width / SCREEN.scaleUi); - // skyboxSprite.max[1] = (float_t)(SCREEN.height / SCREEN.scaleUi); - // shadermaterial_t skyboxMaterial = { - // .unlit = { .color = color(0xFF, 0x00, 0x00, 0xFF/2), .texture = NULL } - // }; - // errorChain( - // spriteBatchBuffer(&skyboxSprite, 1, &SHADER_UNLIT, skyboxMaterial) - // ); - // errorChain(spriteBatchFlush()); - - errorOk(); -} - -void sceneOverworldCullUpdate(sceneoverworld_t *overworld) { - rpgCameraGetPosition(overworld->cullTarget); - - const float_t worldH = - (float_t)(SCREEN.height / SCREEN.scale3d) / TILE_SIZE_PIXELS; - const float_t worldW = worldH * SCREEN.aspect; - - overworld->cullHalfRangeX = - (worldW * 0.5f) + SCENE_OVERWORLD_CHUNK_CULL_SKIN_X; - overworld->cullHalfRangeY = - (worldH * 0.5f) + SCENE_OVERWORLD_CHUNK_CULL_SKIN_Y; -} - -bool_t sceneOverworldChunkShouldRender( - const sceneoverworld_t *overworld, - const chunk_t *chunk -) { - worldpos_t worldPos; - chunkPosToWorldPos(&chunk->position, &worldPos); - - const float_t chunkCenterX = (float_t)worldPos.x + (CHUNK_WIDTH * 0.5f); - const float_t chunkCenterY = (float_t)worldPos.y + (CHUNK_HEIGHT * 0.5f); - - if(fabsf(overworld->cullTarget[0] - chunkCenterX) > - overworld->cullHalfRangeX) return false; - if(fabsf(overworld->cullTarget[1] - chunkCenterY) > - overworld->cullHalfRangeY) return false; - - return true; -} - -bool_t sceneOverworldEntityShouldRender( - const sceneoverworld_t *overworld, - const entity_t *ent -) { - if(fabsf(overworld->cullTarget[0] - ent->renderPosition[0]) > - overworld->cullHalfRangeX) return false; - if(fabsf(overworld->cullTarget[1] - ent->renderPosition[1]) > - overworld->cullHalfRangeY) return false; - - return true; -} - -errorret_t sceneOverworldDrawEntity( - const sceneoverworld_t *overworld, - entity_t *ent -) { - if(!sceneOverworldEntityShouldRender(overworld, ent)) errorOk(); - - spritebatchsprite_t sprite; - glm_vec3_copy(ent->renderPosition, sprite.min); - glm_vec3_add(sprite.min, (vec3){ 0, -0.05f, 0.05f }, sprite.min);// Stop Fight - glm_vec3_copy(sprite.min, sprite.max); - glm_vec3_add(sprite.max, (vec3){ 1, 1, 0 }, sprite.max); - glm_vec2_copy((vec2){ 0, 0 }, sprite.uvMin); - glm_vec2_copy((vec2){ 1, 1 }, sprite.uvMax); - - color_t color; - switch(ent->direction) { - case ENTITY_DIR_NORTH: color = COLOR_YELLOW; break; - case ENTITY_DIR_EAST: color = COLOR_RED; break; - case ENTITY_DIR_SOUTH: color = COLOR_GREEN; break; - case ENTITY_DIR_WEST: color = COLOR_BLUE; break; - default: color = COLOR_CYAN; break; - } - - shadermaterial_t material = { - .unlit = { .color = color, .texture = NULL } - }; - errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material)); - errorChain(spriteBatchFlush()); - - errorOk(); -} - -errorret_t sceneOverworldDrawChunksBase(const sceneoverworld_t *overworld) { - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - chunk_t *chunk = MAP.chunkOrder[i]; - if(chunk == NULL) continue; - if(!sceneOverworldChunkShouldRender(overworld, chunk)) continue; - if(chunk->meshCount == 0) continue; - if(chunk->modelEntries[0] == NULL) continue; - if(chunk->modelEntries[0]->state != ASSET_ENTRY_STATE_LOADED) continue; - - assetmodeloutput_t *model = &chunk->modelEntries[0]->data.model; - if(model->meshEntry == NULL) continue; - - texture_t *tex = model->texEntry != NULL - ? &model->texEntry->data.texture - : NULL; - - shadermaterial_t mat = { - .unlit = { .color = model->color, .texture = tex } - }; - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_MODEL, chunk->meshModels[0] - )); - errorChain(shaderSetMaterial(&SHADER_UNLIT, &mat)); - errorChain(meshDraw( - &model->meshEntry->data.mesh.mesh, 0, -1 - )); - } - - // Restore identity model so subsequent renders (e.g. entities) are - // not affected by the last chunk transform. - mat4 identity; - glm_mat4_identity(identity); - errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, identity)); - - errorOk(); -} - -errorret_t sceneOverworldDrawChunksProps(const sceneoverworld_t *overworld) { - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - chunk_t *chunk = MAP.chunkOrder[i]; - if(chunk == NULL) continue; - if(!sceneOverworldChunkShouldRender(overworld, chunk)) continue; - - for(uint8_t m = 1; m < chunk->meshCount; m++) { - if(chunk->modelEntries[m] == NULL) continue; - if(chunk->modelEntries[m]->state != ASSET_ENTRY_STATE_LOADED) continue; - - assetmodeloutput_t *model = &chunk->modelEntries[m]->data.model; - if(model->meshEntry == NULL) continue; - - texture_t *tex = model->texEntry != NULL - ? &model->texEntry->data.texture - : NULL; - - shadermaterial_t mat = { - .unlit = { .color = model->color, .texture = tex } - }; - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_MODEL, chunk->meshModels[m] - )); - errorChain(shaderSetMaterial(&SHADER_UNLIT, &mat)); - errorChain(meshDraw( - &model->meshEntry->data.mesh.mesh, 0, -1 - )); - } - } - - // Restore identity model in case anything renders after props. - mat4 identity; - glm_mat4_identity(identity); - errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, identity)); - - errorOk(); -} - -errorret_t sceneOverworldDispose(scenedata_t *sceneData) { - assertNotNull(sceneData, "Scene data cannot be null"); - errorOk(); -} diff --git a/src/dusk/scene/overworld/sceneoverworld.h b/src/dusk/scene/overworld/sceneoverworld.h deleted file mode 100644 index 048e20d0..00000000 --- a/src/dusk/scene/overworld/sceneoverworld.h +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "scene/scenebase.h" -#include "rpg/overworld/chunk.h" -#include "rpg/entity/entity.h" - -typedef struct { - // Cached per-frame chunk culling bounds, refreshed by - // sceneOverworldCullUpdate() so sceneOverworldChunkShouldRender() - // does not need to recompute them for every chunk. - vec3 cullTarget; - float_t cullHalfRangeX; - float_t cullHalfRangeY; -} sceneoverworld_t; - -// Extra world-space margin, in tiles, added on top of the rough -// screen-size skin below - cheap insurance against the approximation -// culling a chunk that is actually still just in view. Y gets its own -// value since the camera's tilt makes the vertical check less exact. -#define SCENE_OVERWORLD_CHUNK_CULL_PADDING_X 8.0f -#define SCENE_OVERWORLD_CHUNK_CULL_PADDING_Y 4.0f - -// The camera is tilted between the ground-depth (Y) and height (Z) -// axes, so a chunk's on-screen vertical extent also depends on its Z -// layers. Rather than projecting every corner, fold that into a -// generous skin margin on the depth check. -#define SCENE_OVERWORLD_CHUNK_CULL_SKIN_X \ - ((CHUNK_WIDTH * 0.5f) + SCENE_OVERWORLD_CHUNK_CULL_PADDING_X) -#define SCENE_OVERWORLD_CHUNK_CULL_SKIN_Y \ - ((CHUNK_HEIGHT * 0.5f) + (CHUNK_DEPTH * WORLD_LAYER_HEIGHT) + \ - SCENE_OVERWORLD_CHUNK_CULL_PADDING_Y) - -/** - * Initialises the overworld scene. - * - * @param sceneData The scene data used for this scene. - * @return An error if the init failed, or errorOk() if it succeeded. - */ -errorret_t sceneOverworldInit(scenedata_t *sceneData); - -/** - * Updates the overworld scene. - * - * @param sceneData The scene data used for this scene. - * @return An error if the update failed, or errorOk() if it succeeded. - */ -errorret_t sceneOverworldUpdate(scenedata_t *sceneData); - -/** - * Refreshes the chunk culling bounds cached on the overworld scene data, - * from the current camera position and screen dimensions. Must be - * called once per frame before sceneOverworldChunkShouldRender(). - * - * @param overworld The overworld scene data to update. - */ -void sceneOverworldCullUpdate(sceneoverworld_t *overworld); - -/** - * Roughly checks whether a chunk falls within the visible screen area, - * based on chunk size and screen dimensions. Cheap approximation, not - * an exact frustum check - includes a generous skin margin to account - * for the camera's angle tilting height/depth onto the screen's - * vertical axis, so it may pass some chunks that are actually just out - * of view but will never wrongly cull one that is visible. - * - * @param overworld The overworld scene data holding the cached culling - * bounds, as refreshed by sceneOverworldCullUpdate(). - * @param chunk The chunk to check. - * @return true if the chunk should be rendered, false otherwise. - */ -bool_t sceneOverworldChunkShouldRender( - const sceneoverworld_t *overworld, - const chunk_t *chunk -); - -/** - * Draws the base (tile) mesh of every loaded chunk, with normal depth - * testing. Must be called before entities are rendered. - * - * @param overworld The overworld scene data holding the cached culling - * bounds, as refreshed by sceneOverworldCullUpdate(). - * @return An error if drawing failed, or errorOk() on success. - */ -errorret_t sceneOverworldDrawChunksBase(const sceneoverworld_t *overworld); - -/** - * Draws every loaded chunk's additional meshes (trees, buildings, etc), - * with normal depth testing so they correctly occlude entities standing - * beneath them. Must be called after entities are rendered. - * - * @param overworld The overworld scene data holding the cached culling - * bounds, as refreshed by sceneOverworldCullUpdate(). - * @return An error if drawing failed, or errorOk() on success. - */ -errorret_t sceneOverworldDrawChunksProps(const sceneoverworld_t *overworld); - -/** - * Roughly checks whether an entity falls within the visible screen - * area. Reuses the same cached chunk culling bounds, since an entity - * is just a point within that same space - deliberately cheap, a - * couple of subtractions and comparisons, since it runs per entity - * every frame and it is not worth spending more math than that to - * avoid drawing one that is slightly off-screen. - * - * @param overworld The overworld scene data holding the cached culling - * bounds, as refreshed by sceneOverworldCullUpdate(). - * @param ent The entity to check. - * @return true if the entity should be rendered, false otherwise. - */ -bool_t sceneOverworldEntityShouldRender( - const sceneoverworld_t *overworld, - const entity_t *ent -); - -/** - * Draws a single entity as a sprite, colored by its facing direction. - * Skips drawing (without error) if the entity should not render. - * - * @param overworld The overworld scene data holding the cached culling - * bounds, as refreshed by sceneOverworldCullUpdate(). - * @param ent The entity to draw. - * @return An error if drawing failed, or errorOk() on success. - */ -errorret_t sceneOverworldDrawEntity( - const sceneoverworld_t *overworld, - entity_t *ent -); - -/** - * Renders the overworld scene. - * - * @param sceneData The scene data used for this scene. - * @return An error if the render failed, or errorOk() if it succeeded. - */ -errorret_t sceneOverworldRender(scenedata_t *sceneData); - -/** - * Disposes the overworld scene. - * - * @param sceneData The scene data used for this scene. - * @return An error if the dispose failed, or errorOk() if it succeeded. - */ -errorret_t sceneOverworldDispose(scenedata_t *sceneData); \ No newline at end of file diff --git a/src/dusk/scene/scene.c b/src/dusk/scene/scene.c deleted file mode 100644 index 72c4e9d9..00000000 --- a/src/dusk/scene/scene.c +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) 2026 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -#include "scene.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "time/time.h" -#include "display/screen/screen.h" -#include "display/shader/shaderunlit.h" -#include "display/display.h" -#include "ui/ui.h" -#include "asset/asset.h" -#include "asset/loader/assetloader.h" -#include "console/console.h" - -scene_t SCENE; - -errorret_t sceneInit(void) { - memoryZero(&SCENE, sizeof(scene_t)); - errorOk(); -} - -errorret_t sceneUpdate(void) { - // Handle scene change. - if(SCENE.next != SCENE_TYPE_NULL) { - if( - SCENE.current != SCENE_TYPE_NULL && - SCENE_TYPES[SCENE.current].dispose != NULL - ) { - errorChain(SCENE_TYPES[SCENE.current].dispose(&SCENE.data)); - } - - SCENE.current = SCENE.next; - SCENE.next = SCENE_TYPE_NULL; - - if( - SCENE.current != SCENE_TYPE_NULL && - SCENE_TYPES[SCENE.current].init != NULL - ) { - errorChain(SCENE_TYPES[SCENE.current].init(&SCENE.data)); - } - } - - #if DUSK_TIME_DYNAMIC - if(TIME.dynamicUpdate) errorOk(); - #endif - - if( - SCENE.current != SCENE_TYPE_NULL && - SCENE_TYPES[SCENE.current].update != NULL - ) { - errorChain(SCENE_TYPES[SCENE.current].update(&SCENE.data)); - } - - errorOk(); -} - -errorret_t sceneRender(void) { - // Setup screen matrices for 3D rendering. - glm_mat4_identity(SCENE.screenIdentity); - - glm_ortho( - 0.0f, (float_t)(SCREEN.width / SCREEN.scaleUi), - (float_t)(SCREEN.height / SCREEN.scaleUi), 0.0f, - 0.1f, 100.0f, - SCENE.screenProj - ); - - glm_lookat( - (vec3){ 0.0f, 0.0f, 1.0f }, - (vec3){ 0.0f, 0.0f, 0.0f }, - (vec3){ 0.0f, 1.0f, 0.0f }, - SCENE.screenView - ); - - // Scene rendering - if( - SCENE.current != SCENE_TYPE_NULL && - SCENE_TYPES[SCENE.current].render != NULL - ) { - errorChain(SCENE_TYPES[SCENE.current].render(&SCENE.data)); - } - - // UI Rendering - errorChain(shaderBind(&SHADER_UNLIT)); - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_MODEL, SCENE.screenIdentity - )); - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_PROJECTION, SCENE.screenProj - )); - errorChain(shaderSetMatrix( - &SHADER_UNLIT, SHADER_UNLIT_VIEW, SCENE.screenView - )); - - errorChain(displaySetState((displaystate_t){ - .flags = DISPLAY_STATE_FLAG_BLEND - })); - errorChain(uiRender()); - - errorOk(); -} - -void sceneSet(const scenetype_t type) { - assertTrue(type < SCENE_TYPE_COUNT, "Invalid scene type."); - SCENE.next = type; -} - -errorret_t sceneDispose(void) { - if( - SCENE.current != SCENE_TYPE_NULL && - SCENE_TYPES[SCENE.current].dispose != NULL - ) { - errorChain(SCENE_TYPES[SCENE.current].dispose(&SCENE.data)); - } - - errorOk(); -} diff --git a/src/dusk/scene/scene.h b/src/dusk/scene/scene.h deleted file mode 100644 index db138699..00000000 --- a/src/dusk/scene/scene.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" -#include "scenetype.h" - -typedef struct { - scenetype_t current; - scenetype_t next; - scenedata_t data; - mat4 screenProj; - mat4 screenView; - mat4 screenIdentity; -} scene_t; - -extern scene_t SCENE; - -/** - * Initialises the scene manager. - * - * @return An error if the init failed, or errorOk() if it succeeded. - */ -errorret_t sceneInit(void); - -/** - * Ticks the scene manager. Processes any pending scene transition, then - * calls scriptSceneUpdate on the active scene. - * - * @return An error if the update failed, or errorOk() if it succeeded. - */ -errorret_t sceneUpdate(void); - -/** - * Renders the current scene (entities, render pipeline, UI). - * - * @return An error if the render failed, or errorOk() if it succeeded. - */ -errorret_t sceneRender(void); - -/** - * Requests the next frame to change to this scene. - * - * @param type The scene type to change to. - */ -void sceneSet(const scenetype_t type); - -/** - * Disposes the active scene immediately. - * - * @return An error if the dispose failed, or errorOk() if it succeeded. - */ -errorret_t sceneDispose(void); diff --git a/src/dusk/scene/scenebase.h b/src/dusk/scene/scenebase.h deleted file mode 100644 index 7846a9cc..00000000 --- a/src/dusk/scene/scenebase.h +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef union scenedata_u scenedata_t; \ No newline at end of file diff --git a/src/dusk/scene/scenetype.c b/src/dusk/scene/scenetype.c deleted file mode 100644 index 3dad6627..00000000 --- a/src/dusk/scene/scenetype.c +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "scenetype.h" - -scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT] = { - [SCENE_TYPE_NULL] = { 0 }, - - [SCENE_TYPE_INITIAL] = { - .init = sceneInitialInit, - .update = sceneInitialUpdate, - .render = sceneInitialRender, - .dispose = sceneInitialDispose - }, - - [SCENE_TYPE_OVERWORLD] = { - .init = sceneOverworldInit, - .update = sceneOverworldUpdate, - .render = sceneOverworldRender, - .dispose = sceneOverworldDispose - }, - - [SCENE_TYPE_BATTLE] = { - .init = sceneBattleInit, - .update = sceneBattleUpdate, - .render = sceneBattleRender, - .dispose = sceneBattleDispose - }, -}; - diff --git a/src/dusk/scene/scenetype.h b/src/dusk/scene/scenetype.h deleted file mode 100644 index 677959ee..00000000 --- a/src/dusk/scene/scenetype.h +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "scene/scenebase.h" -#include "scene/initial/sceneinitial.h" -#include "scene/overworld/sceneoverworld.h" -#include "scene/battle/scenebattle.h" - -typedef union scenedata_u { - sceneinitial_t initial; - sceneoverworld_t overworld; - scenebattle_t battle; -} scenedata_t; - -typedef errorret_t (*scenecallback_t)(scenedata_t *); - -typedef struct { - scenecallback_t init; - scenecallback_t update; - scenecallback_t render; - scenecallback_t dispose; -} scenecallbacks_t; - -typedef enum { - SCENE_TYPE_NULL, - - SCENE_TYPE_INITIAL, - SCENE_TYPE_OVERWORLD, - SCENE_TYPE_BATTLE, - - SCENE_TYPE_COUNT -} scenetype_t; - -extern scenecallbacks_t SCENE_TYPES[SCENE_TYPE_COUNT]; \ No newline at end of file diff --git a/src/dusk/script/core/array/scriptarraycopy.c b/src/dusk/script/core/array/scriptarraycopy.c index 000c12ab..1f33a57b 100644 --- a/src/dusk/script/core/array/scriptarraycopy.c +++ b/src/dusk/script/core/array/scriptarraycopy.c @@ -15,6 +15,5 @@ scriptvalue_t scriptArrayCopy(scriptruntime_t *runtime, const scriptarray_t *sou scriptvalue_t result = scriptArrayNew(runtime); uint16_t i; for(i = 0; i < source->length; i++) scriptArrayPushRaw(runtime, result.as.array, source->items[i]); - result.as.array->category = source->category; return result; } diff --git a/src/dusk/script/core/array/scriptarraycopy.h b/src/dusk/script/core/array/scriptarraycopy.h index 045b95f6..9d64ca67 100644 --- a/src/dusk/script/core/array/scriptarraycopy.h +++ b/src/dusk/script/core/array/scriptarraycopy.h @@ -11,7 +11,7 @@ #include "value/scriptvalue.h" /** - * Copies the contents of one array into a new array with the same category. + * Copies the contents of one array into a new array. * * @param runtime The runtime. * @param source The array to copy. diff --git a/src/dusk/script/core/array/scriptarraydeep.c b/src/dusk/script/core/array/scriptarraydeep.c index 7a55d39c..dcad2b6b 100644 --- a/src/dusk/script/core/array/scriptarraydeep.c +++ b/src/dusk/script/core/array/scriptarraydeep.c @@ -28,7 +28,6 @@ scriptvalue_t scriptDeepCopy(scriptruntime_t *runtime, const scriptvalue_t value for(i = 0; i < value.as.array->length; i++) { scriptArrayPushRaw(runtime, result.as.array, scriptDeepCopy(runtime, value.as.array->items[i], depth + 1)); } - result.as.array->category = value.as.array->category; return result; } diff --git a/src/dusk/script/core/array/scriptarraymodify.c b/src/dusk/script/core/array/scriptarraymodify.c index 2a7c3539..01913e1c 100644 --- a/src/dusk/script/core/array/scriptarraymodify.c +++ b/src/dusk/script/core/array/scriptarraymodify.c @@ -16,7 +16,6 @@ void scriptArrayPush(scriptruntime_t *runtime, scriptarray_t *array, const scriptvalue_t value) { if(array->frozen) scriptRaise(runtime, "Can not change a constant"); - scriptArrayCheck(runtime, array, value); scriptArrayPushRaw(runtime, array, value); } @@ -54,7 +53,6 @@ void scriptArrayPushRaw(scriptruntime_t *runtime, scriptarray_t *array, const sc void scriptArrayInsert(scriptruntime_t *runtime, scriptarray_t *array, const uint16_t index, const scriptvalue_t value) { uint16_t i; - scriptArrayCheck(runtime, array, value); scriptArrayPushRaw(runtime, array, value); for(i = (uint16_t)(array->length - 1); i > index; i--) { array->items[i] = array->items[i - 1]; diff --git a/src/dusk/script/core/array/scriptarraynew.c b/src/dusk/script/core/array/scriptarraynew.c index ca88184c..7b8b6356 100644 --- a/src/dusk/script/core/array/scriptarraynew.c +++ b/src/dusk/script/core/array/scriptarraynew.c @@ -15,41 +15,5 @@ scriptvalue_t scriptArrayNew(scriptruntime_t *runtime) { scriptvalue_t value = { .type = SCRIPT_VALUE_ARRAY }; value.as.array = scriptAllocKind(runtime, sizeof(scriptarray_t), SCRIPT_HEAP_KIND_ARRAY); - value.as.array->category = SCRIPT_CATEGORY_UNKNOWN; return value; } - -scriptcategory_t scriptCategoryOf(const scriptvalue_t value) { - switch(value.type) { - case SCRIPT_VALUE_NUMBER: return SCRIPT_CATEGORY_NUMBER; - case SCRIPT_VALUE_BOOL: return SCRIPT_CATEGORY_BOOL; - case SCRIPT_VALUE_STRING: return SCRIPT_CATEGORY_STRING; - case SCRIPT_VALUE_ARRAY: return SCRIPT_CATEGORY_ARRAY; - case SCRIPT_VALUE_STRUCT: return SCRIPT_CATEGORY_STRUCT; - case SCRIPT_VALUE_FUNCTION: return SCRIPT_CATEGORY_FUNCTION; - case SCRIPT_VALUE_PROMISE: return SCRIPT_CATEGORY_PROMISE; - default: return SCRIPT_CATEGORY_UNKNOWN; - } -} - -void scriptArrayCheck(scriptruntime_t *runtime, scriptarray_t *array, const scriptvalue_t value) { - scriptcategory_t category; - - if(value.type == SCRIPT_VALUE_UNDEFINED) { - scriptRaise(runtime, "Arrays can not hold undefined"); - } - - if(value.type == SCRIPT_VALUE_NULL) { - if(array->category == SCRIPT_CATEGORY_NUMBER || array->category == SCRIPT_CATEGORY_BOOL) { - scriptRaise(runtime, "Numbers and bools can not be null, so an array of them can not hold null"); - } - return; - } - - category = scriptCategoryOf(value); - if(array->category == SCRIPT_CATEGORY_UNKNOWN) { - array->category = category; - } else if(array->category != category) { - scriptRaise(runtime, "Arrays can not mix types, tried to add %s to an array of another type", scriptTypeName(value)); - } -} diff --git a/src/dusk/script/core/array/scriptarraynew.h b/src/dusk/script/core/array/scriptarraynew.h index b02a388e..c328507f 100644 --- a/src/dusk/script/core/array/scriptarraynew.h +++ b/src/dusk/script/core/array/scriptarraynew.h @@ -18,22 +18,3 @@ * @returns The array value. */ scriptvalue_t scriptArrayNew(scriptruntime_t *runtime); - -/** - * Which kind of thing an array can hold a value as. - * - * @param value Value to look at. - * @returns The category, SCRIPT_CATEGORY_UNKNOWN for null and undefined. - */ -scriptcategory_t scriptCategoryOf(const scriptvalue_t value); - -/** - * Checks a value can go in an array and sets the array's category from the - * first value placed in it. Mixed types, undefined, and null in an array of - * numbers or bools are fatal errors. - * - * @param runtime The runtime. - * @param array The array. - * @param value The value about to be added. - */ -void scriptArrayCheck(scriptruntime_t *runtime, scriptarray_t *array, const scriptvalue_t value); diff --git a/src/dusk/script/core/builtin/array/scriptbarraybasic.c b/src/dusk/script/core/builtin/array/scriptbarraybasic.c index 046e0cf0..c060dcff 100644 --- a/src/dusk/script/core/builtin/array/scriptbarraybasic.c +++ b/src/dusk/script/core/builtin/array/scriptbarraybasic.c @@ -66,7 +66,6 @@ SCRIPT_NATIVE(scriptBEmpty) { scriptRejectString(runtime, args[0]); array = scriptArgArrayWritable(runtime, args, argc, 0); array->length = 0; - array->category = SCRIPT_CATEGORY_UNKNOWN; return args[0]; } diff --git a/src/dusk/script/core/builtin/array/scriptbarraymodify.c b/src/dusk/script/core/builtin/array/scriptbarraymodify.c index 5f1de894..86e0c7d9 100644 --- a/src/dusk/script/core/builtin/array/scriptbarraymodify.c +++ b/src/dusk/script/core/builtin/array/scriptbarraymodify.c @@ -27,7 +27,6 @@ SCRIPT_NATIVE(scriptBFill) { if(count < 0 || count > SCRIPT_ARRAY_LENGTH_MAX) scriptRaise(runtime, "fill needs a count from 0 to %d", SCRIPT_ARRAY_LENGTH_MAX); array->length = 0; - array->category = SCRIPT_CATEGORY_UNKNOWN; for(i = 0; i < count; i++) scriptArrayPush(runtime, array, args[1]); return args[0]; } @@ -65,7 +64,6 @@ SCRIPT_NATIVE(scriptBSplice) { removed = scriptArrayNew(runtime); for(i = 0; i < count; i++) scriptArrayPushRaw(runtime, removed.as.array, array->items[start + i]); - removed.as.array->category = array->category; for(i = 0; i < count; i++) scriptArrayRemove(array, (uint16_t)start); return removed; } diff --git a/src/dusk/script/core/builtin/array/scriptbarrayslice.c b/src/dusk/script/core/builtin/array/scriptbarrayslice.c index 1f73c988..9f789590 100644 --- a/src/dusk/script/core/builtin/array/scriptbarrayslice.c +++ b/src/dusk/script/core/builtin/array/scriptbarrayslice.c @@ -47,14 +47,12 @@ SCRIPT_NATIVE(scriptBSubarr) { result = scriptArrayNew(runtime); for(i = (uint16_t)start; i < end; i++) scriptArrayPushRaw(runtime, result.as.array, args[0].as.array->items[i]); - result.as.array->category = args[0].as.array->category; return result; } SCRIPT_NATIVE(scriptBFlatten) { scriptarray_t *array; scriptvalue_t result = scriptArrayNew(runtime); - scriptcategory_t category = SCRIPT_CATEGORY_UNKNOWN; uint16_t i; uint16_t j; @@ -67,14 +65,9 @@ SCRIPT_NATIVE(scriptBFlatten) { scriptRaise(runtime, "flatten needs an array of arrays, found %s", scriptTypeName(array->items[i])); } inner = array->items[i].as.array; - if(inner->category != SCRIPT_CATEGORY_UNKNOWN) { - if(category == SCRIPT_CATEGORY_UNKNOWN) category = inner->category; - else if(category != inner->category) scriptRaise(runtime, "flatten only works on arrays that all hold the same type"); - } for(j = 0; j < inner->length; j++) scriptArrayPushRaw(runtime, result.as.array, inner->items[j]); } - result.as.array->category = category; return result; } diff --git a/src/dusk/script/core/ops/scriptref.c b/src/dusk/script/core/ops/scriptref.c index 5df29523..bb7c9f6e 100644 --- a/src/dusk/script/core/ops/scriptref.c +++ b/src/dusk/script/core/ops/scriptref.c @@ -54,7 +54,6 @@ void scriptRefSet(scriptruntime_t *runtime, const scriptref_t *ref, const script if(index == array->length) { scriptArrayPush(runtime, array, value); } else { - scriptArrayCheck(runtime, array, value); array->items[(uint16_t)index] = value; } return; diff --git a/src/dusk/script/core/value/scriptarray.h b/src/dusk/script/core/value/scriptarray.h index a40ec51e..486df7fd 100644 --- a/src/dusk/script/core/value/scriptarray.h +++ b/src/dusk/script/core/value/scriptarray.h @@ -10,22 +10,9 @@ #include "core/scriptcore.h" #include "value/scriptvalue.h" -// What an array is allowed to hold, all number types count as one. -typedef enum { - SCRIPT_CATEGORY_UNKNOWN, - SCRIPT_CATEGORY_NUMBER, - SCRIPT_CATEGORY_BOOL, - SCRIPT_CATEGORY_STRING, - SCRIPT_CATEGORY_ARRAY, - SCRIPT_CATEGORY_STRUCT, - SCRIPT_CATEGORY_FUNCTION, - SCRIPT_CATEGORY_PROMISE -} scriptcategory_t; - struct scriptarray_s { uint16_t length; uint16_t capacity; - scriptcategory_t category; scriptvalue_t *items; bool frozen; // a constant, which nothing changes }; diff --git a/src/dusk/ui/CMakeLists.txt b/src/dusk/ui/CMakeLists.txt deleted file mode 100644 index ac7639b2..00000000 --- a/src/dusk/ui/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -add_subdirectory(debug) -add_subdirectory(dialog) -add_subdirectory(screen) -add_subdirectory(focus) -add_subdirectory(overlay) -add_subdirectory(rpg) -add_subdirectory(transition) -add_subdirectory(widget) - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - ui.c - uielement.c - uielementlist.c - # uitextbox.c -) \ No newline at end of file diff --git a/src/dusk/ui/debug/CMakeLists.txt b/src/dusk/ui/debug/CMakeLists.txt deleted file mode 100644 index 52d8095a..00000000 --- a/src/dusk/ui/debug/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uiconsole.c - uicutscene.c - uifps.c - uiplayerpos.c -) diff --git a/src/dusk/ui/debug/uiconsole.c b/src/dusk/ui/debug/uiconsole.c deleted file mode 100644 index 631bd1fd..00000000 --- a/src/dusk/ui/debug/uiconsole.c +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiconsole.h" -#include "display/screen/screen.h" -#include "display/text/text.h" -#include "display/spritebatch/spritebatch.h" - -uiconsole_t UICONSOLE; - -errorret_t uiConsoleInit(void) { - float_t lineH = (float_t)FONT_DEFAULT.tileset->tileHeight; - - for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) { - uiLabelInit( - &UICONSOLE.labels[i], - CONSOLE.line[i], - UICONSOLE.sprites[i], CONSOLE_LINE_MAX - ); - uiLabelSetX(&UICONSOLE.labels[i], (float_t)SCREEN.scanX); - uiLabelSetY( - &UICONSOLE.labels[i], (float_t)SCREEN.scanY + lineH * (float_t)i - ); - } - - errorOk(); -} - -errorret_t uiConsoleDraw(void) { - if(!CONSOLE.visible) errorOk(); - - for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) { - UICONSOLE.labels[i].dirty = true; - errorChain(uiLabelRender(&UICONSOLE.labels[i], COLOR_RED)); - } - return spriteBatchFlush(); -} diff --git a/src/dusk/ui/debug/uiconsole.h b/src/dusk/ui/debug/uiconsole.h deleted file mode 100644 index be745ab3..00000000 --- a/src/dusk/ui/debug/uiconsole.h +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "console/console.h" -#include "ui/widget/uilabel.h" - -typedef struct { - uilabel_t labels[CONSOLE_HISTORY_MAX]; - spritebatchsprite_t sprites[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX]; -} uiconsole_t; - -extern uiconsole_t UICONSOLE; - -/** - * Initializes the console's history labels. - * - * @return Any error that occurs. - */ -errorret_t uiConsoleInit(void); - -/** - * Renders the console history into the scan-safe area. - * No-ops when the console is not visible. - * - * @return Any error that occurs. - */ -errorret_t uiConsoleDraw(void); diff --git a/src/dusk/ui/debug/uicutscene.c b/src/dusk/ui/debug/uicutscene.c deleted file mode 100644 index 94bdf321..00000000 --- a/src/dusk/ui/debug/uicutscene.c +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uicutscene.h" -#include "util/string.h" -#include "display/screen/screen.h" -#include "display/text/text.h" -#include "display/spritebatch/spritebatch.h" -#include "display/color.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "rpg/cutscene/item/cutsceneitem.h" - -uicutscene_t UICUTSCENE; - -errorret_t uiCutsceneInit() { - uiLabelInit( - &UICUTSCENE.countLabel, - UICUTSCENE.countText, - UICUTSCENE.countSprites, UI_CUTSCENE_SPRITES_MAX - ); - uiLabelSetX(&UICUTSCENE.countLabel, (float_t)SCREEN.scanX); - uiLabelSetY(&UICUTSCENE.countLabel, (float_t)SCREEN.scanY + - (float_t)FONT_DEFAULT.tileset->tileHeight * 2 - ); - - uiLabelInit( - &UICUTSCENE.itemLabel, - UICUTSCENE.itemText, - UICUTSCENE.itemSprites, UI_CUTSCENE_SPRITES_MAX - ); - uiLabelSetX(&UICUTSCENE.itemLabel, (float_t)SCREEN.scanX); - uiLabelSetY(&UICUTSCENE.itemLabel, (float_t)SCREEN.scanY + - (float_t)FONT_DEFAULT.tileset->tileHeight * 3 - ); - - errorOk(); -} - -errorret_t uiCutsceneDraw() { - const cutsceneitem_t *item = cutsceneSystemGetCurrentItem(); - if(!item || !CUTSCENE_SYSTEM.scene) { - stringCopy( - UICUTSCENE.countText, "Cutscene Inactive", UI_CUTSCENE_TEXT_MAX - 1 - ); - UICUTSCENE.itemText[0] = '\0'; - } else { - const char_t *name = CUTSCENE_ITEM_CALLBACKS[item->type].jsonName; - if(!name) name = "???"; - - stringFormat( - UICUTSCENE.countText, - UI_CUTSCENE_TEXT_MAX - 1, - "Cutscene Item Count: %d", - (int_t)CUTSCENE_SYSTEM.scene->itemCount - ); - stringFormat( - UICUTSCENE.itemText, - UI_CUTSCENE_TEXT_MAX - 1, - "Current Item: %s", - name - ); - } - UICUTSCENE.countLabel.dirty = true; - UICUTSCENE.itemLabel.dirty = true; - - errorChain(uiLabelRender(&UICUTSCENE.countLabel, COLOR_GREEN)); - errorChain(uiLabelRender(&UICUTSCENE.itemLabel, COLOR_GREEN)); - return spriteBatchFlush(); -} diff --git a/src/dusk/ui/debug/uicutscene.h b/src/dusk/ui/debug/uicutscene.h deleted file mode 100644 index 07fa9441..00000000 --- a/src/dusk/ui/debug/uicutscene.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uilabel.h" - -#define UI_CUTSCENE_TEXT_MAX 64 -#define UI_CUTSCENE_SPRITES_MAX UI_CUTSCENE_TEXT_MAX - -typedef struct { - uilabel_t countLabel; - char_t countText[UI_CUTSCENE_TEXT_MAX]; - spritebatchsprite_t countSprites[UI_CUTSCENE_SPRITES_MAX]; - - uilabel_t itemLabel; - char_t itemText[UI_CUTSCENE_TEXT_MAX]; - spritebatchsprite_t itemSprites[UI_CUTSCENE_SPRITES_MAX]; -} uicutscene_t; - -extern uicutscene_t UICUTSCENE; - -/** - * Initializes the cutscene debug labels. - * - * @return Any error that occurs. - */ -errorret_t uiCutsceneInit(); - -/** - * Draws the currently running cutscene's item count and current item - * type as two lines ("Item Count: %d" / "Current Item: %s"), or a - * single "Cutscene Inactive" line if no cutscene is running. Renders - * below UIPLAYERPOS (see ui/debug/uiplayerpos.h). - * - * @return Any error that occurs. - */ -errorret_t uiCutsceneDraw(); diff --git a/src/dusk/ui/debug/uifps.c b/src/dusk/ui/debug/uifps.c deleted file mode 100644 index d5df8ac5..00000000 --- a/src/dusk/ui/debug/uifps.c +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uifps.h" -#include "time/time.h" -#include "util/string.h" -#include "display/spritebatch/spritebatch.h" -#include "display/color.h" -#include "display/screen/screen.h" -#include "engine/engine.h" - -uifps_t UIFPS; - -errorret_t uiFPSInit() { - uiLabelInit( - &UIFPS.fpsLabel, - UIFPS.fpsText, - UIFPS.fpsSprites, UI_FPS_SPRITES_MAX - ); - uiLabelSetX(&UIFPS.fpsLabel, (float_t)SCREEN.scanX); - uiLabelSetY(&UIFPS.fpsLabel, (float_t)SCREEN.scanY); - - uiLabelInit( - &UIFPS.versionLabel, - UIFPS.versionText, - UIFPS.versionSprites, UI_FPS_VERSION_SPRITES_MAX - ); - stringCopy(UIFPS.versionText, ENGINE.version, UI_FPS_VERSION_TEXT_MAX - 1); - UIFPS.versionLabel.dirty = true; - uiLabelRebuffer(&UIFPS.versionLabel); - uiLabelSetX(&UIFPS.versionLabel, (float_t)( - SCREEN.scanX + SCREEN.scanWidth - UIFPS.versionLabel.width - )); - uiLabelSetY(&UIFPS.versionLabel, (float_t)( - SCREEN.scanY + SCREEN.scanHeight - UIFPS.versionLabel.height - )); - - errorOk(); -} - -errorret_t uiFPSDraw() { - // Get now. - dusktimeepoch_t now = timeGetEpoch(); - double_t delta = now.time - UIFPS.lastTick.time; - UIFPS.lastTick = now; - - // Raw current FPS - float_t fps = delta > 0 ? 1.0 / delta : 0.0; - - // Average FPS using exponential moving average - const float_t alpha = 0.1f; // Smoothing factor - if(UIFPS.fpsAverage == 0.0f) { - UIFPS.fpsAverage = fps; // Initialize average on first run - } else { - UIFPS.fpsAverage = alpha * fps + (1.0f - alpha) * UIFPS.fpsAverage; - } - - stringFormat( - UIFPS.fpsText, - UI_FPS_TEXT_MAX - 1, - "%.1f/%.1fms", - UIFPS.fpsAverage, - delta * 1000.0f - ); - UIFPS.fpsLabel.dirty = true; - - color_t textColor; - if(fps >= 55.0f) { - textColor = COLOR_GREEN; - } else if(fps >= 45.0f) { - textColor = COLOR_YELLOW; - } else { - textColor = COLOR_RED; - } - - errorChain(uiLabelRender(&UIFPS.fpsLabel, textColor)); - errorChain(spriteBatchFlush()); - - errorChain(uiLabelRender(&UIFPS.versionLabel, color(255, 255, 255, 128))); - - return spriteBatchFlush(); -} diff --git a/src/dusk/ui/debug/uifps.h b/src/dusk/ui/debug/uifps.h deleted file mode 100644 index fda1345b..00000000 --- a/src/dusk/ui/debug/uifps.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "time/timeepoch.h" -#include "ui/widget/uilabel.h" - -#define UI_FPS_TEXT_MAX 32 -#define UI_FPS_SPRITES_MAX UI_FPS_TEXT_MAX -#define UI_FPS_VERSION_TEXT_MAX 32 -#define UI_FPS_VERSION_SPRITES_MAX UI_FPS_VERSION_TEXT_MAX - -typedef struct { - dusktimeepoch_t lastTick; - float_t fpsAverage; - - uilabel_t fpsLabel; - char_t fpsText[UI_FPS_TEXT_MAX]; - spritebatchsprite_t fpsSprites[UI_FPS_SPRITES_MAX]; - - uilabel_t versionLabel; - char_t versionText[UI_FPS_VERSION_TEXT_MAX]; - spritebatchsprite_t versionSprites[UI_FPS_VERSION_SPRITES_MAX]; -} uifps_t; - -extern uifps_t UIFPS; - -/** - * Initializes the FPS counter's labels. - * - * @return Any error that occurs. - */ -errorret_t uiFPSInit(); - -/** - * Draws the FPS counter on the screen, and also does the update (for now). - * - * @return Any error that occurs. - */ -errorret_t uiFPSDraw(); \ No newline at end of file diff --git a/src/dusk/ui/debug/uiplayerpos.c b/src/dusk/ui/debug/uiplayerpos.c deleted file mode 100644 index ce7972c2..00000000 --- a/src/dusk/ui/debug/uiplayerpos.c +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiplayerpos.h" -#include "util/string.h" -#include "display/screen/screen.h" -#include "display/text/text.h" -#include "display/spritebatch/spritebatch.h" -#include "rpg/entity/entity.h" -#include "rpg/entity/entitytype.h" -#include "rpg/overworld/worldpos.h" -#include "rpg/overworld/map.h" - -uiplayerpos_t UIPLAYERPOS; - -errorret_t uiPlayerPosInit() { - uiLabelInit( - &UIPLAYERPOS.label, - UIPLAYERPOS.text, - UIPLAYERPOS.sprites, UI_PLAYERPOS_SPRITES_MAX - ); - uiLabelSetX(&UIPLAYERPOS.label, (float_t)SCREEN.scanX); - uiLabelSetY(&UIPLAYERPOS.label, (float_t)SCREEN.scanY + - (float_t)FONT_DEFAULT.tileset->tileHeight - ); - - errorOk(); -} - -errorret_t uiPlayerPosDraw() { - entity_t *player = NULL; - for(uint8_t i = 0; i < ENTITY_COUNT; i++) { - if(ENTITIES[i].type == ENTITY_TYPE_PLAYER) { - player = &ENTITIES[i]; - break; - } - } - if(!player) errorOk(); - - chunkpos_t chunkPos; - worldPosToChunkPos(&player->position, &chunkPos); - - stringFormat( - UIPLAYERPOS.text, - UI_PLAYERPOS_TEXT_MAX - 1, - "%s %d,%d,%d[%d,%d,%d]#%d", - MAP.name, - (int_t)player->position.x, - (int_t)player->position.y, - (int_t)player->position.z, - (int_t)chunkPos.x, - (int_t)chunkPos.y, - (int_t)chunkPos.z, - (int_t)player->chunkIndex - ); - UIPLAYERPOS.label.dirty = true; - - errorChain(uiLabelRender(&UIPLAYERPOS.label, COLOR_GREEN)); - return spriteBatchFlush(); -} diff --git a/src/dusk/ui/debug/uiplayerpos.h b/src/dusk/ui/debug/uiplayerpos.h deleted file mode 100644 index 1fb03ff7..00000000 --- a/src/dusk/ui/debug/uiplayerpos.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uilabel.h" - -#define UI_PLAYERPOS_TEXT_MAX 64 -#define UI_PLAYERPOS_SPRITES_MAX UI_PLAYERPOS_TEXT_MAX - -typedef struct { - uilabel_t label; - char_t text[UI_PLAYERPOS_TEXT_MAX]; - spritebatchsprite_t sprites[UI_PLAYERPOS_SPRITES_MAX]; -} uiplayerpos_t; - -extern uiplayerpos_t UIPLAYERPOS; - -/** - * Initializes the player position label. - * - * @return Any error that occurs. - */ -errorret_t uiPlayerPosInit(); - -/** - * Draws the current map name, player world position, chunk position, and - * chunk index on screen. Searches for the first ENTITY_TYPE_PLAYER and - * renders: MAPNAME WORLDX,WORLDY,WORLDZ[chunkx,chunky,chunkz]#chunkIndex - * - * @return Any error that occurs. - */ -errorret_t uiPlayerPosDraw(); diff --git a/src/dusk/ui/dialog/CMakeLists.txt b/src/dusk/ui/dialog/CMakeLists.txt deleted file mode 100644 index bfaab3ef..00000000 --- a/src/dusk/ui/dialog/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uiconfirm.c -) - -add_subdirectory(save) -add_subdirectory(keyboard) \ No newline at end of file diff --git a/src/dusk/ui/dialog/keyboard/CMakeLists.txt b/src/dusk/ui/dialog/keyboard/CMakeLists.txt deleted file mode 100644 index 9e5f2567..00000000 --- a/src/dusk/ui/dialog/keyboard/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uikeyboard.c -) diff --git a/src/dusk/ui/dialog/keyboard/uikeyboard.c b/src/dusk/ui/dialog/keyboard/uikeyboard.c deleted file mode 100644 index 8d56f69b..00000000 --- a/src/dusk/ui/dialog/keyboard/uikeyboard.c +++ /dev/null @@ -1,610 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uikeyboard.h" -#include "ui/widget/uiframe.h" -#include "ui/widget/uibutton.h" -#include "ui/dialog/uiconfirm.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "util/math.h" -#include "display/screen/screen.h" -#include "display/color.h" -#include "display/text/font.h" -#include "display/spritebatch/spritebatch.h" -#include "time/time.h" - -// Key cell size, expressed in font tiles so it scales if the font ever -// does. Row height is derived from this ratio in uiKeyboardDraw rather -// than being its own fixed constant, so a key cell keeps its proportions -// even when contentWidth is stretched wider by a long title/text label. -#define UI_KEYBOARD_KEY_WIDTH ((float_t)(FONT_DEFAULT_TILE_WIDTH * 4)) -#define UI_KEYBOARD_KEY_HEIGHT ((float_t)(FONT_DEFAULT_TILE_HEIGHT * 2)) - -uikeyboard_t UI_KEYBOARD; - -// String literals have static storage duration, so these stay valid for -// as long as the process runs - safe for uiButtonInit's non-copying -// label contract. "" cells are blank filler/reserved slots, NULL is -// shorthand for the same thing where there's no key at all - see -// uikeyboard.h and uiKeyboardNormalizeKey. -// -// Unshifted, caps off - also the canonical table (see -// uiKeyboardGetCanonicalKey). -static const char_t *const UI_KEYBOARD_KEYS_NONE[UI_KEYBOARD_KEY_COUNT] = { - // 1 2 3 4 5 6 7 8 9 0 DEL - "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", - UI_KEYBOARD_KEY_BACKSPACE, - - // q w e r t y u i o p - "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", NULL, - - // CAPS a s d f g h j k l (NEWLINE) - UI_KEYBOARD_KEY_CAPS, - "a", "s", "d", "f", "g", "h", "j", "k", "l", NULL, - - // SHIFT z x c v b n m SHIFT - UI_KEYBOARD_KEY_SHIFT, - NULL, - "z", "x", "c", "v", "b", "n", "m", - NULL, - UI_KEYBOARD_KEY_SHIFT, - - // (CANCEL) SPACE (CONFIRM) - NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL, - NULL -}; - -// Caps on - letters uppercase; caps doesn't affect digits/symbols on a -// physical keyboard, so this table is otherwise identical to _NONE. -static const char_t *const UI_KEYBOARD_KEYS_CAPS[UI_KEYBOARD_KEY_COUNT] = { - "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", - UI_KEYBOARD_KEY_BACKSPACE, - - "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", NULL, - - UI_KEYBOARD_KEY_CAPS, - "A", "S", "D", "F", "G", "H", "J", "K", "L", NULL, - - UI_KEYBOARD_KEY_SHIFT, - NULL, - "Z", "X", "C", "V", "B", "N", "M", - NULL, - UI_KEYBOARD_KEY_SHIFT, - - NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL, - NULL -}; - -// Shift held - letters uppercase, digits become their US-layout shift -// symbols, '-'/'=' become '_'/'+'. Takes priority over caps (see -// uiKeyboardGetChar). -static const char_t *const UI_KEYBOARD_KEYS_SHIFT[UI_KEYBOARD_KEY_COUNT] = { - "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", - UI_KEYBOARD_KEY_BACKSPACE, - - "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", NULL, - - UI_KEYBOARD_KEY_CAPS, - "A", "S", "D", "F", "G", "H", "J", "K", "L", NULL, - - UI_KEYBOARD_KEY_SHIFT, - NULL, - "Z", "X", "C", "V", "B", "N", "M", - NULL, - UI_KEYBOARD_KEY_SHIFT, - - NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL, - NULL -}; - -// Collapses a table entry's NULL ("no key here") down to "" (blank -// filler), the only form of "nothing" callers need to handle. -const char_t * uiKeyboardNormalizeKey(const char_t *key) { - return key != NULL ? key : ""; -} - -// Shift takes priority over caps - there's no fourth table for "shift -// while caps is on", unlike a physical keyboard. -const char_t *const * uiKeyboardSelectTable( - const bool_t shift, - const bool_t caps -) { - if(shift) return UI_KEYBOARD_KEYS_SHIFT; - if(caps) return UI_KEYBOARD_KEYS_CAPS; - return UI_KEYBOARD_KEYS_NONE; -} - -uint8_t uiKeyboardBuildItems( - uimenuitem_t *items, - const bool_t shift, - const bool_t caps -) { - assertNotNull(items, "Items cannot be NULL"); - const char_t *const *table = uiKeyboardSelectTable(shift, caps); - - for(uint8_t i = 0; i < UI_KEYBOARD_KEY_COUNT; i++) { - items[i].type = UI_MENU_WIDGET_TYPE_BUTTON; - uiButtonInit(&items[i].button, uiKeyboardNormalizeKey(table[i])); - } - - return UI_KEYBOARD_KEY_COUNT; -} - -void uiKeyboardUpdateDisplay( - uimenuitem_t *items, - const bool_t shift, - const bool_t caps -) { - assertNotNull(items, "Items cannot be NULL"); - const char_t *const *table = uiKeyboardSelectTable(shift, caps); - - for(uint8_t i = 0; i < UI_KEYBOARD_KEY_COUNT; i++) { - // These reserved slots are blank in every shift/caps table - their - // real labels (NEWLINE/CANCEL/CONFIRM) are patched in once at open - // time and aren't affected by shift/caps, so leave them alone here. - if( - i == UI_KEYBOARD_NEWLINE_INDEX || - i == UI_KEYBOARD_CANCEL_INDEX || - i == UI_KEYBOARD_CONFIRM_INDEX - ) continue; - - items[i].button.label.text = uiKeyboardNormalizeKey(table[i]); - items[i].button.label.dirty = true; - - const char_t *canonical = uiKeyboardGetCanonicalKey(i); - if(stringCompare(canonical, UI_KEYBOARD_KEY_CAPS) == 0) { - uiButtonSetActive(&items[i].button, caps); - } else if(stringCompare(canonical, UI_KEYBOARD_KEY_SHIFT) == 0) { - uiButtonSetActive(&items[i].button, shift); - } - } -} - -const char_t * uiKeyboardGetCanonicalKey(const uint8_t index) { - assertTrue(index < UI_KEYBOARD_KEY_COUNT, "Index out of range"); - return uiKeyboardNormalizeKey(UI_KEYBOARD_KEYS_NONE[index]); -} - -bool_t uiKeyboardIsShiftSensitive(const uint8_t index) { - assertTrue(index < UI_KEYBOARD_KEY_COUNT, "Index out of range"); - return stringCompare( - uiKeyboardNormalizeKey(UI_KEYBOARD_KEYS_NONE[index]), - uiKeyboardNormalizeKey(UI_KEYBOARD_KEYS_SHIFT[index]) - ) != 0; -} - -char_t uiKeyboardGetChar( - const uint8_t index, - const bool_t shift, - const bool_t caps -) { - assertTrue(index < UI_KEYBOARD_KEY_COUNT, "Index out of range"); - const char_t *const *table = uiKeyboardSelectTable(shift, caps); - return uiKeyboardNormalizeKey(table[index])[0]; -} - -void uiKeyboardFocusConfirm(void) { - uiMenuSetPosition( - &UI_KEYBOARD.menu, - UI_KEYBOARD_CONFIRM_INDEX % UI_KEYBOARD_COLUMNS, - UI_KEYBOARD_CONFIRM_INDEX / UI_KEYBOARD_COLUMNS - ); -} - -bool_t uiKeyboardTextIsBlank(void) { - for(const char_t *c = UI_KEYBOARD.text; *c != '\0'; c++) { - if(!stringIsWhitespace(*c)) return false; - } - return true; -} - -void uiKeyboardAppendChar(const char_t c) { - size_t length = strlen(UI_KEYBOARD.text); - - // Full - drop the last character instead of ignoring the keypress, so - // the new one overrides it. Falls through to the normal append below, - // which now has room again. - if(length >= UI_KEYBOARD.maxLength) { - length--; - UI_KEYBOARD.text[length] = '\0'; - } - - if(c == '\n') { - uint8_t lines = 1; - for(size_t i = 0; i < length; i++) { - if(UI_KEYBOARD.text[i] == '\n') lines++; - } - if(lines >= UI_KEYBOARD.lineCount) return; - } - - UI_KEYBOARD.text[length] = c; - UI_KEYBOARD.text[length + 1] = '\0'; - UI_KEYBOARD.textLabel.dirty = true; - uiLabelRebuffer(&UI_KEYBOARD.textLabel); - - // Nothing more can be typed - jump focus to confirm so accepting is a - // single button press away. - if(length + 1 >= UI_KEYBOARD.maxLength) uiKeyboardFocusConfirm(); -} - -void uiKeyboardBackspace(void) { - size_t length = strlen(UI_KEYBOARD.text); - if(length == 0) return; - - UI_KEYBOARD.text[length - 1] = '\0'; - UI_KEYBOARD.textLabel.dirty = true; - uiLabelRebuffer(&UI_KEYBOARD.textLabel); -} - -void uiKeyboardMenuClosed(const uimenu_t *menu) { - if(UI_KEYBOARD.onInput != NULL) { - UI_KEYBOARD.onInput( - UI_KEYBOARD.text, UI_KEYBOARD.confirmed, UI_KEYBOARD.user - ); - } -} - -bool_t uiKeyboardMenuCancel(const uimenu_t *menu) { - if(strlen(UI_KEYBOARD.text) > 0) { - uiKeyboardBackspace(); - return true; - } - - // Nothing left to delete - close, but only if this dialog is even - // allowed to be cancelled. Closed explicitly (rather than returning - // false to fall through to uiFocusUpdate's default pop) since that - // path only pops the focus stack - it never clears UI_KEYBOARD.open, - // which would leave uiKeyboardDraw drawing a focus-less dialog forever - // and the next uiKeyboardOpen() tripping its already-open assert. - if(!UI_KEYBOARD.cancel) return true; - - uiKeyboardDoCancel(); - return true; -} - -void uiKeyboardConfirmSecondaryResult(const bool_t result, void *user) { - if(!result) return; - - UI_KEYBOARD.confirmed = true; - uiKeyboardClose(); -} - -void uiKeyboardDoCancel(void) { - if(UI_KEYBOARD.cancelConfirm) { - uiConfirmOpen( - UI_KEYBOARD.cancelConfirmQuestion, - uiKeyboardCancelConfirmSecondaryResult, NULL - ); - return; - } - - UI_KEYBOARD.confirmed = false; - uiKeyboardClose(); -} - -void uiKeyboardCancelConfirmSecondaryResult(const bool_t result, void *user) { - if(!result) return; - - UI_KEYBOARD.confirmed = false; - uiKeyboardClose(); -} - -void uiKeyboardMenuSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - if(index == UI_KEYBOARD_CONFIRM_INDEX) { - if(UI_KEYBOARD.trimmed) { - stringTrim(UI_KEYBOARD.text); - UI_KEYBOARD.textLabel.dirty = true; - uiLabelRebuffer(&UI_KEYBOARD.textLabel); - } - if(!UI_KEYBOARD.allowBlank && uiKeyboardTextIsBlank()) return; - - if(UI_KEYBOARD.confirm) { - uiConfirmOpen( - UI_KEYBOARD.confirmQuestion, uiKeyboardConfirmSecondaryResult, NULL - ); - return; - } - - UI_KEYBOARD.confirmed = true; - uiKeyboardClose(); - return; - } - - if(UI_KEYBOARD.cancel && index == UI_KEYBOARD_CANCEL_INDEX) { - uiKeyboardDoCancel(); - return; - } - - if(index == UI_KEYBOARD_NEWLINE_INDEX && UI_KEYBOARD.lineCount > 1) { - if( - UI_KEYBOARD.onKeyPress != NULL && - !UI_KEYBOARD.onKeyPress('\n', UI_KEYBOARD.user) - ) return; - uiKeyboardAppendChar('\n'); - return; - } - - // The canonical (unshifted) key, independent of whatever character - // it's currently drawn as - see uiKeyboardGetCanonicalKey. Used to - // identify the key, not item->button.label.text, which changes with - // shift/caps. - const char_t *key = uiKeyboardGetCanonicalKey(index); - - // Blank filler/reserved cell (includes an unused cancel/newline slot). - if(key[0] == '\0') return; - - if(stringCompare(key, UI_KEYBOARD_KEY_BACKSPACE) == 0) { - uiKeyboardBackspace(); - return; - } - - if(stringCompare(key, UI_KEYBOARD_KEY_SHIFT) == 0) { - UI_KEYBOARD.shift = !UI_KEYBOARD.shift; - uiKeyboardUpdateDisplay( - UI_KEYBOARD.items, UI_KEYBOARD.shift, UI_KEYBOARD.caps - ); - return; - } - - if(stringCompare(key, UI_KEYBOARD_KEY_CAPS) == 0) { - UI_KEYBOARD.caps = !UI_KEYBOARD.caps; - uiKeyboardUpdateDisplay( - UI_KEYBOARD.items, UI_KEYBOARD.shift, UI_KEYBOARD.caps - ); - return; - } - - char_t c; - if(stringCompare(key, UI_KEYBOARD_KEY_SPACE) == 0) { - c = ' '; - } else { - c = uiKeyboardGetChar(index, UI_KEYBOARD.shift, UI_KEYBOARD.caps); - - // Shift is a one-shot, consumed by any letter/digit/-/= it actually - // affected (and the display refreshed to drop back to whatever caps - // alone says); caps is a persistent toggle. - if(UI_KEYBOARD.shift && uiKeyboardIsShiftSensitive(index)) { - UI_KEYBOARD.shift = false; - uiKeyboardUpdateDisplay( - UI_KEYBOARD.items, UI_KEYBOARD.shift, UI_KEYBOARD.caps - ); - } - } - - if( - UI_KEYBOARD.onKeyPress != NULL && - !UI_KEYBOARD.onKeyPress(c, UI_KEYBOARD.user) - ) { - return; - } - uiKeyboardAppendChar(c); -} - -errorret_t uiKeyboardInit(void) { - memoryZero(&UI_KEYBOARD, sizeof(uikeyboard_t)); - - uiLabelInit( - &UI_KEYBOARD.titleLabel, UI_KEYBOARD.titleText, - UI_KEYBOARD.titleSprites, UI_KEYBOARD_TITLE_SPRITES_MAX - ); - uiLabelInit( - &UI_KEYBOARD.textLabel, UI_KEYBOARD.text, - UI_KEYBOARD.textSprites, UI_KEYBOARD_TEXT_SPRITES_MAX - ); - uiLabelInit( - &UI_KEYBOARD.cursorLabel, UI_KEYBOARD_CURSOR_TEXT, - UI_KEYBOARD.cursorSprites, UI_KEYBOARD_CURSOR_SPRITES_MAX - ); - UI_KEYBOARD.cursorLabel.dirty = true; - - errorOk(); -} - -bool_t uiKeyboardIsOpen(void) { - return UI_KEYBOARD.open; -} - -void uiKeyboardOpen(const uikeyboardopen_t *open) { - assertNotNull(open, "Open parameters cannot be NULL"); - assertFalse(UI_KEYBOARD.open, "Keyboard is already open"); - assertTrue( - open->maxLength <= UI_KEYBOARD_TEXT_MAX - 1, - "maxLength exceeds UI_KEYBOARD_TEXT_MAX" - ); - assertTrue( - open->lineCount <= UI_KEYBOARD_LINE_COUNT_MAX, - "lineCount exceeds UI_KEYBOARD_LINE_COUNT_MAX" - ); - - stringCopy( - UI_KEYBOARD.titleText, - open->title != NULL ? open->title : UI_KEYBOARD_TITLE_DEFAULT, - UI_KEYBOARD_TITLE_TEXT_MAX - 1 - ); - UI_KEYBOARD.titleLabel.dirty = true; - uiLabelRebuffer(&UI_KEYBOARD.titleLabel); - - UI_KEYBOARD.text[0] = '\0'; - UI_KEYBOARD.textLabel.dirty = true; - uiLabelRebuffer(&UI_KEYBOARD.textLabel); - - UI_KEYBOARD.onInput = open->onInput; - UI_KEYBOARD.onKeyPress = open->onKeyPress; - UI_KEYBOARD.cancel = open->cancel; - UI_KEYBOARD.maxLength = open->maxLength == 0 ? - UI_KEYBOARD_TEXT_MAX - 1 : open->maxLength; - UI_KEYBOARD.lineCount = open->lineCount == 0 ? 1 : open->lineCount; - UI_KEYBOARD.confirm = open->confirm; - stringCopy( - UI_KEYBOARD.confirmQuestion, - open->confirmLabel != NULL ? - open->confirmLabel : UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT, - UI_KEYBOARD_CONFIRM_QUESTION_MAX - 1 - ); - UI_KEYBOARD.trimmed = open->trimmed; - UI_KEYBOARD.allowBlank = open->allowBlank; - UI_KEYBOARD.user = open->user; - UI_KEYBOARD.confirmed = false; - UI_KEYBOARD.caps = false; - UI_KEYBOARD.shift = false; - UI_KEYBOARD.open = true; - - // Builds the full fixed grid, including blank filler at the - // newline/confirm/cancel reserved slots - patched with real buttons - // below where applicable. Letters start lowercase and digits - // unshifted, matching caps/shift both being reset to false above. - uiKeyboardBuildItems(UI_KEYBOARD.items, false, false); - - if(UI_KEYBOARD.lineCount > 1) { - uiButtonInit( - &UI_KEYBOARD.items[UI_KEYBOARD_NEWLINE_INDEX].button, - UI_KEYBOARD_KEY_NEWLINE - ); - } - - uiButtonInit( - &UI_KEYBOARD.items[UI_KEYBOARD_CONFIRM_INDEX].button, - UI_KEYBOARD_CONFIRM_LABEL - ); - - if(UI_KEYBOARD.cancel) { - uiButtonInit( - &UI_KEYBOARD.items[UI_KEYBOARD_CANCEL_INDEX].button, - UI_KEYBOARD_CANCEL_LABEL - ); - } - - uiMenuInit( - &UI_KEYBOARD.menu, uiKeyboardMenuSelected, uiKeyboardMenuClosed, NULL - ); - uiMenuSetItems( - &UI_KEYBOARD.menu, UI_KEYBOARD.items, UI_KEYBOARD_KEY_COUNT, - UI_KEYBOARD_COLUMNS - ); - - // Back deletes a character rather than closing the dialog outright - - // see uiKeyboardMenuCancel. - uiMenuSetCancelCallback(&UI_KEYBOARD.menu, uiKeyboardMenuCancel); - uiMenuOpen(&UI_KEYBOARD.menu); -} - -void uiKeyboardClose(void) { - if(!UI_KEYBOARD.open) return; - UI_KEYBOARD.open = false; - - if(uiMenuIsActive(&UI_KEYBOARD.menu)) { - uiMenuClose(&UI_KEYBOARD.menu); - } else { - uiKeyboardMenuClosed(NULL); - } -} - -errorret_t uiKeyboardDraw(void) { - if(!UI_KEYBOARD.open) errorOk(); - - float_t x = (float_t)SCREEN.scanX; - float_t y = (float_t)SCREEN.scanY; - float_t width = (float_t)SCREEN.scanWidth; - float_t height = (float_t)SCREEN.scanHeight; - - errorChain(uiFrameDraw(x, y, width, height)); - - float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; - // When multi-line, reserve the full lineCount rows up front so the - // dialog doesn't resize as newlines are typed - otherwise fall back to - // the label's own measured height (floored at one row for an empty - // string, which measures to zero). - float_t textHeight = UI_KEYBOARD.lineCount > 1 ? - (float_t)UI_KEYBOARD.lineCount * rowHeight : - mathMax(rowHeight, (float_t)UI_KEYBOARD.textLabel.height); - uint8_t columns = UI_KEYBOARD.menu.columns; - uint8_t rows = (UI_KEYBOARD.menu.itemCount + columns - 1) / columns; - - float_t contentWidth = mathMax( - mathMax( - (float_t)UI_KEYBOARD.titleLabel.width, - (float_t)UI_KEYBOARD.textLabel.width - ), - (float_t)columns * UI_KEYBOARD_KEY_WIDTH - ); - float_t colStep = contentWidth / (float_t)columns; - float_t keyRowHeight = - colStep * (UI_KEYBOARD_KEY_HEIGHT / UI_KEYBOARD_KEY_WIDTH); - float_t contentHeight = rowHeight + UI_FRAME_PADDING_Y - + textHeight + UI_FRAME_PADDING_Y - + ((float_t)rows * keyRowHeight); - - // The frame now fills the whole screen, but the title/text/keys still - // lay out as one content block - center that block within the screen. - float_t contentX = x + (width - contentWidth) * 0.5f; - float_t contentY = y + (height - contentHeight) * 0.5f; - - uiLabelSetX(&UI_KEYBOARD.titleLabel, contentX); - uiLabelSetY(&UI_KEYBOARD.titleLabel, contentY); - errorChain(uiLabelRender(&UI_KEYBOARD.titleLabel, COLOR_WHITE)); - - float_t textY = contentY + rowHeight + UI_FRAME_PADDING_Y; - uiLabelSetX(&UI_KEYBOARD.textLabel, contentX); - uiLabelSetY(&UI_KEYBOARD.textLabel, textY); - errorChain(uiLabelRender(&UI_KEYBOARD.textLabel, COLOR_WHITE)); - - // Insertion point is always the end of text - there's no mid-string - // cursor movement - so find it by counting characters since the last - // newline (column) and newlines overall (line). Once maxLength is - // reached, a new key overrides the last character (see - // uiKeyboardAppendChar) rather than appending one, so stop one - // character short here too - the cursor should sit on that last - // character, not past it, to reflect what typing will actually do. - size_t textLength = strlen(UI_KEYBOARD.text); - size_t cursorLimit = textLength >= UI_KEYBOARD.maxLength ? - textLength - 1 : textLength; - - uint8_t cursorLine = 0; - uint8_t cursorCol = 0; - for(size_t i = 0; i < cursorLimit; i++) { - if(UI_KEYBOARD.text[i] == '\n') { cursorLine++; cursorCol = 0; } - else cursorCol++; - } - - if(mathModFloat(TIME.time, UI_KEYBOARD_CURSOR_BLINK_RATE * 2.0f) < - UI_KEYBOARD_CURSOR_BLINK_RATE - ) { - uiLabelSetX( - &UI_KEYBOARD.cursorLabel, - contentX + (float_t)cursorCol * (float_t)FONT_DEFAULT_TILE_WIDTH - ); - uiLabelSetY( - &UI_KEYBOARD.cursorLabel, textY + (float_t)cursorLine * rowHeight - ); - errorChain(uiLabelRender(&UI_KEYBOARD.cursorLabel, COLOR_WHITE)); - } - - // Drawn directly rather than via uiMenuDraw - that helper hardcodes row - // spacing to the font's native tile height, which would keep the grid - // vertically compact no matter how much contentWidth grows. Focus - // highlighting is unaffected: it's applied to item->button.highlighted - // by the focus system independently of whoever calls uiButtonDraw. - float_t menuY = textY + textHeight + UI_FRAME_PADDING_Y; - for(uint8_t i = 0; i < UI_KEYBOARD.menu.itemCount; i++) { - float_t ix = contentX + (float_t)(i % columns) * colStep; - float_t iy = menuY + (float_t)(i / columns) * keyRowHeight; - errorChain(uiButtonDraw(&UI_KEYBOARD.items[i].button, ix, iy)); - } - - errorChain(spriteBatchFlush()); - errorOk(); -} - -errorret_t uiKeyboardDispose(void) { - errorOk(); -} diff --git a/src/dusk/ui/dialog/keyboard/uikeyboard.h b/src/dusk/ui/dialog/keyboard/uikeyboard.h deleted file mode 100644 index 565d7cba..00000000 --- a/src/dusk/ui/dialog/keyboard/uikeyboard.h +++ /dev/null @@ -1,490 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uilabel.h" -#include "ui/widget/uimenu.h" -#include "display/text/font.h" - -// A fixed 11x5 QWERTY grid - the only layout that exists (numbers/ -// symbols layouts were removed as separate concepts; digits already -// live on this one): -// 1 2 3 4 5 6 7 8 9 0 DEL -// q w e r t y u i o p -// CAPS a s d f g h j k l (NEWLINE) -// SHIFT z x c v b n m SHIFT -// (CANCEL) SPACE (CONFIRM) -// Rows shorter than 11 columns are padded with blank filler buttons -// (empty label) so every key lands in its intended position - the menu/ -// focus grid only supports uniform columns, it has no concept of a key -// spanning multiple cells or a row being narrower than the grid, so this -// is the only way to get real positioning out of it. A filler cell is -// still a focusable/navigable-to grid slot, it just does nothing when -// selected - see uiKeyboardMenuSelected's blank-label check. A -// UI_KEYBOARD_KEYS_* table may use NULL for a slot with no key at all - -// see uiKeyboardGetCanonicalKey, which normalizes it to the same blank -// filler behavior. -// -// NEWLINE/CANCEL/CONFIRM are reserved slots the tables always leave -// blank - uiKeyboardOpen patches them with real buttons depending on -// lineCount/cancel (CONFIRM is unconditional, just needed as a fixed -// slot addressable by constant). -#define UI_KEYBOARD_COLUMNS 11 -#define UI_KEYBOARD_ROWS 5 -#define UI_KEYBOARD_KEY_COUNT (UI_KEYBOARD_COLUMNS * UI_KEYBOARD_ROWS) - -// Reserved slot indices uiKeyboardOpen patches with real buttons. -#define UI_KEYBOARD_NEWLINE_INDEX 32 -#define UI_KEYBOARD_CANCEL_INDEX 44 -#define UI_KEYBOARD_CONFIRM_INDEX 54 - -// Labels uiKeyboardMenuSelected checks for by pointer/string compare to -// tell special keys apart from an ordinary single-character key. A blank -// ("") label is a filler cell - see above. Identical across every -// UI_KEYBOARD_KEYS_* table, since these don't change with shift/caps. -#define UI_KEYBOARD_KEY_SPACE FONT_ICON_SPACE -#define UI_KEYBOARD_KEY_BACKSPACE FONT_ICON_BACKSPACE -#define UI_KEYBOARD_KEY_SHIFT FONT_ICON_SHIFT -#define UI_KEYBOARD_KEY_CAPS FONT_ICON_CAPSLOCK -// Only patched into UI_KEYBOARD_NEWLINE_INDEX when -// uikeyboardopen_t.lineCount > 1 - otherwise that slot stays blank. -#define UI_KEYBOARD_KEY_NEWLINE FONT_ICON_NEWLINE - -#define UI_KEYBOARD_TITLE_TEXT_MAX 64 -#define UI_KEYBOARD_TITLE_SPRITES_MAX UI_KEYBOARD_TITLE_TEXT_MAX -#define UI_KEYBOARD_TEXT_MAX 64 -#define UI_KEYBOARD_TEXT_SPRITES_MAX UI_KEYBOARD_TEXT_MAX -// The grid is a fixed size - only one layout exists today, so this is -// just its key count. -#define UI_KEYBOARD_ITEMS_MAX UI_KEYBOARD_KEY_COUNT - -// Hardcoded for now - a uiModalOpen-style title/button text override is -// planned (see uikeyboardopen_t) but not built yet. Patched into the -// grid's reserved slots (UI_KEYBOARD_CONFIRM_INDEX etc.) rather than -// appended, since the grid is a fixed shape. -#define UI_KEYBOARD_CONFIRM_LABEL "CONFIRM" -#define UI_KEYBOARD_CANCEL_LABEL "CANCEL" -#define UI_KEYBOARD_TITLE_DEFAULT "ENTER YOUR TEXT" - -#define UI_KEYBOARD_CONFIRM_QUESTION_MAX 64 -// Used when uikeyboardopen_t.confirmLabel is NULL. -#define UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT "Is this correct?" - -#define UI_KEYBOARD_CANCEL_QUESTION_MAX 64 -// Used when uikeyboardopen_t.cancelConfirmLabel is NULL. -#define UI_KEYBOARD_CANCEL_QUESTION_DEFAULT "Discard your changes?" - -// Maximum number of lines uikeyboardopen_t.lineCount may request. -#define UI_KEYBOARD_LINE_COUNT_MAX 4 - -// Marks where the next typed character will be inserted (always the end -// of the current text - there's no mid-string cursor movement). -#define UI_KEYBOARD_CURSOR_TEXT "_" -#define UI_KEYBOARD_CURSOR_SPRITES_MAX 1 -// Seconds per on/off half-cycle of the cursor blink. -#define UI_KEYBOARD_CURSOR_BLINK_RATE 0.5f - -/** - * Callback invoked once the keyboard dialog is dismissed. - * - * @param text The entered text. Only meaningful when confirmed is true; - * points at UI_KEYBOARD's own buffer, so copy it out if it needs to - * outlive the next uiKeyboardOpen call. - * @param confirmed True if the confirm button was picked, false if - * cancelled. - * @param user Arbitrary pointer passed via uikeyboardopen_t.user. - */ -typedef void (*uikeyboardinputcallback_t)( - const char_t *text, - const bool_t confirmed, - void *user -); - -/** - * Callback invoked before a key press is applied, giving the caller a - * chance to reject it - e.g. an email field rejecting "%". - * - * @param c The character about to be entered (SPACE included, DEL/ - * confirm/cancel excluded - those aren't text being entered). - * @param user Arbitrary pointer passed via uikeyboardopen_t.user. - * @returns True to allow the character, false to reject it (ignored, - * nothing is entered). - */ -typedef bool_t (*uikeyboardkeypresscallback_t)(const char_t c, void *user); - -/** - * Parameters for uiKeyboardOpen. Expect this to grow (e.g. button text - * overrides) as more dialogs start using the keyboard. - */ -typedef struct { - // Title text shown above the entered text; copied internally, safe to - // be transient. Displayed as-is - resolve any locale string yourself - // before passing it in. NULL uses UI_KEYBOARD_TITLE_DEFAULT ("ENTER - // YOUR TEXT"). - const char_t *title; - - // Fired once the dialog is dismissed, confirmed or cancelled. May be - // NULL. - uikeyboardinputcallback_t onInput; - - // Fired for each key press before it's applied. May be NULL to allow - // everything. - uikeyboardkeypresscallback_t onKeyPress; - - // Whether the cancel button is shown, and whether pressing back with - // no text entered closes the dialog - see uiKeyboardMenuCancel. - bool_t cancel; - - // Maximum length of the entered text, in characters excluding the - // null terminator. 0 defaults to the buffer's full capacity - // (UI_KEYBOARD_TEXT_MAX - 1). Must not exceed that. - size_t maxLength; - - // When true, picking confirm opens a second uiConfirm "are you sure" - // dialog (see confirmLabel) on top of the keyboard before actually - // closing it - picking Cancel there returns to the keyboard with the - // text untouched. - bool_t confirm; - - // Question text/locale message ID for the second confirm dialog when - // confirm is true; copied internally, safe to be transient. NULL uses - // UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT. Ignored when confirm is false. - const char_t *confirmLabel; - - // When true, actually cancelling (the CANCEL button, or back with - // nothing left to delete - see uiKeyboardMenuCancel) opens a second - // uiConfirm "are you sure" dialog (see cancelConfirmLabel) before the - // keyboard closes - picking Cancel there returns to the keyboard with - // the text untouched. Ignored when cancel is false, since there's - // nothing to confirm. - bool_t cancelConfirm; - - // Question text/locale message ID for the cancel confirm dialog when - // cancelConfirm is true; copied internally, safe to be transient. NULL - // uses UI_KEYBOARD_CANCEL_QUESTION_DEFAULT. Ignored when cancelConfirm - // is false. - const char_t *cancelConfirmLabel; - - // If true, leading/trailing whitespace is trimmed from the entered - // text when confirm is pressed, before it's checked against - // allowBlank or passed to onInput. - bool_t trimmed; - - // If true, a blank result is accepted when confirm is pressed - blank - // means an empty string, or (when trimmed is false) a string of only - // spaces. When false, pressing confirm does nothing while the text is - // blank - see uiKeyboardTextIsBlank. - bool_t allowBlank; - - // Maximum number of lines the entered text can span, from 1 to - // UI_KEYBOARD_LINE_COUNT_MAX. 0 defaults to 1. When > 1, a NEWLINE key - // is added to the grid, capped at inserting lineCount - 1 newlines. - uint8_t lineCount; - - // Arbitrary pointer passed to onInput/onKeyPress. - void *user; -} uikeyboardopen_t; - -typedef struct { - uilabel_t titleLabel; - char_t titleText[UI_KEYBOARD_TITLE_TEXT_MAX]; - spritebatchsprite_t titleSprites[UI_KEYBOARD_TITLE_SPRITES_MAX]; - - uilabel_t textLabel; - char_t text[UI_KEYBOARD_TEXT_MAX]; - spritebatchsprite_t textSprites[UI_KEYBOARD_TEXT_SPRITES_MAX]; - - // Blinking insertion-point marker, positioned each draw at the end of - // text - a separate label rather than appending to text/textSprites, - // since neither buffer has spare room for it (see - // UI_KEYBOARD_TEXT_MAX/UI_KEYBOARD_TEXT_SPRITES_MAX). - uilabel_t cursorLabel; - spritebatchsprite_t cursorSprites[UI_KEYBOARD_CURSOR_SPRITES_MAX]; - - uimenu_t menu; - uimenuitem_t items[UI_KEYBOARD_ITEMS_MAX]; - - char_t confirmQuestion[UI_KEYBOARD_CONFIRM_QUESTION_MAX]; - char_t cancelConfirmQuestion[UI_KEYBOARD_CANCEL_QUESTION_MAX]; - - uikeyboardinputcallback_t onInput; - uikeyboardkeypresscallback_t onKeyPress; - void *user; - size_t maxLength; - uint8_t lineCount; - bool_t cancel; - bool_t confirm; - bool_t cancelConfirm; - bool_t trimmed; - bool_t allowBlank; - bool_t open; - bool_t confirmed; - - // Persistent caps-lock toggle, and a one-shot shift consumed by the - // next letter/digit/-/= typed - see uiKeyboardMenuSelected. Key labels - // are redrawn to match (see uiKeyboardUpdateDisplay) rather than - // staying static. - bool_t caps; - bool_t shift; -} uikeyboard_t; - -extern uikeyboard_t UI_KEYBOARD; - -/** - * Initializes the on-screen keyboard. - * - * @return Any error that occurs. - */ -errorret_t uiKeyboardInit(void); - -/** - * Draws the on-screen keyboard: a semi-transparent black backdrop - * covering the whole screen, then its own centered frame with the title, - * currently entered text, and the key grid. No-op when not open. - * - * @return Any error that occurs. - */ -errorret_t uiKeyboardDraw(void); - -/** - * Returns true when the keyboard dialog is currently open. - * - * @returns True if open. - */ -bool_t uiKeyboardIsOpen(void); - -/** - * Opens the on-screen keyboard with an empty text buffer. - * - * @param open Parameters for this dialog; copied internally, safe to be - * transient. Cannot be NULL. - */ -void uiKeyboardOpen(const uikeyboardopen_t *open); - -/** - * Closes the keyboard dialog, invoking the result callback set by - * uiKeyboardOpen as cancelled (confirmed=false) unless the confirm - * button was what triggered this close. No-op when already closed. - */ -void uiKeyboardClose(void); - -/** - * Disposes of the on-screen keyboard. - * - * @return Any error that occurs. - */ -errorret_t uiKeyboardDispose(void); - -/** - * Appends a character to the currently entered text, silently ignoring - * it once UI_KEYBOARD.maxLength is reached, or (for '\n') once the text - * already has UI_KEYBOARD.lineCount - 1 newlines. Does not consult - * onKeyPress - that happens in uiKeyboardMenuSelected before this is - * called. Moves focus to the confirm button once this append fills the - * text to maxLength - see uiKeyboardFocusConfirm. - * - * @param c The character to append. - */ -void uiKeyboardAppendChar(const char_t c); - -/** - * Moves the menu's focus to the confirm button. - */ -void uiKeyboardFocusConfirm(void); - -/** - * Returns whether the currently entered text counts as blank for - * UI_KEYBOARD.allowBlank purposes - an empty string, or (when - * UI_KEYBOARD.trimmed is false, so whitespace-only text was never - * reduced to empty) a string of only whitespace. - * - * @returns True if blank. - */ -bool_t uiKeyboardTextIsBlank(void); - -/** - * Removes the last character of the currently entered text. No-op when - * already empty. - */ -void uiKeyboardBackspace(void); - -/** - * Internal menu callback - routes a picked key/confirm/cancel item to - * uiKeyboardAppendChar/uiKeyboardBackspace/uiKeyboardClose, consulting - * onKeyPress before a character key is applied. When UI_KEYBOARD.confirm - * is set, picking confirm opens a second uiConfirm dialog (see - * uiKeyboardConfirmSecondaryResult) instead of closing immediately. - * SHIFT/CAPS toggle UI_KEYBOARD.shift/caps instead of entering anything. - * A blank ("") label is a filler/reserved grid cell and is a no-op. - * - * @param menu The keyboard's menu. - * @param index Index of the picked item. - * @param item The picked item. - */ -void uiKeyboardMenuSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -); - -/** - * Result callback for the second "are you sure" uiConfirm dialog opened - * when UI_KEYBOARD.confirm is set - see uiKeyboardMenuSelected. - * - * @param result True if Confirm was picked there, false if Cancel. - * @param user Unused. - */ -void uiKeyboardConfirmSecondaryResult(const bool_t result, void *user); - -/** - * Actually cancels the keyboard - sets confirmed to false and closes the - * dialog. Shared by uiKeyboardMenuCancel (back with nothing to delete) - * and uiKeyboardMenuSelected (the CANCEL button); called directly when - * UI_KEYBOARD.cancelConfirm is false, or as the result callback (see - * uiKeyboardCancelConfirmSecondaryResult) once the "are you sure" dialog - * confirms it when cancelConfirm is true. - */ -void uiKeyboardDoCancel(void); - -/** - * Result callback for the "are you sure" uiConfirm dialog opened when - * UI_KEYBOARD.cancelConfirm is set - see uiKeyboardDoCancel. - * - * @param result True if Confirm was picked there, false if Cancel. - * @param user Unused. - */ -void uiKeyboardCancelConfirmSecondaryResult(const bool_t result, void *user); - -/** - * Internal menu callback - fires the result callback set by - * uiKeyboardOpen once the menu finishes closing. - * - * @param menu Unused; matches uimenuclosedcallback_t. - */ -void uiKeyboardMenuClosed(const uimenu_t *menu); - -/** - * Internal menu cancel callback (see uiMenuSetCancelCallback): back - * deletes the last entered character; with nothing left to delete, it - * closes the dialog as cancelled via uiKeyboardClose, but only when - * UI_KEYBOARD.cancel allows it - otherwise back is swallowed and does - * nothing. - * - * @param menu The keyboard's menu. - * @returns Always true - this always fully handles back itself - * (backspace, explicit close, or swallow), never falling through to the - * default pop. - */ -bool_t uiKeyboardMenuCancel(const uimenu_t *menu); - -/** - * Collapses a UI_KEYBOARD_KEYS_* table entry's NULL ("no key here") down - * to "" (blank filler), the only form of "nothing" callers need to - * handle. - * - * @param key A table entry, possibly NULL. - * @returns key, or "" if key was NULL. - */ -const char_t * uiKeyboardNormalizeKey(const char_t *key); - -/** - * Picks which of UI_KEYBOARD_KEYS_NONE/_CAPS/_SHIFT is active for the - * given state - shift takes priority over caps when both are set. - * - * @param shift Current shift state. - * @param caps Current caps state. - * @returns The selected table. - */ -const char_t *const * uiKeyboardSelectTable( - const bool_t shift, - const bool_t caps -); - -/** - * Builds the key grid's buttons into items, starting at index 0, one - * UI_MENU_WIDGET_TYPE_BUTTON per grid cell (UI_KEYBOARD_KEY_COUNT of - * them, in row-major order across UI_KEYBOARD_COLUMNS columns). Each - * button's label is a single character, one of the UI_KEYBOARD_KEY_* - * literals, or "" for a blank filler/reserved slot, read straight from - * whichever of the three UI_KEYBOARD_KEYS_* tables shift/caps selects - * (see uiKeyboardGetChar) - can be refreshed later with - * uiKeyboardUpdateDisplay. See uiKeyboardGetCanonicalKey for identifying - * a picked key independent of its displayed character. - * - * @param items Destination array; must have room for at least - * UI_KEYBOARD_KEY_COUNT entries. - * @param shift Initial shift state - see uiKeyboardGetChar. - * @param caps Initial caps state - see uiKeyboardGetChar. - * @returns The number of items written (UI_KEYBOARD_KEY_COUNT). - */ -uint8_t uiKeyboardBuildItems( - uimenuitem_t *items, - const bool_t shift, - const bool_t caps -); - -/** - * Repoints every key already built into items by uiKeyboardBuildItems at - * whichever UI_KEYBOARD_KEYS_* table the new shift/caps state selects, - * marking all of them dirty so they redraw. Unconditional (not just the - * letter/digit/-/= keys that actually differ between tables) - simpler - * than tracking which changed, and cheap since this only runs on an - * infrequent SHIFT/CAPS press. - * - * @param items The same array passed to uiKeyboardBuildItems. - * @param shift New shift state - see uiKeyboardGetChar. - * @param caps New caps state - see uiKeyboardGetChar. - */ -void uiKeyboardUpdateDisplay( - uimenuitem_t *items, - const bool_t shift, - const bool_t caps -); - -/** - * Returns a grid slot's canonical (unshifted, no-caps) key label, - * independent of whatever character it's currently drawn as - use this - * to identify which key was picked instead of reading a selected item's - * own (possibly shifted) label.text. - * - * @param index Grid slot index, from 0 to UI_KEYBOARD_KEY_COUNT - 1. - * @returns The slot's canonical label. - */ -const char_t * uiKeyboardGetCanonicalKey(const uint8_t index); - -/** - * True if a grid slot's character actually differs between the - * unshifted and shifted tables - i.e. a letter, digit, '-', or '='. False - * for a slot whose UI_KEYBOARD_KEYS_* entry is the same in every table - * (SPACE, DEL, SHIFT, CAPS, or blank filler). - * - * @param index Grid slot index, from 0 to UI_KEYBOARD_KEY_COUNT - 1. - * @returns True if shift affects this slot's character. - */ -bool_t uiKeyboardIsShiftSensitive(const uint8_t index); - -/** - * Looks up the character a grid slot represents under the given shift/ - * caps state, straight from whichever UI_KEYBOARD_KEYS_* table they - * select - shift takes priority over caps when both are set (so, unlike - * a physical keyboard, shift while caps is on still gives uppercase, not - * lowercase - there is no fourth table for that combination). For a slot - * whose label isn't a single character (a blank filler cell, or a - * multi-character special key like "SPACE"), returns that label's first - * byte ('\0' for blank). - * - * @param index Grid slot index, from 0 to UI_KEYBOARD_KEY_COUNT - 1. - * @param shift Current shift state (one-shot, see UI_KEYBOARD.shift). - * @param caps Current caps state (persistent toggle, see UI_KEYBOARD.caps). - * @returns The character to display/type. - */ -char_t uiKeyboardGetChar( - const uint8_t index, - const bool_t shift, - const bool_t caps -); diff --git a/src/dusk/ui/dialog/save/CMakeLists.txt b/src/dusk/ui/dialog/save/CMakeLists.txt deleted file mode 100644 index aae3928e..00000000 --- a/src/dusk/ui/dialog/save/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uisaveslot.c - uiselectsave.c -) diff --git a/src/dusk/ui/dialog/save/uisaveslot.c b/src/dusk/ui/dialog/save/uisaveslot.c deleted file mode 100644 index b4e90486..00000000 --- a/src/dusk/ui/dialog/save/uisaveslot.c +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uisaveslot.h" -#include "ui/widget/uiframe.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "display/color.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -void uiSaveSlotInit( - uisaveslot_t *saveSlot, - const uint8_t slotIndex, - const char_t *fileName -) { - assertNotNull(saveSlot, "Save slot cannot be NULL"); - assertNotNull(fileName, "File name cannot be NULL"); - - memoryZero(saveSlot, sizeof(uisaveslot_t)); - saveSlot->slotIndex = slotIndex; - - uiLabelInit( - &saveSlot->numberLabel, saveSlot->numberText, - saveSlot->numberSprites, UI_SAVE_SLOT_NUMBER_SPRITES_MAX - ); - errorret_t result = localeManagerGetText( - "ui.save_slot.number_format", - saveSlot->numberText, UI_SAVE_SLOT_NUMBER_TEXT_MAX, 0, - slotIndex + 1 - ); - if(errorIsNotOk(result)) errorCatch(result); - saveSlot->numberLabel.dirty = true; - - uiLabelInit( - &saveSlot->nameLabel, saveSlot->nameText, - saveSlot->nameSprites, UI_SAVE_SLOT_NAME_SPRITES_MAX - ); - stringCopy(saveSlot->nameText, fileName, UI_SAVE_SLOT_NAME_TEXT_MAX); - saveSlot->nameLabel.dirty = true; -} - -bool_t uiSaveSlotIsHighlighted(const uisaveslot_t *saveSlot) { - assertNotNull(saveSlot, "Save slot cannot be NULL"); - return saveSlot->highlighted; -} - -void uiSaveSlotSetHighlighted( - uisaveslot_t *saveSlot, const bool_t highlighted -) { - assertNotNull(saveSlot, "Save slot cannot be NULL"); - saveSlot->highlighted = highlighted; -} - -bool_t uiSaveSlotIsCorrupt(const uisaveslot_t *saveSlot) { - assertNotNull(saveSlot, "Save slot cannot be NULL"); - return saveSlot->corrupt; -} - -void uiSaveSlotSetCorrupt(uisaveslot_t *saveSlot, const bool_t corrupt) { - assertNotNull(saveSlot, "Save slot cannot be NULL"); - saveSlot->corrupt = corrupt; -} - -uisaveslotdisplaytype_t uiSaveSlotGetDisplayType(const uisaveslot_t *saveSlot) { - assertNotNull(saveSlot, "Save slot cannot be NULL"); - return saveSlot->displayType; -} - -void uiSaveSlotSetDisplayType( - uisaveslot_t *saveSlot, - const uisaveslotdisplaytype_t displayType -) { - assertNotNull(saveSlot, "Save slot cannot be NULL"); - saveSlot->displayType = displayType; -} - -errorret_t uiSaveSlotDraw( - uisaveslot_t *saveSlot, - const float_t x, - const float_t y, - const float_t width, - const float_t height -) { - assertNotNull(saveSlot, "Save slot cannot be NULL"); - - errorChain(uiFrameDraw(x, y, width, height)); - - const float_t contentX = x + UI_FRAME_START_X; - const float_t contentY = y + UI_FRAME_START_Y; - - color_t color; - if(saveSlot->corrupt) { - color = COLOR_RED; - } else if(saveSlot->highlighted) { - color = COLOR_RED; - } else if(saveSlot->displayType == UI_SAVE_SLOT_DISPLAY_TYPE_DELETE) { - color = COLOR_ORANGE; - } else { - color = COLOR_WHITE; - } - - uiLabelSetX(&saveSlot->numberLabel, contentX); - uiLabelSetY(&saveSlot->numberLabel, contentY); - errorChain(uiLabelRender(&saveSlot->numberLabel, color)); - - const float_t nameY = contentY + (float_t)saveSlot->numberLabel.height + - UI_FRAME_PADDING_Y; - uiLabelSetX(&saveSlot->nameLabel, contentX); - uiLabelSetY(&saveSlot->nameLabel, nameY); - errorChain(uiLabelRender(&saveSlot->nameLabel, color)); - - errorOk(); -} diff --git a/src/dusk/ui/dialog/save/uisaveslot.h b/src/dusk/ui/dialog/save/uisaveslot.h deleted file mode 100644 index 31b7ea43..00000000 --- a/src/dusk/ui/dialog/save/uisaveslot.h +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uilabel.h" - -#define UI_SAVE_SLOT_NUMBER_TEXT_MAX 16 -#define UI_SAVE_SLOT_NUMBER_SPRITES_MAX UI_SAVE_SLOT_NUMBER_TEXT_MAX -#define UI_SAVE_SLOT_NAME_TEXT_MAX 64 -#define UI_SAVE_SLOT_NAME_SPRITES_MAX UI_SAVE_SLOT_NAME_TEXT_MAX - -/** - * How a save slot entry should be styled: NORMAL for everyday - * save/load browsing, DELETE to visually flag the row as part of a - * destructive delete flow. - */ -typedef enum { - UI_SAVE_SLOT_DISPLAY_TYPE_NORMAL, - UI_SAVE_SLOT_DISPLAY_TYPE_DELETE -} uisaveslotdisplaytype_t; - -/** - * A single framed save slot entry: a slot number and a file name. Later - * this will grow to show more per-slot save data (level, playtime, - * etc). - */ -typedef struct { - uint8_t slotIndex; - uisaveslotdisplaytype_t displayType; - - uilabel_t numberLabel; - char_t numberText[UI_SAVE_SLOT_NUMBER_TEXT_MAX]; - spritebatchsprite_t numberSprites[UI_SAVE_SLOT_NUMBER_SPRITES_MAX]; - - uilabel_t nameLabel; - char_t nameText[UI_SAVE_SLOT_NAME_TEXT_MAX]; - spritebatchsprite_t nameSprites[UI_SAVE_SLOT_NAME_SPRITES_MAX]; - - bool_t highlighted; - bool_t corrupt; -} uisaveslot_t; - -/** - * Initializes a save slot widget with the given slot index and display - * file name. - * - * @param saveSlot The save slot widget to initialize. - * @param slotIndex The save slot index this widget represents. - * @param fileName Display file name; copied internally, safe to be - * transient. - */ -void uiSaveSlotInit( - uisaveslot_t *saveSlot, - const uint8_t slotIndex, - const char_t *fileName -); - -/** - * Returns whether the save slot widget is highlighted. - * - * @param saveSlot The save slot widget to query. - * @returns True if highlighted. - */ -bool_t uiSaveSlotIsHighlighted(const uisaveslot_t *saveSlot); - -/** - * Sets the highlighted state of the save slot widget. - * - * @param saveSlot The save slot widget to update. - * @param highlighted The new highlighted state. - */ -void uiSaveSlotSetHighlighted(uisaveslot_t *saveSlot, const bool_t highlighted); - -/** - * Returns whether the save slot widget is flagged as corrupt. - * - * @param saveSlot The save slot widget to query. - * @returns True if flagged as corrupt. - */ -bool_t uiSaveSlotIsCorrupt(const uisaveslot_t *saveSlot); - -/** - * Sets whether the save slot widget is flagged as corrupt - drawn in red - * regardless of highlighted/display-type state, taking priority over both. - * - * @param saveSlot The save slot widget to update. - * @param corrupt The new corrupt state. - */ -void uiSaveSlotSetCorrupt(uisaveslot_t *saveSlot, const bool_t corrupt); - -/** - * Returns the save slot widget's display type. - * - * @param saveSlot The save slot widget to query. - * @returns The display type. - */ -uisaveslotdisplaytype_t uiSaveSlotGetDisplayType(const uisaveslot_t *saveSlot); - -/** - * Sets the save slot widget's display type. - * - * @param saveSlot The save slot widget to update. - * @param displayType The new display type. - */ -void uiSaveSlotSetDisplayType( - uisaveslot_t *saveSlot, - const uisaveslotdisplaytype_t displayType -); - -/** - * Draws the save slot widget as a framed box at the given position and - * size, showing its slot number and file name. - * - * @param saveSlot The save slot widget to draw. - * @param x Screen x position. - * @param y Screen y position. - * @param width Frame width. - * @param height Frame height. - * @return Any error that occurs. - */ -errorret_t uiSaveSlotDraw( - uisaveslot_t *saveSlot, - const float_t x, - const float_t y, - const float_t width, - const float_t height -); diff --git a/src/dusk/ui/dialog/save/uiselectsave.c b/src/dusk/ui/dialog/save/uiselectsave.c deleted file mode 100644 index dcdf4c2c..00000000 --- a/src/dusk/ui/dialog/save/uiselectsave.c +++ /dev/null @@ -1,416 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiselectsave.h" -#include "ui/widget/uiframe.h" -#include "ui/widget/uimodal.h" -#include "ui/dialog/uiconfirm.h" -#include "ui/dialog/keyboard/uikeyboard.h" -#include "ui/overlay/uifatalerror.h" -#include "save/save.h" -#include "time/timeepoch.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "display/screen/screen.h" -#include "display/text/text.h" -#include "display/color.h" -#include "display/spritebatch/spritebatch.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -uiselectsave_t UI_SELECT_SAVE; - -void uiSelectSaveFormatSlot( - const uint8_t index, - char_t *buffer, - const size_t bufferSize -) { - saveslotcache_t *cache = &SAVE.caches[index]; - - if(cache->corrupt) { - errorret_t result = localeManagerGetString( - "ui.select_save.corrupt", buffer, bufferSize, 0 - ); - if(errorIsNotOk(result)) errorCatch(result); - return; - } - - if(!saveSlotInUse(cache)) { - errorret_t result = localeManagerGetString( - "ui.select_save.empty", buffer, bufferSize, 0 - ); - if(errorIsNotOk(result)) errorCatch(result); - return; - } - - char_t dateText[32]; - timeEpochFormat(cache->time, "%Y-%m-%d %H:%M", dateText, sizeof(dateText)); - - errorret_t result = localeManagerGetText( - "ui.select_save.slot_format", buffer, bufferSize, 0, - cache->name, cache->playerLevel, dateText - ); - if(errorIsNotOk(result)) errorCatch(result); -} - -void uiSelectSaveRefreshSlot(const uint8_t index) { - char_t fileName[UI_SAVE_SLOT_NAME_TEXT_MAX]; - uiSelectSaveFormatSlot(index, fileName, sizeof(fileName)); - uiSaveSlotInit(&UI_SELECT_SAVE.slots[index], index, fileName); - - uisaveslotdisplaytype_t displayType = - UI_SELECT_SAVE.type == UI_SELECT_SAVE_TYPE_DELETE ? - UI_SAVE_SLOT_DISPLAY_TYPE_DELETE : UI_SAVE_SLOT_DISPLAY_TYPE_NORMAL; - uiSaveSlotSetDisplayType(&UI_SELECT_SAVE.slots[index], displayType); - uiSaveSlotSetCorrupt( - &UI_SELECT_SAVE.slots[index], SAVE.caches[index].corrupt - ); -} - -void uiSelectSaveSetActionLabel(const uiselectsavetype_t type) { - const char_t *labelKey = type == UI_SELECT_SAVE_TYPE_LOAD ? - "ui.select_save.delete_mode" : "ui.confirm.cancel"; - - errorret_t result = localeManagerGetString( - labelKey, UI_SELECT_SAVE.actionText, UI_SELECT_SAVE_ACTION_LABEL_MAX, 0 - ); - if(errorIsNotOk(result)) errorCatch(result); - - UI_SELECT_SAVE.slotItems[UI_SELECT_SAVE_ACTION_INDEX].button.label.dirty = - true; -} - -void uiSelectSaveSwitchType(const uiselectsavetype_t type) { - UI_SELECT_SAVE.type = type; - - uisaveslotdisplaytype_t displayType = type == UI_SELECT_SAVE_TYPE_DELETE ? - UI_SAVE_SLOT_DISPLAY_TYPE_DELETE : UI_SAVE_SLOT_DISPLAY_TYPE_NORMAL; - for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) { - uiSaveSlotSetDisplayType(&UI_SELECT_SAVE.slots[i], displayType); - } - - uiSelectSaveSetActionLabel(type); -} - -void uiSelectSaveDeleteConfirmed(const bool_t result, void *user) { - if(!result) return; - - assertTrue(SAVE.deviceCurrent != 0xFF, "No current device"); - const uint8_t index = UI_SELECT_SAVE.pendingDeleteIndex; - - // uiSelectSaveOpen is only ever reached before any gameplay starts (see - // uimainmenu.c), so it's safe to reuse SAVE.slot as scratch for whichever - // index is being written here - nothing else is relying on it. - SAVE.slotCurrent = index; - errorret_t initResult = saveSlotInit(&SAVE.slot); - if(errorIsNotOk(initResult)) { - errorCatch(errorPrint(initResult)); - uiFatalErrorOpen(initResult.state->message); - return; - } - - errorret_t writeResult = saveDeviceSlotWrite( - &SAVE.devices[SAVE.deviceCurrent], &SAVE.slot, index - ); - if(errorIsNotOk(writeResult)) { - errorCatch(errorPrint(writeResult)); - return; - } - - SAVE.caches[index] = SAVE.slot.cachedData; - - uiSelectSaveSwitchType(UI_SELECT_SAVE_TYPE_LOAD); - uiSelectSaveRefreshSlot(index); - uiSaveSlotSetHighlighted(&UI_SELECT_SAVE.slots[index], true); -} - -void uiSelectSaveConfirmDelete(const uint8_t index) { - UI_SELECT_SAVE.pendingDeleteIndex = index; - uiConfirmOpen( - "ui.select_save.delete_confirm", uiSelectSaveDeleteConfirmed, NULL - ); -} - -void uiSelectSaveNameEntered( - const char_t *text, - const bool_t confirmed, - void *user -) { - if(!confirmed) return; - - const uint8_t index = UI_SELECT_SAVE.pendingNameIndex; - - // uiSelectSaveOpen is only ever reached before any gameplay starts (see - // uimainmenu.c), so it's safe to reuse SAVE.slot as scratch for whichever - // index is being written here - nothing else is relying on it. - SAVE.slotCurrent = index; - errorret_t newGameResult = saveSlotNewGame(); - if(errorIsNotOk(newGameResult)) { - errorCatch(errorPrint(newGameResult)); - uiFatalErrorOpen(newGameResult.state->message); - return; - } - stringCopy(SAVE.slot.cachedData.name, text, SAVE_SLOT_NAME_LENGTH); - - // No save device available - proceed with an in-memory-only slot rather - // than writing to a device that doesn't exist (see saveLoadSlot()'s own - // comment on the same "continue without a save device" flow). - if(SAVE.deviceCurrent != 0xFF) { - errorret_t writeResult = saveDeviceSlotWrite( - &SAVE.devices[SAVE.deviceCurrent], &SAVE.slot, index - ); - if(errorIsNotOk(writeResult)) { - errorCatch(errorPrint(writeResult)); - uiFatalErrorOpen(writeResult.state->message); - return; - } - } - - SAVE.caches[index] = SAVE.slot.cachedData; - uiSelectSaveRefreshSlot(index); - - UI_SELECT_SAVE.result = index; - uiSelectSaveClose(); -} - -void uiSelectSaveNameEmptySlot(const uint8_t index) { - UI_SELECT_SAVE.pendingNameIndex = index; - - char_t title[UI_KEYBOARD_TITLE_TEXT_MAX]; - errorret_t result = localeManagerGetString( - "ui.select_save.name_title", title, sizeof(title), 0 - ); - if(errorIsNotOk(result)) errorCatch(result); - - uikeyboardopen_t open = { - .title = title, - .onInput = uiSelectSaveNameEntered, - .cancel = true, - .maxLength = SAVE_SLOT_NAME_LENGTH, - .trimmed = true, - .allowBlank = false - }; - uiKeyboardOpen(&open); -} - -void uiSelectSaveWarnCorrupt(void) { - const char_t *options[] = { "ui.confirm.confirm" }; - // Acknowledging just closes the warning - the player stays on the - // select-save list rather than being pushed into naming/overwriting - // the slot. - uiModalOpen( - "ui.select_save.corrupt", "ui.select_save.corrupt_warning", - options, 1, - NULL, NULL, NULL - ); -} - -bool_t uiSelectSaveMenuCancelled(const uimenu_t *menu) { - if(UI_SELECT_SAVE.type != UI_SELECT_SAVE_TYPE_DELETE) return false; - - uiSelectSaveSwitchType(UI_SELECT_SAVE_TYPE_LOAD); - return true; -} - -void uiSelectSaveSlotSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - if(UI_SELECT_SAVE.hasAction && index == UI_SELECT_SAVE_ACTION_INDEX) { - uiSelectSaveSwitchType( - UI_SELECT_SAVE.type == UI_SELECT_SAVE_TYPE_LOAD ? - UI_SELECT_SAVE_TYPE_DELETE : UI_SELECT_SAVE_TYPE_LOAD - ); - return; - } - - if(UI_SELECT_SAVE.type == UI_SELECT_SAVE_TYPE_DELETE) { - uiSelectSaveConfirmDelete(index); - return; - } - - if(SAVE.caches[index].corrupt) { - uiSelectSaveWarnCorrupt(); - return; - } - - if(!saveSlotInUse(&SAVE.caches[index])) { - uiSelectSaveNameEmptySlot(index); - return; - } - - UI_SELECT_SAVE.result = index; - uiSelectSaveClose(); -} - -void uiSelectSaveSlotChanged( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) { - uiSaveSlotSetHighlighted(&UI_SELECT_SAVE.slots[i], i == index); - } - - // The action row sits fixed below the scrollable list, so it never - // needs to be scrolled into view. - if(index >= SAVE_SLOT_COUNT) return; - - uiScrollingEnsureVisible( - &UI_SELECT_SAVE.scroll, index, SAVE_SLOT_COUNT, UI_SELECT_SAVE_VISIBLE_ROWS - ); -} - -void uiSelectSaveSlotClosed(const uimenu_t *menu) { - for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) { - uiSaveSlotSetHighlighted(&UI_SELECT_SAVE.slots[i], false); - } - - if(UI_SELECT_SAVE.callback != NULL) { - UI_SELECT_SAVE.callback(UI_SELECT_SAVE.result, UI_SELECT_SAVE.user); - } -} - -errorret_t uiSelectSaveInit(void) { - memoryZero(&UI_SELECT_SAVE, sizeof(uiselectsave_t)); - - uiLabelInit( - &UI_SELECT_SAVE.titleLabel, UI_SELECT_SAVE.titleText, - UI_SELECT_SAVE.titleSprites, UI_SELECT_SAVE_TITLE_SPRITES_MAX - ); - errorChain(localeManagerGetString( - "ui.select_save.title", - UI_SELECT_SAVE.titleText, - UI_SELECT_SAVE_TITLE_TEXT_MAX, - 0 - )); - UI_SELECT_SAVE.titleLabel.dirty = true; - uiLabelRebuffer(&UI_SELECT_SAVE.titleLabel); - - uiMenuInit( - &UI_SELECT_SAVE.slotMenu, uiSelectSaveSlotSelected, uiSelectSaveSlotClosed, - uiSelectSaveSlotChanged - ); - uiMenuSetCancelCallback(&UI_SELECT_SAVE.slotMenu, uiSelectSaveMenuCancelled); - uiMenuSetItems( - &UI_SELECT_SAVE.slotMenu, UI_SELECT_SAVE.slotItems, SAVE_SLOT_COUNT, 1 - ); - UI_SELECT_SAVE.slotItems[UI_SELECT_SAVE_ACTION_INDEX].type = - UI_MENU_WIDGET_TYPE_BUTTON; - uiButtonInit( - &UI_SELECT_SAVE.slotItems[UI_SELECT_SAVE_ACTION_INDEX].button, - UI_SELECT_SAVE.actionText - ); - - uiScrollingInit(&UI_SELECT_SAVE.scroll); - - errorOk(); -} - -errorret_t uiSelectSaveDraw(void) { - if(!uiMenuIsActive(&UI_SELECT_SAVE.slotMenu)) errorOk(); - - const float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; - const float_t rowFrameHeight = - (UI_FRAME_START_Y * 2) + (rowHeight * 2) + UI_FRAME_PADDING_Y; - const float_t listHeight = - (rowFrameHeight * (float_t)UI_SELECT_SAVE_VISIBLE_ROWS) + - (UI_SELECT_SAVE_ROW_GAP * (float_t)(UI_SELECT_SAVE_VISIBLE_ROWS - 1)); - - const float_t width = UI_SELECT_SAVE_WIDTH; - const float_t height = (UI_FRAME_START_Y * 2) - + (float_t)UI_SELECT_SAVE.titleLabel.height + UI_FRAME_PADDING_Y - + listHeight - + (UI_SELECT_SAVE.hasAction ? UI_FRAME_PADDING_Y + rowHeight : 0.0f); - - const float_t x = (float_t)SCREEN.scanX + - ((float_t)SCREEN.scanWidth - width) * 0.5f; - const float_t y = (float_t)SCREEN.scanY + - ((float_t)SCREEN.scanHeight - height) * 0.5f; - - errorChain(uiFrameDraw(x, y, width, height)); - - const float_t contentX = x + UI_FRAME_START_X; - const float_t contentY = y + UI_FRAME_START_Y; - const float_t contentWidth = width - (UI_FRAME_START_X * 2); - - uiLabelSetX(&UI_SELECT_SAVE.titleLabel, contentX); - uiLabelSetY(&UI_SELECT_SAVE.titleLabel, contentY); - errorChain(uiLabelRender(&UI_SELECT_SAVE.titleLabel, COLOR_WHITE)); - - const float_t listY = contentY + (float_t)UI_SELECT_SAVE.titleLabel.height + - UI_FRAME_PADDING_Y; - - const uint8_t offset = UI_SELECT_SAVE.scroll.offset; - uint8_t visibleEnd = (uint8_t)(offset + UI_SELECT_SAVE_VISIBLE_ROWS); - if(visibleEnd > SAVE_SLOT_COUNT) visibleEnd = SAVE_SLOT_COUNT; - - for(uint8_t i = offset; i < visibleEnd; i++) { - const float_t iy = - listY + (float_t)(i - offset) * (rowFrameHeight + UI_SELECT_SAVE_ROW_GAP); - errorChain(uiSaveSlotDraw( - &UI_SELECT_SAVE.slots[i], contentX, iy, contentWidth, rowFrameHeight - )); - } - - if(UI_SELECT_SAVE.hasAction) { - const float_t actionY = listY + listHeight + UI_FRAME_PADDING_Y; - errorChain(uiButtonDraw( - &UI_SELECT_SAVE.slotItems[UI_SELECT_SAVE_ACTION_INDEX].button, - contentX, actionY - )); - } - - errorChain(spriteBatchFlush()); - errorOk(); -} - -bool_t uiSelectSaveIsOpen(void) { - return uiMenuIsActive(&UI_SELECT_SAVE.slotMenu); -} - -void uiSelectSaveOpen( - const uiselectsavetype_t type, - uiselectsaveresultcallback_t callback, - void *user -) { - assertNotNull(callback, "Callback cannot be NULL"); - if(uiMenuIsActive(&UI_SELECT_SAVE.slotMenu)) return; - - UI_SELECT_SAVE.type = type; - UI_SELECT_SAVE.callback = callback; - UI_SELECT_SAVE.user = user; - UI_SELECT_SAVE.result = UI_SELECT_SAVE_RESULT_NONE; - - for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) { - uiSelectSaveRefreshSlot(i); - } - - UI_SELECT_SAVE.hasAction = - type == UI_SELECT_SAVE_TYPE_LOAD || type == UI_SELECT_SAVE_TYPE_DELETE; - uint8_t itemCount = SAVE_SLOT_COUNT; - if(UI_SELECT_SAVE.hasAction) { - uiSelectSaveSetActionLabel(type); - itemCount = UI_SELECT_SAVE_ITEM_CAPACITY; - } - uiMenuSetItems( - &UI_SELECT_SAVE.slotMenu, UI_SELECT_SAVE.slotItems, itemCount, 1 - ); - - uiScrollingInit(&UI_SELECT_SAVE.scroll); - uiMenuOpen(&UI_SELECT_SAVE.slotMenu); -} - -void uiSelectSaveClose(void) { - uiMenuClose(&UI_SELECT_SAVE.slotMenu); -} - -errorret_t uiSelectSaveDispose(void) { - errorOk(); -} diff --git a/src/dusk/ui/dialog/save/uiselectsave.h b/src/dusk/ui/dialog/save/uiselectsave.h deleted file mode 100644 index ccc5bf31..00000000 --- a/src/dusk/ui/dialog/save/uiselectsave.h +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uilabel.h" -#include "ui/widget/uimenu.h" -#include "ui/widget/uiscrolling.h" -#include "ui/dialog/save/uisaveslot.h" -#include "save/slot/saveslot.h" - -#define UI_SELECT_SAVE_TITLE_TEXT_MAX 64 -#define UI_SELECT_SAVE_TITLE_SPRITES_MAX UI_SELECT_SAVE_TITLE_TEXT_MAX -#define UI_SELECT_SAVE_ACTION_LABEL_MAX 32 -#define UI_SELECT_SAVE_VISIBLE_ROWS (SAVE_SLOT_COUNT < 3 ? SAVE_SLOT_COUNT : 3) -#define UI_SELECT_SAVE_WIDTH 260.0f -#define UI_SELECT_SAVE_ROW_GAP 4.0f -#define UI_SELECT_SAVE_RESULT_NONE 0xFF - -// Slot rows plus one trailing action row (switch-to-delete/cancel), only -// present for the LOAD and DELETE types - see uiSelectSaveOpen. -#define UI_SELECT_SAVE_ITEM_CAPACITY (SAVE_SLOT_COUNT + 1) -#define UI_SELECT_SAVE_ACTION_INDEX SAVE_SLOT_COUNT - -/** - * The action the select-save panel is being used for. The caller - * chooses this when calling uiSelectSaveOpen. - */ -typedef enum { - UI_SELECT_SAVE_TYPE_SAVE, - UI_SELECT_SAVE_TYPE_LOAD, - UI_SELECT_SAVE_TYPE_DELETE -} uiselectsavetype_t; - -/** - * Callback invoked once the select-save panel closes: slotIndex is the - * chosen slot, or UI_SELECT_SAVE_RESULT_NONE if backed out of without - * choosing. - * - * @param slotIndex The chosen slot, or UI_SELECT_SAVE_RESULT_NONE. - * @param user Arbitrary pointer passed to uiSelectSaveOpen. - */ -typedef void (*uiselectsaveresultcallback_t)( - const uint8_t slotIndex, void *user -); - -typedef struct { - uilabel_t titleLabel; - char_t titleText[UI_SELECT_SAVE_TITLE_TEXT_MAX]; - spritebatchsprite_t titleSprites[UI_SELECT_SAVE_TITLE_SPRITES_MAX]; - - uiselectsavetype_t type; - bool_t hasAction; - char_t actionText[UI_SELECT_SAVE_ACTION_LABEL_MAX]; - - uimenu_t slotMenu; - uimenuitem_t slotItems[UI_SELECT_SAVE_ITEM_CAPACITY]; - uisaveslot_t slots[SAVE_SLOT_COUNT]; - uiscrolling_t scroll; - uint8_t pendingDeleteIndex; - uint8_t pendingNameIndex; - - uiselectsaveresultcallback_t callback; - void *user; - uint8_t result; -} uiselectsave_t; - -extern uiselectsave_t UI_SELECT_SAVE; - -/** - * Initializes the select-save panel. - * - * @return Any error that occurs. - */ -errorret_t uiSelectSaveInit(void); - -/** - * Draws the select-save panel: a centered frame with a title, a - * scrolling list of save/save.h's slots (each shown as a framed - * uisaveslot_t, showing at most UI_SELECT_SAVE_VISIBLE_ROWS at a time), - * and - when UI_SELECT_SAVE.hasAction is set - the switch-to-delete/ - * cancel action row fixed below the scrollable list, outside it. No-op - * when not open. - * - * @return Any error that occurs. - */ -errorret_t uiSelectSaveDraw(void); - -/** - * Returns true when the select-save panel is currently open. - * - * @returns True if open. - */ -bool_t uiSelectSaveIsOpen(void); - -/** - * Opens the select-save panel for the given action, refreshing each row - * from the current SAVE.caches state. callback is invoked exactly once - * with the result, whether a slot was chosen or the panel was backed - * out of without choosing. - * - * UI_SELECT_SAVE_TYPE_LOAD adds a trailing row to switch to - * UI_SELECT_SAVE_TYPE_DELETE in place, and UI_SELECT_SAVE_TYPE_DELETE - * adds a trailing "Cancel" row to switch back to - * UI_SELECT_SAVE_TYPE_LOAD - neither closes the panel or invokes - * callback, they just toggle UI_SELECT_SAVE.type and the slot rows' - * styling. UI_SELECT_SAVE_TYPE_SAVE has no trailing row. - * - * @param type The action the caller is opening the panel for. - * @param callback Called with the result once the panel closes. Cannot - * be NULL. - * @param user Arbitrary pointer passed through to callback. - */ -void uiSelectSaveOpen( - const uiselectsavetype_t type, - uiselectsaveresultcallback_t callback, - void *user -); - -/** - * Closes the select-save panel. No-op when already closed. - */ -void uiSelectSaveClose(void); - -/** - * Disposes of the select-save panel. - * - * @return Any error that occurs. - */ -errorret_t uiSelectSaveDispose(void); diff --git a/src/dusk/ui/dialog/uiconfirm.c b/src/dusk/ui/dialog/uiconfirm.c deleted file mode 100644 index e7da213d..00000000 --- a/src/dusk/ui/dialog/uiconfirm.c +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiconfirm.h" -#include "ui/widget/uimodal.h" -#include "assert/assert.h" -#include "util/memory.h" - -#define UI_CONFIRM_INDEX_CONFIRM 0 -#define UI_CONFIRM_INDEX_CANCEL 1 - -uiconfirm_t UI_CONFIRM; - -void uiConfirmModalCallback(const uint8_t optionIndex, void *user) { - UI_CONFIRM.result = optionIndex == UI_CONFIRM_INDEX_CONFIRM; - if(UI_CONFIRM.callback != NULL) { - UI_CONFIRM.callback(UI_CONFIRM.result, user); - } -} - -errorret_t uiConfirmInit(void) { - memoryZero(&UI_CONFIRM, sizeof(uiconfirm_t)); - errorOk(); -} - -bool_t uiConfirmIsOpen(void) { - return uiModalIsOpen(); -} - -bool_t uiConfirmGetResult(void) { - return UI_CONFIRM.result; -} - -void uiConfirmOpen( - const char_t *question, - uiconfirmcallback_t callback, - void *user -) { - assertNotNull(question, "Question cannot be NULL"); - - UI_CONFIRM.callback = callback; - UI_CONFIRM.result = false; - - const char_t *options[] = { - "ui.confirm.confirm", - "ui.confirm.cancel" - }; - uiModalOpen( - NULL, question, options, sizeof(options) / sizeof(options[0]), - uiConfirmModalCallback, NULL, user - ); -} - -void uiConfirmClose(void) { - uiModalClose(NULL); -} - -errorret_t uiConfirmDispose(void) { - errorOk(); -} diff --git a/src/dusk/ui/dialog/uiconfirm.h b/src/dusk/ui/dialog/uiconfirm.h deleted file mode 100644 index 3b76ac59..00000000 --- a/src/dusk/ui/dialog/uiconfirm.h +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -/** - * Callback invoked once a confirm dialog is dismissed. Back/cancel - * input is disabled while it's open, so the dialog can only be - * dismissed by picking Confirm or Cancel. - * - * @param result True if Confirm was selected, false if Cancel was - * selected. - * @param user Arbitrary pointer passed to uiConfirmOpen. - */ -typedef void (*uiconfirmcallback_t)(const bool_t result, void *user); - -typedef struct { - uiconfirmcallback_t callback; - bool_t result; -} uiconfirm_t; - -extern uiconfirm_t UI_CONFIRM; - -/** - * Initializes the confirm dialog. - * - * @return Any error that occurs. - */ -errorret_t uiConfirmInit(void); - -/** - * Returns true when the confirm dialog is currently open. - * - * @returns True if open. - */ -bool_t uiConfirmIsOpen(void); - -/** - * Returns the result of the most recently dismissed confirm dialog. - * - * @returns True if Confirm was selected, false otherwise. - */ -bool_t uiConfirmGetResult(void); - -/** - * Opens a confirm dialog, backed by the shared uimodal_t, with the given - * question text and Confirm/Cancel buttons. callback is invoked exactly - * once with the result once a button is selected. - * - * @param question Display text, or a locale message ID; copied - * internally by uimodal, safe to be transient. - * @param callback Called with the result once the dialog closes. May be - * NULL. - * @param user Arbitrary pointer passed through to callback. - */ -void uiConfirmOpen( - const char_t *question, - uiconfirmcallback_t callback, - void *user -); - -/** - * Closes the confirm dialog. No-op when already closed. - */ -void uiConfirmClose(void); - -/** - * Disposes of the confirm dialog. - * - * @return Any error that occurs. - */ -errorret_t uiConfirmDispose(void); diff --git a/src/dusk/ui/focus/CMakeLists.txt b/src/dusk/ui/focus/CMakeLists.txt deleted file mode 100644 index 819217b7..00000000 --- a/src/dusk/ui/focus/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uifocus.c -) diff --git a/src/dusk/ui/focus/uifocus.c b/src/dusk/ui/focus/uifocus.c deleted file mode 100644 index 443ed035..00000000 --- a/src/dusk/ui/focus/uifocus.c +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uifocus.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "input/input.h" -#include "time/time.h" - -const uifocusdirmap_t UI_FOCUS_DIR_MAP[] = { - { INPUT_ACTION_UP, UI_FOCUS_DIRECTION_UP, 0, -1 }, - { INPUT_ACTION_DOWN, UI_FOCUS_DIRECTION_DOWN, 0, 1 }, - { INPUT_ACTION_LEFT, UI_FOCUS_DIRECTION_LEFT, -1, 0 }, - { INPUT_ACTION_RIGHT, UI_FOCUS_DIRECTION_RIGHT, 1, 0 }, - { INPUT_ACTION_NULL, UI_FOCUS_DIRECTION_NONE, 0, 0 }, -}; - -uifocus_t UI_FOCUS; - -void uiFocusInit(void) { - memoryZero(&UI_FOCUS, sizeof(uifocus_t)); -} - -uifocusitem_t * uiFocusPush( - const uint8_t cols, - const uint8_t rows, - uifocusitemcallback_t selected, - uifocusitemcallback_t changed, - uifocusitemcallback_t closed, - uifocusitemdirectioncallback_t direction, - void *user -) { - assertTrue( - UI_FOCUS.count < UI_FOCUS_STACK_MAX, - "UI focus stack overflow" - ); - assertTrue(cols > 0, "Focus item cols must be > 0"); - assertTrue(rows > 0, "Focus item rows must be > 0"); - - uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count]; - memoryZero(item, sizeof(uifocusitem_t)); - item->cols = cols; - item->rows = rows; - item->selected = selected; - item->changed = changed; - item->closed = closed; - item->direction = direction; - item->user = user; - UI_FOCUS.count++; - UI_FOCUS.pushedThisTick = true; - if(item->changed != NULL) item->changed(item); - return item; -} - -void uiFocusPop(void) { - assertTrue(UI_FOCUS.count > 0, "UI focus stack underflow"); - - // Count is decremented before the closed callback fires so that a - // push triggered from within it (e.g. reopening a menu once another - // one reports it closed with no result) lands in the now-free slot - // instead of one past it, which would silently strand the new item - // above the tracked stack top. - UI_FOCUS.count--; - uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count]; - if(item->closed != NULL) item->closed(item); -} - -void uiFocusPopItem(uifocusitem_t *item) { - assertTrue(UI_FOCUS.count > 0, "UI focus stack underflow"); - assertTrue(item >= UI_FOCUS.items, "Item is not on the focus stack"); - assertTrue( - item < UI_FOCUS.items + UI_FOCUS.count, - "Item is not on the focus stack" - ); - - while(&UI_FOCUS.items[UI_FOCUS.count - 1] != item) { - uiFocusPop(); - } - uiFocusPop(); -} - -void uiFocusSetPosition(uifocusitem_t *item, const uint8_t x, const uint8_t y) { - assertTrue(UI_FOCUS.count > 0, "No active focus item"); - assertTrue(item->cols > 0, "Focus item cols must be > 0"); - assertTrue(item->rows > 0, "Focus item rows must be > 0"); - - uint8_t newX = x % item->cols; - uint8_t newY = y % item->rows; - - if(item->changed != NULL) { - uint8_t oldX = item->x; - uint8_t oldY = item->y; - item->x = newX; - item->y = newY; - if(!item->changed(item)) { - item->x = oldX; - item->y = oldY; - } - return; - } - - item->x = newX; - item->y = newY; -} - -void uiFocusMoveDirection( - uifocusitem_t *item, - const uifocusdirection_t dir -) { - for( - const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - m->action != INPUT_ACTION_NULL; - m++ - ) { - if(m->direction != dir) continue; - - // Widened to int16_t so a move off the left/top edge (e.g. x=0, - // dx=-1) stays negative here instead of wrapping straight to 255 via - // a uint8_t truncation - that would then land on `255 % cols` - // (column 3 of a 14-wide grid, not the intended last column) rather - // than wrapping to the row/column's actual last cell. - int16_t x = (int16_t)item->x + m->dx; - int16_t y = (int16_t)item->y + m->dy; - if(x < 0) x += item->cols; - if(y < 0) y += item->rows; - - uiFocusSetPosition(item, (uint8_t)x, (uint8_t)y); - break; - } -} - -void uiFocusHandleDirection( - uifocusitem_t *item, - const uifocusdirection_t dir -) { - if(item->direction != NULL && item->direction(item, dir)) return; - uiFocusMoveDirection(item, dir); -} - -void uiFocusUpdate(void) { - if(UI_FOCUS.count == 0) return; - - #ifdef DUSK_TIME_DYNAMIC - if(TIME.dynamicUpdate) return; - #endif - - if(UI_FOCUS.pushedThisTick) { - UI_FOCUS.pushedThisTick = false; - return; - } - - uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1]; - - if(inputPressed(INPUT_ACTION_ACCEPT)) { - if(item->selected != NULL) item->selected(item); - return; - } - - if(inputPressed(INPUT_ACTION_CANCEL)) { - if(item->cancel != NULL && item->cancel(item)) return; - if(!item->disableBack) uiFocusPop(); - return; - } - - for( - const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - m->action != INPUT_ACTION_NULL; - m++ - ) { - if(!inputPressed(m->action)) continue; - UI_FOCUS.direction = m->direction; - UI_FOCUS.timeHeld = 0.0f; - uiFocusHandleDirection(item, m->direction); - return; - } - - bool_t held = false; - for( - const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - m->action != INPUT_ACTION_NULL; - m++ - ) { - if(m->direction != UI_FOCUS.direction) continue; - held = inputIsDown(m->action); - break; - } - - if(!held) { - UI_FOCUS.direction = UI_FOCUS_DIRECTION_NONE; - UI_FOCUS.timeHeld = 0.0f; - return; - } - - UI_FOCUS.timeHeld += TIME.delta; - - if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) return; - if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) return; - UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY; - uiFocusHandleDirection(item, UI_FOCUS.direction); -} diff --git a/src/dusk/ui/focus/uifocus.h b/src/dusk/ui/focus/uifocus.h deleted file mode 100644 index d68de640..00000000 --- a/src/dusk/ui/focus/uifocus.h +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "uifocusitem.h" -#include "input/inputaction.h" - -/** Maximum depth of the focus stack. */ -#define UI_FOCUS_STACK_MAX 8 - -/** - * How long a direction must be held before repeating begins, in seconds. - */ -#define UI_FOCUS_HOLD_DELAY 0.5f - -/** - * Interval between repeated moves while a direction is held, in seconds. - */ -#define UI_FOCUS_HOLD_REPEAT 0.1f - -typedef struct { - inputaction_t action; - uifocusdirection_t direction; - int8_t dx; - int8_t dy; -} uifocusdirmap_t; - -/** - * Mapping of input actions to focus directions, terminated by an - * entry with action == INPUT_ACTION_NULL. - */ -extern const uifocusdirmap_t UI_FOCUS_DIR_MAP[]; - -/** - * A stack of focused UI items. Push an item when a widget captures - * focus; pop it when focus is released. The topmost item is always - * the active focus context. - */ -typedef struct { - uifocusitem_t items[UI_FOCUS_STACK_MAX]; - uint8_t count; - uifocusdirection_t direction; - float_t timeHeld; - bool_t pushedThisTick; -} uifocus_t; - -extern uifocus_t UI_FOCUS; - -/** - * Initializes the focus system, zeroing all state. - */ -void uiFocusInit(void); - -/** - * Pushes a new focus item onto the stack with the given grid dimensions - * and callbacks. x and y are initialized to 0. - * - * @param cols Number of columns in the focus grid. - * @param rows Number of rows in the focus grid. - * @param selected Called when the user selects the focused cell. - * @param changed Called when the focused cell position changes. - * @param closed Called when this focus item is popped. - * @param direction Called on a direction press/hold before the default - * cell-to-cell movement is applied; may be NULL. - * @param user Arbitrary pointer stored on the item before changed fires. - * @returns Pointer to the newly pushed focus item. - */ -uifocusitem_t * uiFocusPush( - const uint8_t cols, - const uint8_t rows, - uifocusitemcallback_t selected, - uifocusitemcallback_t changed, - uifocusitemcallback_t closed, - uifocusitemdirectioncallback_t direction, - void *user -); - -/** - * Pops the topmost focus item from the stack, invoking its closed - * callback if one is set. - */ -void uiFocusPop(void); - -/** - * Pops an item and anything that was pushed after it from the focus stack. - * - * @param item The focus item to pop to. Must be on the stack. - */ -void uiFocusPopItem(uifocusitem_t *item); - -/** - * Manually sets the cursor position of the topmost focus item and - * fires its changed callback. - * - * @param x Column to move to. - * @param y Row to move to. - */ -void uiFocusSetPosition(uifocusitem_t *item, const uint8_t x, const uint8_t y); - -/** - * Moves the topmost focus item one step in the given direction, - * wrapping at the grid edges, and fires its changed callback. - * - * @param item The focus item to move. - * @param dir Direction to move. - */ -void uiFocusMoveDirection( - uifocusitem_t *item, - const uifocusdirection_t dir -); - -/** - * Handles a direction press/hold for the given item: gives the item's - * direction callback (if any) first refusal, falling back to the - * default cell-to-cell movement if it's unset or returns false. - * - * @param item The focus item to move. - * @param dir Direction that was pressed or held. - */ -void uiFocusHandleDirection( - uifocusitem_t *item, - const uifocusdirection_t dir -); - -/** - * Updates the focus system. Handles first-press movement and - * held-direction repeating. Called once per game tick. - */ -void uiFocusUpdate(void); diff --git a/src/dusk/ui/focus/uifocusitem.h b/src/dusk/ui/focus/uifocusitem.h deleted file mode 100644 index 32654321..00000000 --- a/src/dusk/ui/focus/uifocusitem.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -typedef struct uifocusitem_s uifocusitem_t; - -typedef enum { - UI_FOCUS_DIRECTION_NONE, - UI_FOCUS_DIRECTION_UP, - UI_FOCUS_DIRECTION_DOWN, - UI_FOCUS_DIRECTION_LEFT, - UI_FOCUS_DIRECTION_RIGHT -} uifocusdirection_t; - -/** - * Callback invoked when a focus item's selected cell changes. - * - * @param item The focus item that changed. - */ -typedef bool_t (*uifocusitemcallback_t)(const uifocusitem_t *item); - -/** - * Callback invoked when a direction is pressed or held-repeated while - * this item is focused, before the default cell-to-cell movement is - * applied. - * - * @param item The focus item that is currently focused. - * @param direction The direction that was pressed. - * @returns True if the direction was fully handled and the default - * grid movement should be skipped; false to fall through to it. - */ -typedef bool_t (*uifocusitemdirectioncallback_t)( - const uifocusitem_t *item, - const uifocusdirection_t direction -); - -/** - * A single entry on the UI focus stack. Tracks the focused cell - * within a grid of cols x rows, and the current x/y position. - */ -struct uifocusitem_s { - uint8_t cols; - uint8_t rows; - uint8_t x; - uint8_t y; - uifocusitemcallback_t selected; - uifocusitemcallback_t changed; - uifocusitemcallback_t closed; - uifocusitemdirectioncallback_t direction; - - // Called when INPUT_ACTION_CANCEL is pressed, before disableBack/the - // default pop is applied; may be NULL. Returns true if fully handled - // (e.g. it did something else instead, or popped itself), which skips - // the default pop; false to fall through to the default disableBack/ - // pop behavior below. - uifocusitemcallback_t cancel; - - void *user; - - // When true, INPUT_ACTION_CANCEL is ignored while this item is the - // topmost focus item - it does not pop. Does not affect a - // programmatic uiFocusPop()/uiFocusPopItem() call. Only consulted - // when cancel is unset or returns false. - bool_t disableBack; -}; diff --git a/src/dusk/ui/overlay/CMakeLists.txt b/src/dusk/ui/overlay/CMakeLists.txt deleted file mode 100644 index 2c49abf3..00000000 --- a/src/dusk/ui/overlay/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uicrop.c - uifullbox.c - uiloading.c - uifatalerror.c -) diff --git a/src/dusk/ui/overlay/uicrop.c b/src/dusk/ui/overlay/uicrop.c deleted file mode 100644 index 3e8d388b..00000000 --- a/src/dusk/ui/overlay/uicrop.c +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uicrop.h" -#include "display/screen/screen.h" -#include "display/spritebatch/spritebatch.h" -#include "display/shader/shaderunlit.h" -#include "display/texture/texture.h" - -uicrop_t UI_CROP; - -errorret_t uiCropInit(void) { - UI_CROP.color = COLOR_BLACK; - errorOk(); -} - -errorret_t uiCropDraw(void) { - #ifndef DUSK_DISPLAY_SIZE_DYNAMIC - errorOk(); - #endif - - if( - SCREEN.scanX == 0 && - SCREEN.scanY == 0 && - SCREEN.scanWidth == SCREEN.width && - SCREEN.scanHeight == SCREEN.height - ) errorOk(); - - float_t x0 = (float_t)SCREEN.scanX; - float_t y0 = (float_t)SCREEN.scanY; - float_t x1 = (float_t)(SCREEN.scanX + SCREEN.scanWidth); - float_t y1 = (float_t)(SCREEN.scanY + SCREEN.scanHeight); - float_t w = (float_t)SCREEN.width; - float_t h = (float_t)SCREEN.height; - - spritebatchsprite_t sprites[4]; - int32_t count = 0; - - if(SCREEN.scanX > 0) { - sprites[count++] = (spritebatchsprite_t){ - .min = { 0.0f, 0.0f, 0.0f }, - .max = { x0, h, 0.0f }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - } - - if(SCREEN.scanX + SCREEN.scanWidth < SCREEN.width) { - sprites[count++] = (spritebatchsprite_t){ - .min = { x1, 0.0f, 0.0f }, - .max = { w, h, 0.0f }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - } - - if(SCREEN.scanY > 0) { - sprites[count++] = (spritebatchsprite_t){ - .min = { x0, 0.0f, 0.0f }, - .max = { x1, y0, 0.0f }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - } - - if(SCREEN.scanY + SCREEN.scanHeight < SCREEN.height) { - sprites[count++] = (spritebatchsprite_t){ - .min = { x0, y1, 0.0f }, - .max = { x1, h, 0.0f }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - } - - if(count == 0) errorOk(); - - shadermaterial_t material = { - .unlit = { - .color = UI_CROP.color, - .texture = &TEXTURE_WHITE - } - }; - errorChain(spriteBatchBuffer(sprites, count, &SHADER_UNLIT, material)); - return spriteBatchFlush(); -} diff --git a/src/dusk/ui/overlay/uicrop.h b/src/dusk/ui/overlay/uicrop.h deleted file mode 100644 index ef822633..00000000 --- a/src/dusk/ui/overlay/uicrop.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "display/color.h" - -typedef struct { - color_t color; -} uicrop_t; - -extern uicrop_t UI_CROP; - -/** - * Initializes the crop bars to opaque black. - * - * @return Any error that occurs. - */ -errorret_t uiCropInit(void); - -/** - * Renders solid-color bars covering every area outside the - * current scan-safe region. No-ops when the scan area equals - * the full viewport. - * - * @return Any error that occurs. - */ -errorret_t uiCropDraw(void); diff --git a/src/dusk/ui/overlay/uifatalerror.c b/src/dusk/ui/overlay/uifatalerror.c deleted file mode 100644 index efc59171..00000000 --- a/src/dusk/ui/overlay/uifatalerror.c +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uifatalerror.h" -#include "ui/widget/uiframe.h" -#include "ui/widget/uibutton.h" -#include "engine/engine.h" -#include "util/memory.h" -#include "util/string.h" -#include "util/math.h" -#include "display/screen/screen.h" -#include "display/color.h" -#include "display/text/font.h" -#include "display/spritebatch/spritebatch.h" - -uifatalerror_t UI_FATAL_ERROR; - -void uiFatalErrorMenuSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - ENGINE.running = false; -} - -errorret_t uiFatalErrorInit(void) { - memoryZero(&UI_FATAL_ERROR, sizeof(uifatalerror_t)); - - uiLabelInit( - &UI_FATAL_ERROR.messageLabel, UI_FATAL_ERROR.messageText, - UI_FATAL_ERROR.messageSprites, UI_FATAL_ERROR_MESSAGE_SPRITES_MAX - ); - - errorOk(); -} - -void uiFatalErrorOpen(const char_t *message) { - // This is the last line of defense - it must never itself fail, so a - // second fatal error while one is already showing is silently dropped - // rather than asserting. - if(UI_FATAL_ERROR.open) return; - if(message == NULL) message = UI_FATAL_ERROR_GENERIC_MESSAGE; - - stringCopy( - UI_FATAL_ERROR.messageText, message, UI_FATAL_ERROR_MESSAGE_MAX - 1 - ); - - const float_t wrapWidth = - (float_t)SCREEN.scanWidth * UI_FATAL_ERROR_MESSAGE_WRAP_RATIO; - uiLabelSetWrap( - &UI_FATAL_ERROR.messageLabel, UI_FATAL_ERROR.messageWrapText, - UI_FATAL_ERROR_MESSAGE_MAX, wrapWidth - ); - uiLabelRebuffer(&UI_FATAL_ERROR.messageLabel); - - UI_FATAL_ERROR.open = true; - - uiMenuInit(&UI_FATAL_ERROR.menu, uiFatalErrorMenuSelected, NULL, NULL); - UI_FATAL_ERROR.items[0].type = UI_MENU_WIDGET_TYPE_BUTTON; - uiButtonInit(&UI_FATAL_ERROR.items[0].button, UI_FATAL_ERROR_QUIT_LABEL); - uiMenuSetItems( - &UI_FATAL_ERROR.menu, UI_FATAL_ERROR.items, UI_FATAL_ERROR_ITEM_COUNT, 1 - ); - // No way out but the QUIT button - this is a fatal, unrecoverable - // condition. - uiMenuSetDisableBack(&UI_FATAL_ERROR.menu, true); - uiMenuOpen(&UI_FATAL_ERROR.menu); -} - -bool_t uiFatalErrorIsOpen(void) { - return UI_FATAL_ERROR.open; -} - -errorret_t uiFatalErrorDraw(void) { - if(!UI_FATAL_ERROR.open) errorOk(); - - const float_t x = (float_t)SCREEN.scanX; - const float_t y = (float_t)SCREEN.scanY; - const float_t width = (float_t)SCREEN.scanWidth; - const float_t height = (float_t)SCREEN.scanHeight; - - errorChain(uiFrameDraw(x, y, width, height)); - - const float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; - const float_t contentWidth = mathMax( - (float_t)UI_FATAL_ERROR.messageLabel.width, - (float_t)UI_FATAL_ERROR.items[0].button.label.width - ); - const float_t contentHeight = (float_t)UI_FATAL_ERROR.messageLabel.height + - UI_FRAME_PADDING_Y + rowHeight; - - const float_t contentX = x + (width - contentWidth) * 0.5f; - const float_t contentY = y + (height - contentHeight) * 0.5f; - - uiLabelSetX(&UI_FATAL_ERROR.messageLabel, contentX); - uiLabelSetY(&UI_FATAL_ERROR.messageLabel, contentY); - errorChain(uiLabelRender(&UI_FATAL_ERROR.messageLabel, COLOR_WHITE)); - - const float_t menuY = contentY + - (float_t)UI_FATAL_ERROR.messageLabel.height + UI_FRAME_PADDING_Y; - errorChain(uiMenuDraw( - &UI_FATAL_ERROR.menu, contentX, menuY, contentWidth, rowHeight - )); - - errorChain(spriteBatchFlush()); - errorOk(); -} - -errorret_t uiFatalErrorDispose(void) { - errorOk(); -} diff --git a/src/dusk/ui/overlay/uifatalerror.h b/src/dusk/ui/overlay/uifatalerror.h deleted file mode 100644 index 85e8b6b1..00000000 --- a/src/dusk/ui/overlay/uifatalerror.h +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uilabel.h" -#include "ui/widget/uimenu.h" - -#define UI_FATAL_ERROR_MESSAGE_MAX 256 -#define UI_FATAL_ERROR_MESSAGE_SPRITES_MAX UI_FATAL_ERROR_MESSAGE_MAX -#define UI_FATAL_ERROR_MESSAGE_WRAP_RATIO 0.8f -#define UI_FATAL_ERROR_ITEM_COUNT 1 -#define UI_FATAL_ERROR_QUIT_LABEL "QUIT" -#define UI_FATAL_ERROR_GENERIC_MESSAGE \ - "An unexpected error occurred and the game must exit. " \ - "Please contact support if this issue persists." - -typedef struct { - bool_t open; - - uilabel_t messageLabel; - char_t messageText[UI_FATAL_ERROR_MESSAGE_MAX]; - char_t messageWrapText[UI_FATAL_ERROR_MESSAGE_MAX]; - spritebatchsprite_t messageSprites[UI_FATAL_ERROR_MESSAGE_SPRITES_MAX]; - - uimenu_t menu; - uimenuitem_t items[UI_FATAL_ERROR_ITEM_COUNT]; -} uifatalerror_t; - -extern uifatalerror_t UI_FATAL_ERROR; - -/** - * Initializes the fatal error overlay. - * - * @return Any error that occurs. - */ -errorret_t uiFatalErrorInit(void); - -/** - * Opens the fatal error overlay, a fullscreen, non-dismissible display - * for an unrecoverable error - the only way out is its QUIT button, - * which sets ENGINE.running to false. Intended as a last-resort, nicer- - * looking alternative to a raw assert crash for a genuinely fatal - * condition, so it must never itself fail: a second call while already - * open is silently ignored rather than asserting, and it draws above - * every other UI element. - * - * @param message The error text to display; copied internally, safe to - * be transient. Truncated to UI_FATAL_ERROR_MESSAGE_MAX - 1 characters. - * May be NULL, in which case UI_FATAL_ERROR_GENERIC_MESSAGE is shown - * instead - useful for callers with no specific error text to report - * (or that don't want to risk formatting/locale-lookup failures of their - * own right before calling this, the last line of defense). - */ -void uiFatalErrorOpen(const char_t *message); - -/** - * Returns whether the fatal error overlay is currently open. - * - * @returns True if open. - */ -bool_t uiFatalErrorIsOpen(void); - -/** - * Draws the fatal error overlay. No-op when not open. - * - * @return Any error that occurs. - */ -errorret_t uiFatalErrorDraw(void); - -/** - * Disposes of the fatal error overlay. - * - * @return Any error that occurs. - */ -errorret_t uiFatalErrorDispose(void); diff --git a/src/dusk/ui/overlay/uifullbox.c b/src/dusk/ui/overlay/uifullbox.c deleted file mode 100644 index d1751f0b..00000000 --- a/src/dusk/ui/overlay/uifullbox.c +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uifullbox.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "time/time.h" -#include "display/screen/screen.h" -#include "display/texture/texture.h" -#include "display/spritebatch/spritebatch.h" -#include "display/shader/shaderunlit.h" - -uifullbox_t UI_FULLBOX_UNDER; -uifullbox_t UI_FULLBOX_OVER; - -void uiFullboxInit(uifullbox_t *fullbox) { - assertNotNull(fullbox, "fullbox must not be NULL"); - memoryZero(fullbox, sizeof(uifullbox_t)); -} - -void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) { - assertNotNull(fullbox, "fullbox must not be NULL"); - if(fullbox->duration <= 0.0f || fullbox->time >= fullbox->duration) return; - - fullbox->time += delta; - if(fullbox->time >= fullbox->duration) { - fullbox->time = fullbox->duration; - if(fullbox->onTransitionEnd) { - fullbox->onTransitionEnd(fullbox, fullbox->onTransitionEndUser); - } - } -} - -color_t uiFullboxGetColor(const uifullbox_t *fullbox) { - if(fullbox->duration <= 0.0f || fullbox->time >= fullbox->duration) { - return fullbox->toColor; - } - float_t t = easingApply(fullbox->easing, fullbox->time / fullbox->duration); - return color4b( - (uint8_t)((float_t)fullbox->fromColor.r + - ((float_t)fullbox->toColor.r - (float_t)fullbox->fromColor.r) * t), - (uint8_t)((float_t)fullbox->fromColor.g + - ((float_t)fullbox->toColor.g - (float_t)fullbox->fromColor.g) * t), - (uint8_t)((float_t)fullbox->fromColor.b + - ((float_t)fullbox->toColor.b - (float_t)fullbox->fromColor.b) * t), - (uint8_t)((float_t)fullbox->fromColor.a + - ((float_t)fullbox->toColor.a - (float_t)fullbox->fromColor.a) * t) - ); -} - -errorret_t uiFullboxDraw(uifullbox_t *fullbox) { - assertNotNull(fullbox, "fullbox must not be NULL"); - - color_t color = uiFullboxGetColor(fullbox); - if(color.a == 0) errorOk(); - - spritebatchsprite_t sprite = { - .min = { 0.0f, 0.0f, 0.0f }, - .max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - shadermaterial_t material = { - .unlit = { - .color = color, - .texture = &TEXTURE_WHITE - } - }; - errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material)); - return spriteBatchFlush(); -} - -void uiFullboxTransition( - uifullbox_t *fullbox, - color_t from, - color_t to, - float_t duration, - easingtype_t easing -) { - assertNotNull(fullbox, "fullbox must not be NULL"); - fullbox->fromColor = from; - fullbox->toColor = to; - fullbox->duration = duration; - fullbox->time = 0.0f; - fullbox->easing = easing; -} - -errorret_t uiFullboxUnderInit(void) { - uiFullboxInit(&UI_FULLBOX_UNDER); - errorOk(); -} - -errorret_t uiFullboxUnderUpdate(void) { - uiFullboxUpdate(&UI_FULLBOX_UNDER, TIME.delta); - errorOk(); -} - -errorret_t uiFullboxUnderDraw(void) { - return uiFullboxDraw(&UI_FULLBOX_UNDER); -} - -errorret_t uiFullboxOverInit(void) { - uiFullboxInit(&UI_FULLBOX_OVER); - errorOk(); -} - -errorret_t uiFullboxOverUpdate(void) { - uiFullboxUpdate(&UI_FULLBOX_OVER, TIME.delta); - errorOk(); -} - -errorret_t uiFullboxOverDraw(void) { - return uiFullboxDraw(&UI_FULLBOX_OVER); -} diff --git a/src/dusk/ui/overlay/uifullbox.h b/src/dusk/ui/overlay/uifullbox.h deleted file mode 100644 index f27a1466..00000000 --- a/src/dusk/ui/overlay/uifullbox.h +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "display/color.h" -#include "animation/easing.h" - -typedef struct uifullbox_s uifullbox_t; - -/** - * Callback fired once when a fullbox transition completes. - * - * @param fullbox The uifullbox_t the transition ran on. - * @param user The user pointer passed alongside the callback. - */ -typedef void (*uifullboxcallback_t)(uifullbox_t *fullbox, void *user); - -struct uifullbox_s { - color_t fromColor; - color_t toColor; - float_t duration; - float_t time; - easingtype_t easing; - uifullboxcallback_t onTransitionEnd; - void *onTransitionEndUser; -}; - -extern uifullbox_t UI_FULLBOX_UNDER; -extern uifullbox_t UI_FULLBOX_OVER; - -/** - * Initializes a fullbox, zeroing all fields. - * - * @param fullbox The fullbox to initialize. - */ -void uiFullboxInit(uifullbox_t *fullbox); - -/** - * Advances the fullbox transition. Fires onTransitionEnd once when the - * transition completes. Safe to call when no transition is running. - * - * @param fullbox The fullbox to update. - * @param delta Seconds elapsed since last update. - */ -void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta); - -/** - * Computes the fullbox's current color, interpolating between fromColor and - * toColor based on the transition's elapsed time and easing function. - * - * @param fullbox The fullbox to compute the color for. - * @return The current interpolated color. - */ -color_t uiFullboxGetColor(const uifullbox_t *fullbox); - -/** - * Renders the fullbox. Skipped entirely when the current alpha is zero. - * - * @param fullbox The fullbox to draw. - * @return Any error that occurs. - */ -errorret_t uiFullboxDraw(uifullbox_t *fullbox); - -/** - * Begins a color transition. - * - * @param fullbox The fullbox to transition. - * @param from Start color. - * @param to End color. - * @param duration Transition duration in seconds. - * @param easing Easing function to apply. - */ -void uiFullboxTransition( - uifullbox_t *fullbox, - color_t from, - color_t to, - float_t duration, - easingtype_t easing -); - -/** - * Init function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS). - * - * @return Any error that occurs. - */ -errorret_t uiFullboxUnderInit(void); - -/** - * Update function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS). - * - * @return Any error that occurs. - */ -errorret_t uiFullboxUnderUpdate(void); - -/** - * Draw function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS). - * - * @return Any error that occurs. - */ -errorret_t uiFullboxUnderDraw(void); - -/** - * Init function for UI_FULLBOX_OVER (registered in UI_ELEMENTS). - * - * @return Any error that occurs. - */ -errorret_t uiFullboxOverInit(void); - -/** - * Update function for UI_FULLBOX_OVER (registered in UI_ELEMENTS). - * - * @return Any error that occurs. - */ -errorret_t uiFullboxOverUpdate(void); - -/** - * Draw function for UI_FULLBOX_OVER (registered in UI_ELEMENTS). - * - * @return Any error that occurs. - */ -errorret_t uiFullboxOverDraw(void); diff --git a/src/dusk/ui/overlay/uiloading.c b/src/dusk/ui/overlay/uiloading.c deleted file mode 100644 index 70927eac..00000000 --- a/src/dusk/ui/overlay/uiloading.c +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiloading.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "time/time.h" -#include "display/text/text.h" -#include "display/screen/screen.h" -#include "display/spritebatch/spritebatch.h" -#include "uifullbox.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -uiloading_t UI_LOADING; - -errorret_t uiLoadingInit(void) { - memoryZero(&UI_LOADING, sizeof(uiloading_t)); - - errorChain(localeManagerGetString( - "ui.loading.text", UI_LOADING.text, UI_LOADING_TEXT_MAX, 0 - )); - - errorOk(); -} - -errorret_t uiLoadingUpdate(void) { - if(UI_LOADING.duration > 0.0f && UI_LOADING.time < UI_LOADING.duration) { - UI_LOADING.time += TIME.delta; - if(UI_LOADING.time >= UI_LOADING.duration) { - UI_LOADING.time = UI_LOADING.duration; - if(UI_LOADING.onTransitionEnd) { - UI_LOADING.onTransitionEnd(&UI_LOADING, UI_LOADING.onTransitionEndUser); - } - } - } - errorOk(); -} - -errorret_t uiLoadingDraw(void) { - float_t alpha; - if(UI_LOADING.duration <= 0.0f || UI_LOADING.time >= UI_LOADING.duration) { - alpha = UI_LOADING.toAlpha; - } else { - float_t t = UI_LOADING.time / UI_LOADING.duration; - alpha = UI_LOADING.fromAlpha + - (UI_LOADING.toAlpha - UI_LOADING.fromAlpha) * t; - } - - if(alpha <= 0.0f) errorOk(); - - int32_t textW, textH; - textMeasure(UI_LOADING.text, &FONT_DEFAULT, &textW, &textH); - - float_t x = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - - (float_t)textW - UI_LOADING_MARGIN; - float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - - (float_t)textH - UI_LOADING_MARGIN; - - color_t color = COLOR_WHITE; - color.a = (uint8_t)(alpha * 255.0f); - - errorChain(textDraw(x, y, UI_LOADING.text, color, &FONT_DEFAULT)); - return spriteBatchFlush(); -} - -void uiLoadingTransition( - const float_t from, - const float_t to, - const uiloadingcallback_t callback, - void *user -) { - UI_LOADING.fromAlpha = from; - UI_LOADING.toAlpha = to; - UI_LOADING.duration = UI_LOADING_FADE_DURATION; - UI_LOADING.time = 0.0f; - UI_LOADING.onTransitionEnd = callback; - UI_LOADING.onTransitionEndUser = user; -} - -void uiLoadingShow(uiloadingcallback_t callback, void *user) { - uiLoadingTransition(0.0f, 1.0f, callback, user); - uiFullboxTransition( - &UI_FULLBOX_OVER, - COLOR_TRANSPARENT_BLACK, - COLOR_BLACK, - UI_LOADING_FADE_DURATION, - EASING_LINEAR - ); -} - -void uiLoadingHide(uiloadingcallback_t callback, void *user) { - uiLoadingTransition(1.0f, 0.0f, callback, user); - uiFullboxTransition( - &UI_FULLBOX_OVER, - COLOR_BLACK, - COLOR_TRANSPARENT_BLACK, - UI_LOADING_FADE_DURATION, - EASING_LINEAR - ); -} diff --git a/src/dusk/ui/overlay/uiloading.h b/src/dusk/ui/overlay/uiloading.h deleted file mode 100644 index 531d4f23..00000000 --- a/src/dusk/ui/overlay/uiloading.h +++ /dev/null @@ -1,89 +0,0 @@ -/** - * 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_LOADING_FADE_DURATION 0.5f -#define UI_LOADING_MARGIN 8.0f -#define UI_LOADING_TEXT_MAX 32 - -typedef struct uiloading_s uiloading_t; - -/** - * Callback fired once when a loading indicator fade transition completes. - * - * @param loading The uiloading_t the transition ran on. - * @param user The user pointer passed alongside the callback. - */ -typedef void (*uiloadingcallback_t)(uiloading_t *loading, void *user); - -struct uiloading_s { - float_t fromAlpha; - float_t toAlpha; - float_t duration; - float_t time; - uiloadingcallback_t onTransitionEnd; - void *onTransitionEndUser; - char_t text[UI_LOADING_TEXT_MAX]; -}; - -extern uiloading_t UI_LOADING; - -/** - * Initializes the loading indicator. - * - * @return Any error that occurs. - */ -errorret_t uiLoadingInit(void); - -/** - * Advances the loading indicator fade transition. Fires onTransitionEnd once - * when the transition completes. - * - * @return Any error that occurs. - */ -errorret_t uiLoadingUpdate(void); - -/** - * Draws the loading indicator. No-op when fully transparent. - * - * @return Error state. - */ -errorret_t uiLoadingDraw(void); - -/** - * Begins a loading indicator fade transition, replacing any transition - * already in progress along with its pending callback. - * - * @param from Starting alpha. - * @param to Ending alpha. - * @param callback Called when the transition completes. May be NULL. - * @param user Forwarded to the callback unchanged. - */ -void uiLoadingTransition( - const float_t from, - const float_t to, - const uiloadingcallback_t callback, - void *user -); - -/** - * Fades the loading indicator in. Invokes callback when fully visible. - * - * @param callback Called when the fade-in completes. May be NULL. - * @param user Forwarded to the callback unchanged. - */ -void uiLoadingShow(uiloadingcallback_t callback, void *user); - -/** - * Fades the loading indicator out. Invokes callback when fully hidden. - * - * @param callback Called when the fade-out completes. May be NULL. - * @param user Forwarded to the callback unchanged. - */ -void uiLoadingHide(uiloadingcallback_t callback, void *user); diff --git a/src/dusk/ui/rpg/CMakeLists.txt b/src/dusk/ui/rpg/CMakeLists.txt deleted file mode 100644 index eb3441de..00000000 --- a/src/dusk/ui/rpg/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -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/textbox/CMakeLists.txt b/src/dusk/ui/rpg/textbox/CMakeLists.txt deleted file mode 100644 index e7787f2d..00000000 --- a/src/dusk/ui/rpg/textbox/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uitextbox.c - uitextboxmain.c - uitextboxmini.c - uitextboxminilist.c -) diff --git a/src/dusk/ui/rpg/textbox/uitextbox.c b/src/dusk/ui/rpg/textbox/uitextbox.c deleted file mode 100644 index 20b078e5..00000000 --- a/src/dusk/ui/rpg/textbox/uitextbox.c +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uitextbox.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "time/time.h" -#include "display/text/text.h" -#include "display/color.h" -#include "display/spritebatch/spritebatch.h" -#include "display/shader/shaderunlit.h" -#include "ui/widget/uiframe.h" - -void uiTextboxInit( - uitextbox_t *box, - char_t *text, - const uint32_t maxLength, - uitextboxline_t *lines, - const uint32_t linesMax -) { - assertNotNull(box, "Textbox cannot be NULL"); - assertNotNull(text, "Text buffer cannot be NULL"); - assertTrue(maxLength >= 1, "maxLength must be at least 1"); - assertNotNull(lines, "Lines buffer cannot be NULL"); - assertTrue(linesMax >= 1, "linesMax must be at least 1"); - memoryZero(box, sizeof(uitextbox_t)); - box->text = text; - box->maxLength = maxLength; - box->lines = lines; - box->linesMax = linesMax; -} - -void uiTextboxSetText(uitextbox_t *box, const char_t *text) { - assertNotNull(box, "Textbox cannot be NULL"); - assertNotNull(text, "Text cannot be NULL"); - stringCopy(box->text, text, box->maxLength); - box->currentPage = 0; - box->scroll = 0; - box->layoutWidth = 0.0f; - box->layoutHeight = 0.0f; -} - -void uiTextboxBuildLayout( - uitextbox_t *box, - const float_t width, - const float_t height -) { - assertNotNull(box, "Textbox cannot be NULL"); - - box->layoutWidth = width; - box->layoutHeight = height; - box->lineCount = 0; - box->pageCount = 1; - - float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth; - float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; - - if(fontW <= 0.0f || fontH <= 0.0f) return; - - box->charsPerLine = (int32_t)(width / fontW); - box->linesPerPage = (int32_t)(height / (fontH + UI_TEXTBOX_LINE_SPACING)); - if(box->linesPerPage > UI_TEXTBOX_LINES_PER_PAGE_MAX) { - box->linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX; - } - - if(box->charsPerLine <= 0 || box->linesPerPage <= 0) return; - if(box->text[0] == '\0') return; - - char_t *src = box->text; - int32_t i = 0; - - while(src[i] != '\0' && box->lineCount < (int32_t)box->linesMax) { - if(src[i] == '\t') { - i++; - int32_t rem = box->lineCount % box->linesPerPage; - int32_t pad = rem > 0 ? box->linesPerPage - rem : 0; - while(pad > 0 && box->lineCount < (int32_t)box->linesMax) { - box->lines[box->lineCount].start = i; - box->lines[box->lineCount].count = 0; - box->lineCount++; - pad--; - } - continue; - } - - int32_t lineStart = i; - int32_t lineWidth = 0; - - while(src[i] != '\0') { - char_t c = src[i]; - - if(c == '\n') { i++; break; } - if(c == '\t') break; - - if(c == ' ') { - int32_t wordLen = 0; - int32_t j = i + 1; - while( - src[j] != ' ' && src[j] != '\n' && - src[j] != '\t' && src[j] != '\0' - ) { - wordLen++; - j++; - } - - if(lineWidth > 0 && lineWidth + 1 + wordLen > box->charsPerLine) { - i++; - break; - } - - lineWidth++; - i++; - } else { - if(lineWidth >= box->charsPerLine) break; - lineWidth++; - i++; - } - } - - box->lines[box->lineCount].start = lineStart; - box->lines[box->lineCount].count = lineWidth; - box->lineCount++; - } - - if(box->lineCount == 0) { - box->pageCount = 1; - } else { - box->pageCount = - (box->lineCount + box->linesPerPage - 1) / box->linesPerPage; - } -} - -errorret_t uiTextboxUpdate(uitextbox_t *box) { - assertNotNull(box, "Textbox cannot be NULL"); - -#ifdef DUSK_TIME_DYNAMIC - if(TIME.dynamicUpdate) errorOk(); -#endif - - if(!uiTextboxPageIsComplete(box)) { - box->scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK; - } - - errorOk(); -} - -errorret_t uiTextboxDraw( - uitextbox_t *box, - const float_t x, - const float_t y, - const float_t width, - const float_t height -) { - assertNotNull(box, "Textbox cannot be NULL"); - - float_t startX = (float_t)UI_FRAME_START_X; - float_t startY = (float_t)UI_FRAME_START_Y; - float_t contentX = x + startX; - float_t contentY = y + startY; - float_t contentW = width - 2.0f * startX; - float_t contentH = height - 2.0f * startY; - - if(contentW != box->layoutWidth || contentH != box->layoutHeight) { - uiTextboxBuildLayout(box, contentW, contentH); - } - - errorChain(uiFrameDraw(x, y, width, height)); - errorChain(spriteBatchFlush()); - - if(box->lineCount == 0 || box->text[0] == '\0') errorOk(); - - float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth; - float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; - - shadermaterial_t material = { - .unlit = { - .color = COLOR_WHITE, - .texture = FONT_DEFAULT.texture - } - }; - - int32_t pageFirst = box->currentPage * box->linesPerPage; - int32_t pageLast = pageFirst + box->linesPerPage; - if(pageLast > box->lineCount) pageLast = box->lineCount; - - int32_t charsLeft = box->scroll; - - for(int32_t li = pageFirst; li < pageLast && charsLeft > 0; li++) { - uitextboxline_t *line = &box->lines[li]; - int32_t visible = line->count < charsLeft ? line->count : charsLeft; - float_t lineY = contentY + - (float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING); - - for(int32_t ci = 0; ci < visible; ci++) { - char_t c = box->text[line->start + ci]; - if(c == ' ') continue; - spritebatchsprite_t sprite = textGetSprite( - (vec2){ contentX + (float_t)ci * fontW, lineY }, - c, - &FONT_DEFAULT - ); - errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material)); - } - - charsLeft -= visible; - } - - errorChain(spriteBatchFlush()); - errorOk(); -} - -int32_t uiTextboxGetPageCharCount(const uitextbox_t *box) { - assertNotNull(box, "Textbox cannot be NULL"); - int32_t first = box->currentPage * box->linesPerPage; - int32_t last = first + box->linesPerPage; - if(last > box->lineCount) last = box->lineCount; - int32_t total = 0; - for(int32_t i = first; i < last; i++) { - total += box->lines[i].count; - } - return total; -} - -bool_t uiTextboxPageIsComplete(const uitextbox_t *box) { - assertNotNull(box, "Textbox cannot be NULL"); - return box->scroll >= uiTextboxGetPageCharCount(box); -} - -bool_t uiTextboxHasNextPage(const uitextbox_t *box) { - assertNotNull(box, "Textbox cannot be NULL"); - return box->currentPage + 1 < box->pageCount; -} - -void uiTextboxNextPage(uitextbox_t *box) { - assertNotNull(box, "Textbox cannot be NULL"); - if(!uiTextboxHasNextPage(box)) return; - box->currentPage++; - box->scroll = 0; -} diff --git a/src/dusk/ui/rpg/textbox/uitextbox.h b/src/dusk/ui/rpg/textbox/uitextbox.h deleted file mode 100644 index 047e7e1f..00000000 --- a/src/dusk/ui/rpg/textbox/uitextbox.h +++ /dev/null @@ -1,138 +0,0 @@ -/** - * 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_TEXTBOX_LINES_PER_PAGE_MAX 4 -#define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1 -#define UI_TEXTBOX_LINE_SPACING 0.0f - -typedef struct { - int32_t start; - int32_t count; -} uitextboxline_t; - -typedef struct { - char_t *text; - uint32_t maxLength; - - uitextboxline_t *lines; - uint32_t linesMax; - int32_t lineCount; - int32_t charsPerLine; - int32_t linesPerPage; - int32_t pageCount; - - // last dimensions used for layout; rebuild triggers when these change - float_t layoutWidth; - float_t layoutHeight; - - int32_t currentPage; - int32_t scroll; -} uitextbox_t; - -/** - * Initializes a textbox, zeroing all state and binding it to caller-owned - * text and line storage. - * - * @param box The textbox to initialize. - * @param text Caller-owned buffer the textbox copies its text into. - * @param maxLength Capacity of text, in characters. - * @param lines Caller-owned buffer the textbox lays lines out into. - * @param linesMax Capacity of lines, in entries. - */ -void uiTextboxInit( - uitextbox_t *box, - char_t *text, - const uint32_t maxLength, - uitextboxline_t *lines, - const uint32_t linesMax -); - -/** - * Copies text into the textbox and marks layout as dirty. - * Resets currentPage and scroll to 0. - * - * @param box The textbox to update. - * @param text Null-terminated source string. - */ -void uiTextboxSetText(uitextbox_t *box, const char_t *text); - -/** - * Rebuilds word-wrap and page layout for the given draw dimensions. - * Called automatically by uiTextboxDraw when width or height changes. - * - * @param box The textbox to rebuild. - * @param width Available content width in pixels. - * @param height Available content height in pixels. - */ -void uiTextboxBuildLayout( - uitextbox_t *box, - const float_t width, - const float_t height -); - -/** - * Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK. - * Skipped on dynamic ticks. - * - * @param box The textbox to update. - * @returns Any error that occurs. - */ -errorret_t uiTextboxUpdate(uitextbox_t *box); - -/** - * Draws the textbox frame and visible text. Rebuilds layout automatically - * if width or height differs from the last draw call. - * - * @param box The textbox to draw. - * @param x Screen x position. - * @param y Screen y position. - * @param width Draw width in pixels. - * @param height Draw height in pixels. - * @returns Any error that occurs. - */ -errorret_t uiTextboxDraw( - uitextbox_t *box, - const float_t x, - const float_t y, - const float_t width, - const float_t height -); - -/** - * Returns the total visible char count for the current page. - * - * @param box The textbox to query. - * @returns Total chars on the current page. - */ -int32_t uiTextboxGetPageCharCount(const uitextbox_t *box); - -/** - * Returns true when scroll has fully revealed the current page. - * - * @param box The textbox to query. - * @returns True if the current page is fully visible. - */ -bool_t uiTextboxPageIsComplete(const uitextbox_t *box); - -/** - * Returns true when there is at least one more page after the current one. - * - * @param box The textbox to query. - * @returns True if a next page exists. - */ -bool_t uiTextboxHasNextPage(const uitextbox_t *box); - -/** - * Advances to the next page and resets scroll to 0. - * Has no effect if already on the last page. - * - * @param box The textbox to advance. - */ -void uiTextboxNextPage(uitextbox_t *box); diff --git a/src/dusk/ui/rpg/textbox/uitextboxmain.c b/src/dusk/ui/rpg/textbox/uitextboxmain.c deleted file mode 100644 index 09918b1f..00000000 --- a/src/dusk/ui/rpg/textbox/uitextboxmain.c +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uitextboxmain.h" -#include "ui/focus/uifocus.h" -#include "display/screen/screen.h" -#include "display/text/text.h" -#include "display/color.h" -#include "display/spritebatch/spritebatch.h" -#include "display/shader/shaderunlit.h" -#include "ui/widget/uiframe.h" - -uitextboxmain_t UI_TEXTBOX_MAIN; -static uifocusitem_t *focusItem = NULL; - -errorret_t uiTextboxMainInit(void) { - uiTextboxInit( - &UI_TEXTBOX_MAIN.box, - UI_TEXTBOX_MAIN.text, UI_TEXTBOX_MAIN_TEXT_MAX, - UI_TEXTBOX_MAIN.lines, UI_TEXTBOX_MAIN_LINES_MAX - ); - errorOk(); -} - -void uiTextboxMainSetText(const char_t *text) { - uiTextboxSetText(&UI_TEXTBOX_MAIN.box, text); - if(focusItem != NULL) return; - focusItem = uiFocusPush( - 1, 1, - uiTextboxMainFocusSelected, - NULL, - uiTextboxMainFocusClosed, - NULL, - NULL - ); -} - -errorret_t uiTextboxMainUpdate(void) { - if(focusItem == NULL) errorOk(); - return uiTextboxUpdate(&UI_TEXTBOX_MAIN.box); -} - -errorret_t uiTextboxMainDraw(void) { - if(focusItem == NULL) errorOk(); - float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; - float_t h = (float_t)UI_TEXTBOX_MAIN_LINES * fontH + - (float_t)(UI_TEXTBOX_MAIN_LINES - 1) * UI_TEXTBOX_LINE_SPACING + - 2.0f * (float_t)UI_FRAME_START_Y; - float_t w = (float_t)SCREEN.scanWidth; - float_t x = (float_t)SCREEN.scanX; - float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - h; - errorChain(uiTextboxDraw(&UI_TEXTBOX_MAIN.box, x, y, w, h)); - - if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box)) errorOk(); - - float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth; - float_t contentX = x + (float_t)UI_FRAME_START_X; - float_t contentY = y + (float_t)UI_FRAME_START_Y; - float_t contentW = w - 2.0f * (float_t)UI_FRAME_START_X; - float_t contentH = h - 2.0f * (float_t)UI_FRAME_START_Y; - - shadermaterial_t material = { - .unlit = { - .color = COLOR_WHITE, - .texture = FONT_DEFAULT.texture - } - }; - - spritebatchsprite_t caret = textGetSprite( - (vec2){ - contentX + contentW - fontW, - contentY + contentH - fontH - }, - 'v', - &FONT_DEFAULT - ); - errorChain(spriteBatchBuffer(&caret, 1, &SHADER_UNLIT, material)); - errorChain(spriteBatchFlush()); - - errorOk(); -} - -bool_t uiTextboxMainPageIsComplete(void) { - return uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box); -} - -bool_t uiTextboxMainHasNextPage(void) { - return uiTextboxHasNextPage(&UI_TEXTBOX_MAIN.box); -} - -void uiTextboxMainNextPage(void) { - uiTextboxNextPage(&UI_TEXTBOX_MAIN.box); -} - -bool_t uiTextboxMainIsActive(void) { - return focusItem != NULL; -} - -bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item) { - if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box)) { - UI_TEXTBOX_MAIN.box.scroll = - uiTextboxGetPageCharCount(&UI_TEXTBOX_MAIN.box); - return true; - } - - if(uiTextboxHasNextPage(&UI_TEXTBOX_MAIN.box)) { - uiTextboxNextPage(&UI_TEXTBOX_MAIN.box); - return true; - } - - uiFocusPopItem(focusItem); - return true; -} - -bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item) { - focusItem = NULL; - return true; -} diff --git a/src/dusk/ui/rpg/textbox/uitextboxmain.h b/src/dusk/ui/rpg/textbox/uitextboxmain.h deleted file mode 100644 index e7eb83f6..00000000 --- a/src/dusk/ui/rpg/textbox/uitextboxmain.h +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "ui/rpg/textbox/uitextbox.h" -#include "ui/focus/uifocusitem.h" - -#define UI_TEXTBOX_MAIN_LINES 4 -#define UI_TEXTBOX_MAIN_TEXT_MAX 1024 -#define UI_TEXTBOX_MAIN_LINES_MAX 64 - -typedef struct { - uitextbox_t box; - char_t text[UI_TEXTBOX_MAIN_TEXT_MAX]; - uitextboxline_t lines[UI_TEXTBOX_MAIN_LINES_MAX]; -} uitextboxmain_t; - -extern uitextboxmain_t UI_TEXTBOX_MAIN; - -/** - * Initializes UI_TEXTBOX_MAIN. - * - * @returns Any error that occurs. - */ -errorret_t uiTextboxMainInit(void); - -/** - * Copies text into UI_TEXTBOX_MAIN and resets page and scroll. - * - * @param text Null-terminated source string. - */ -void uiTextboxMainSetText(const char_t *text); - -/** - * Advances the typewriter scroll for UI_TEXTBOX_MAIN. - * - * @returns Any error that occurs. - */ -errorret_t uiTextboxMainUpdate(void); - -/** - * Draws UI_TEXTBOX_MAIN full-width at the bottom of the screen. - * Position and size are derived from SCREEN each call. - * - * @returns Any error that occurs. - */ -errorret_t uiTextboxMainDraw(void); - -/** - * Returns true when the current page is fully scrolled in. - * - * @returns True if the current page is complete. - */ -bool_t uiTextboxMainPageIsComplete(void); - -/** - * Returns true when at least one more page follows the current one. - * - * @returns True if a next page exists. - */ -bool_t uiTextboxMainHasNextPage(void); - -/** - * Advances UI_TEXTBOX_MAIN to the next page and resets scroll. - * Has no effect if already on the last page. - */ -void uiTextboxMainNextPage(void); - -/** - * Returns true when UI_TEXTBOX_MAIN has focus (is visible and active). - * - * @returns True if the textbox is currently active. - */ -bool_t uiTextboxMainIsActive(void); - -/** - * Internal focus callback - skip scroll or advance page or dismiss. - * - * @param item The active focus item. - * @returns True. - */ -bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item); - -/** - * Internal focus callback - clears the focus item pointer on dismiss. - * - * @param item The focus item being closed. - * @returns True. - */ -bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item); diff --git a/src/dusk/ui/rpg/textbox/uitextboxmini.c b/src/dusk/ui/rpg/textbox/uitextboxmini.c deleted file mode 100644 index 8ae38f34..00000000 --- a/src/dusk/ui/rpg/textbox/uitextboxmini.c +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uitextboxmini.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "time/time.h" -#include "rpg/rpgcamera.h" -#include "display/text/text.h" -#include "display/screen/screen.h" -#include "ui/widget/uiframe.h" - -void uiTextboxMiniInit(uitextboxmini_t *mini) { - assertNotNull(mini, "Mini textbox cannot be NULL"); - memoryZero(mini, sizeof(uitextboxmini_t)); - uiTextboxInit( - &mini->box, - mini->text, UI_TEXTBOX_MINI_TEXT_MAX, - mini->lines, UI_TEXTBOX_MINI_LINES_MAX - ); -} - -void uiTextboxMiniShow( - uitextboxmini_t *mini, - const char_t *text, - const vec3 position, - const float_t duration, - uitextboxminiclosedcallback_t closed, - void *user -) { - assertNotNull(mini, "Mini textbox cannot be NULL"); - assertNotNull(text, "Text cannot be NULL"); - uiTextboxSetText(&mini->box, text); - glm_vec3_copy(position, mini->position); - - int32_t textWidth, textHeight; - textMeasure(text, &FONT_DEFAULT, &textWidth, &textHeight); - float_t width = (float_t)textWidth + 2.0f * (float_t)UI_FRAME_START_X; - mini->width = width < UI_TEXTBOX_MINI_WIDTH_MAX - ? width : UI_TEXTBOX_MINI_WIDTH_MAX; - - float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; - float_t maxHeight = (float_t)UI_TEXTBOX_MINI_LINES_MAX * fontH + - (float_t)(UI_TEXTBOX_MINI_LINES_MAX - 1) * UI_TEXTBOX_LINE_SPACING + - 2.0f * (float_t)UI_FRAME_START_Y; - uiTextboxBuildLayout( - &mini->box, - mini->width - 2.0f * (float_t)UI_FRAME_START_X, - maxHeight - 2.0f * (float_t)UI_FRAME_START_Y - ); - int32_t lineCount = mini->box.lineCount > 0 ? mini->box.lineCount : 1; - mini->height = (float_t)lineCount * fontH + - (float_t)(lineCount - 1) * UI_TEXTBOX_LINE_SPACING + - 2.0f * (float_t)UI_FRAME_START_Y; - - mini->active = true; - mini->timer = duration; - mini->closed = closed; - mini->user = user; -} - -errorret_t uiTextboxMiniUpdate(uitextboxmini_t *mini) { - assertNotNull(mini, "Mini textbox cannot be NULL"); - if(!mini->active) errorOk(); - - errorChain(uiTextboxUpdate(&mini->box)); - - if(mini->timer != UI_TEXTBOX_MINI_DURATION_INFINITE) { - mini->timer -= TIME.delta; - if(mini->timer <= 0.0f) uiTextboxMiniClose(mini); - } - - errorOk(); -} - -errorret_t uiTextboxMiniDraw(uitextboxmini_t *mini) { - assertNotNull(mini, "Mini textbox cannot be NULL"); - if(!mini->active) errorOk(); - - vec2 screenPos; - rpgCameraToScreen(mini->position, screenPos); - - if( - mini->clamp == UI_TEXTBOX_MINI_CLAMP_X || - mini->clamp == UI_TEXTBOX_MINI_CLAMP_BOTH - ) { - float_t minX = (float_t)SCREEN.scanX; - float_t maxX = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - mini->width; - if(screenPos[0] < minX) screenPos[0] = minX; - if(screenPos[0] > maxX) screenPos[0] = maxX; - } - - if( - mini->clamp == UI_TEXTBOX_MINI_CLAMP_Y || - mini->clamp == UI_TEXTBOX_MINI_CLAMP_BOTH - ) { - float_t minY = (float_t)SCREEN.scanY; - float_t maxY = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - mini->height; - if(screenPos[1] < minY) screenPos[1] = minY; - if(screenPos[1] > maxY) screenPos[1] = maxY; - } - - return uiTextboxDraw( - &mini->box, screenPos[0], screenPos[1], mini->width, mini->height - ); -} - -bool_t uiTextboxMiniIsActive(const uitextboxmini_t *mini) { - assertNotNull(mini, "Mini textbox cannot be NULL"); - return mini->active; -} - -void uiTextboxMiniClose(uitextboxmini_t *mini) { - assertNotNull(mini, "Mini textbox cannot be NULL"); - if(!mini->active) return; - mini->active = false; - if(mini->closed != NULL) mini->closed(mini); -} diff --git a/src/dusk/ui/rpg/textbox/uitextboxmini.h b/src/dusk/ui/rpg/textbox/uitextboxmini.h deleted file mode 100644 index 16cf3f4b..00000000 --- a/src/dusk/ui/rpg/textbox/uitextboxmini.h +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "ui/rpg/textbox/uitextbox.h" - -#define UI_TEXTBOX_MINI_TEXT_MAX 128 -#define UI_TEXTBOX_MINI_LINES_MAX 2 -#define UI_TEXTBOX_MINI_WIDTH_MAX 160.0f - -// Magic duration value that keeps a mini textbox visible indefinitely -// instead of counting down and auto-closing. -#define UI_TEXTBOX_MINI_DURATION_INFINITE -1.0f - -typedef struct uitextboxmini_s uitextboxmini_t; -typedef void (*uitextboxminiclosedcallback_t)(const uitextboxmini_t *mini); - -/** - * Determines whether a mini textbox's screen position is clamped to stay - * fully on screen. Defaults to UI_TEXTBOX_MINI_CLAMP_NONE. - */ -typedef enum { - UI_TEXTBOX_MINI_CLAMP_NONE, - UI_TEXTBOX_MINI_CLAMP_X, - UI_TEXTBOX_MINI_CLAMP_Y, - UI_TEXTBOX_MINI_CLAMP_BOTH -} uitextboxminiclamp_t; - -typedef struct uitextboxmini_s { - uitextbox_t box; - char_t text[UI_TEXTBOX_MINI_TEXT_MAX]; - uitextboxline_t lines[UI_TEXTBOX_MINI_LINES_MAX]; - - vec3 position; - float_t width; - float_t height; - uitextboxminiclamp_t clamp; - bool_t active; - float_t timer; - - uitextboxminiclosedcallback_t closed; - void *user; -} uitextboxmini_t; - -/** - * Initializes a mini textbox, zeroing all state and binding its internal - * uitextbox_t to its own fixed-size text and line storage. - * - * @param mini The mini textbox to initialize. - */ -void uiTextboxMiniInit(uitextboxmini_t *mini); - -/** - * Shows a mini textbox with the given text at the given world position for - * the given duration. Resets the typewriter scroll, measures the text to - * size the box (width capped at UI_TEXTBOX_MINI_WIDTH_MAX, height derived - * from the resulting wrapped line count), and starts the visibility timer. - * - * @param mini The mini textbox to show. - * @param text Null-terminated source string. - * @param position World-space position the box is anchored to on screen. - * @param duration How long the mini textbox stays visible, in seconds, or - * UI_TEXTBOX_MINI_DURATION_INFINITE to never auto-close. - * @param closed Called once the timer elapses and the box closes. May be - * NULL. - * @param user Opaque pointer passed back through the closed callback. - */ -void uiTextboxMiniShow( - uitextboxmini_t *mini, - const char_t *text, - const vec3 position, - const float_t duration, - uitextboxminiclosedcallback_t closed, - void *user -); - -/** - * Advances the typewriter scroll and counts down the visibility timer. - * Closes the mini textbox and fires its closed callback once the timer - * elapses. Has no effect if not active or if the duration was set to - * UI_TEXTBOX_MINI_DURATION_INFINITE. - * - * @param mini The mini textbox to update. - * @returns Any error that occurs. - */ -errorret_t uiTextboxMiniUpdate(uitextboxmini_t *mini); - -/** - * Draws the mini textbox at its text-measured size, anchored on screen to - * its world-space position via the RPG camera. Has no effect if not - * active. - * - * @param mini The mini textbox to draw. - * @returns Any error that occurs. - */ -errorret_t uiTextboxMiniDraw(uitextboxmini_t *mini); - -/** - * Returns true when the mini textbox is currently visible. - * - * @param mini The mini textbox to query. - * @returns True if active. - */ -bool_t uiTextboxMiniIsActive(const uitextboxmini_t *mini); - -/** - * Immediately closes the mini textbox and fires its closed callback. - * Has no effect if not active. - * - * @param mini The mini textbox to close. - */ -void uiTextboxMiniClose(uitextboxmini_t *mini); diff --git a/src/dusk/ui/rpg/textbox/uitextboxminilist.c b/src/dusk/ui/rpg/textbox/uitextboxminilist.c deleted file mode 100644 index 070bc817..00000000 --- a/src/dusk/ui/rpg/textbox/uitextboxminilist.c +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uitextboxminilist.h" - -uitextboxmini_t UI_TEXTBOX_MINI_LIST[UI_TEXTBOX_MINI_LIST_COUNT]; -uint8_t UI_TEXTBOX_MINI_LIST_NEXT; - -errorret_t uiTextboxMiniListInit(void) { - for(uint8_t i = 0; i < UI_TEXTBOX_MINI_LIST_COUNT; i++) { - uiTextboxMiniInit(&UI_TEXTBOX_MINI_LIST[i]); - } - UI_TEXTBOX_MINI_LIST_NEXT = 0; - errorOk(); -} - -uint8_t uiTextboxMiniListGetNext(void) { - uint8_t index = UI_TEXTBOX_MINI_LIST_NEXT; - UI_TEXTBOX_MINI_LIST_NEXT = - (UI_TEXTBOX_MINI_LIST_NEXT + 1) % UI_TEXTBOX_MINI_LIST_COUNT; - return index; -} - -errorret_t uiTextboxMiniListUpdate(void) { - for(uint8_t i = 0; i < UI_TEXTBOX_MINI_LIST_COUNT; i++) { - errorChain(uiTextboxMiniUpdate(&UI_TEXTBOX_MINI_LIST[i])); - } - errorOk(); -} - -errorret_t uiTextboxMiniListDraw(void) { - for(uint8_t i = 0; i < UI_TEXTBOX_MINI_LIST_COUNT; i++) { - errorChain(uiTextboxMiniDraw(&UI_TEXTBOX_MINI_LIST[i])); - } - errorOk(); -} diff --git a/src/dusk/ui/rpg/textbox/uitextboxminilist.h b/src/dusk/ui/rpg/textbox/uitextboxminilist.h deleted file mode 100644 index f3040b2c..00000000 --- a/src/dusk/ui/rpg/textbox/uitextboxminilist.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "ui/rpg/textbox/uitextboxmini.h" - -#define UI_TEXTBOX_MINI_LIST_COUNT 4 - -extern uitextboxmini_t UI_TEXTBOX_MINI_LIST[UI_TEXTBOX_MINI_LIST_COUNT]; -extern uint8_t UI_TEXTBOX_MINI_LIST_NEXT; - -/** - * Initializes all UI_TEXTBOX_MINI_LIST slots and resets - * UI_TEXTBOX_MINI_LIST_NEXT. - * - * @returns Any error that occurs. - */ -errorret_t uiTextboxMiniListInit(void); - -/** - * Returns the index of the next UI_TEXTBOX_MINI_LIST slot to use, cycling - * through all slots in round-robin order via UI_TEXTBOX_MINI_LIST_NEXT. - * - * @returns The next slot index. - */ -uint8_t uiTextboxMiniListGetNext(void); - -/** - * Updates all UI_TEXTBOX_MINI_LIST slots. - * - * @returns Any error that occurs. - */ -errorret_t uiTextboxMiniListUpdate(void); - -/** - * Draws all active UI_TEXTBOX_MINI_LIST slots. - * - * @returns Any error that occurs. - */ -errorret_t uiTextboxMiniListDraw(void); diff --git a/src/dusk/ui/rpg/uiemoji.c b/src/dusk/ui/rpg/uiemoji.c deleted file mode 100644 index 21a3cbf2..00000000 --- a/src/dusk/ui/rpg/uiemoji.c +++ /dev/null @@ -1,119 +0,0 @@ -/** - * 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 deleted file mode 100644 index 6dc8cc00..00000000 --- a/src/dusk/ui/rpg/uiemoji.h +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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/screen/CMakeLists.txt b/src/dusk/ui/screen/CMakeLists.txt deleted file mode 100644 index 6934dc3b..00000000 --- a/src/dusk/ui/screen/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -add_subdirectory(game) -add_subdirectory(mainmenu) -add_subdirectory(battle) -add_subdirectory(backpack) \ No newline at end of file diff --git a/src/dusk/ui/screen/backpack/CMakeLists.txt b/src/dusk/ui/screen/backpack/CMakeLists.txt deleted file mode 100644 index 570abb53..00000000 --- a/src/dusk/ui/screen/backpack/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uibackpack.c -) diff --git a/src/dusk/ui/screen/backpack/uibackpack.c b/src/dusk/ui/screen/backpack/uibackpack.c deleted file mode 100644 index 269b8f0d..00000000 --- a/src/dusk/ui/screen/backpack/uibackpack.c +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uibackpack.h" -#include "ui/widget/uiframe.h" -#include "rpg/item/backpack.h" -#include "util/memory.h" -#include "util/string.h" -#include "display/spritebatch/spritebatch.h" -#include "display/screen/screen.h" -#include "display/text/text.h" -#include "assert/assert.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -uibackpack_t UI_BACKPACK; - -void uiBackpackTabChanged( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - const itemtype_t type = (itemtype_t)(index + 1); - const inventory_t *inventory = backpackGetInventory(type); - - errorCatch(uiItemListSetItemStacks( - &UI_BACKPACK.itemList, inventory->storage, inventory->storageSize - )); -} - -void uiBackpackTabSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - uiItemListOpen(&UI_BACKPACK.itemList); -} - -errorret_t uiBackpackInit(void) { - memoryZero(&UI_BACKPACK, sizeof(uibackpack_t)); - - errorChain(localeManagerGetString( - "ui.backpack.category_format", - UI_BACKPACK.categoryFormat, - UI_BACKPACK_CATEGORY_FORMAT_MAX, - 0 - )); - - MENU_BEGIN( - &UI_BACKPACK.tabsMenu, UI_BACKPACK.tabs, - uiBackpackTabSelected, NULL, uiBackpackTabChanged - ); - const uint8_t tabCount = ITEM.typeCount - 1; - for(uint8_t i = 0; i < tabCount; i++) { - stringFormat( - UI_BACKPACK.tabLabels[i], UI_BACKPACK_TAB_LABEL_MAX - 1, - UI_BACKPACK.categoryFormat, i + 1 - ); - MENU_TAB(UI_BACKPACK.tabLabels[i]); - } - MENU_END(UI_BACKPACK.tabs, menuIndex); - - uiItemListInit( - &UI_BACKPACK.itemList, - UI_BACKPACK_ITEM_LIST_COLUMNS, UI_BACKPACK_ITEM_LIST_ROWS, 1, - NULL, NULL, NULL, NULL - ); - - errorOk(); -} - -errorret_t uiBackpackDraw(void) { - if(!uiMenuIsActive(&UI_BACKPACK.tabsMenu)) errorOk(); - - const float_t width = SCREEN.width; - const float_t height = SCREEN.height; - const float_t x = (float_t)SCREEN.scanX + - ((float_t)SCREEN.scanWidth - width) * 0.5f; - const float_t y = (float_t)SCREEN.scanY + - ((float_t)SCREEN.scanHeight - height) * 0.5f; - - errorChain(uiFrameDraw(x, y, width, height)); - - const float_t contentX = x + UI_FRAME_START_X; - const float_t contentY = y + UI_FRAME_START_Y; - const float_t contentWidth = width - (UI_FRAME_START_X * 2); - const float_t contentHeight = height - (UI_FRAME_START_Y * 2); - - errorChain(uiMenuDraw( - &UI_BACKPACK.tabsMenu, - contentX, - contentY, - contentWidth, - contentHeight - )); - - const float_t tabsRowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; - const float_t listY = contentY + tabsRowHeight + UI_FRAME_PADDING_Y; - - errorChain( - uiItemListDraw(&UI_BACKPACK.itemList, contentX, listY, contentWidth) - ); - - errorChain(spriteBatchFlush()); - errorOk(); -} - -bool_t uiBackpackIsOpen(void) { - return uiMenuIsActive(&UI_BACKPACK.tabsMenu); -} - -void uiBackpackOpen(void) { - uiMenuOpen(&UI_BACKPACK.tabsMenu); -} - -void uiBackpackClose(void) { - uiMenuClose(&UI_BACKPACK.tabsMenu); -} - -errorret_t uiBackpackDispose(void) { - errorOk(); -} diff --git a/src/dusk/ui/screen/backpack/uibackpack.h b/src/dusk/ui/screen/backpack/uibackpack.h deleted file mode 100644 index f48c076d..00000000 --- a/src/dusk/ui/screen/backpack/uibackpack.h +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uimenu.h" -#include "ui/widget/uiitemlist.h" -#include "rpg/item/item.h" - -#define UI_BACKPACK_TAB_COUNT (ITEM_TYPE_DEF_COUNT_MAX - 1) -#define UI_BACKPACK_TAB_LABEL_MAX 32 -#define UI_BACKPACK_CATEGORY_FORMAT_MAX 32 -#define UI_BACKPACK_ITEM_LIST_COLUMNS 4 -#define UI_BACKPACK_ITEM_LIST_ROWS 5 - -typedef struct { - uimenu_t tabsMenu; - uimenuitem_t tabs[UI_BACKPACK_TAB_COUNT]; - char_t tabLabels[UI_BACKPACK_TAB_COUNT][UI_BACKPACK_TAB_LABEL_MAX]; - char_t categoryFormat[UI_BACKPACK_CATEGORY_FORMAT_MAX]; - - uiitemlist_t itemList; -} uibackpack_t; - -extern uibackpack_t UI_BACKPACK; - -/** - * Initializes the backpack panel and its item type tabs. - * - * @return Any error that occurs. - */ -errorret_t uiBackpackInit(void); - -/** - * Draws the backpack panel. No-op when not visible. - * - * @return Any error that occurs. - */ -errorret_t uiBackpackDraw(void); - -/** - * Returns true when the backpack panel is currently open. - * - * @returns True if open. - */ -bool_t uiBackpackIsOpen(void); - -/** - * Opens the backpack panel. No-op when already open. - */ -void uiBackpackOpen(void); - -/** - * Closes the backpack panel. No-op when already closed. - */ -void uiBackpackClose(void); - -/** - * Disposes of the backpack panel. - * - * @return Any error that occurs. - */ -errorret_t uiBackpackDispose(void); diff --git a/src/dusk/ui/screen/battle/CMakeLists.txt b/src/dusk/ui/screen/battle/CMakeLists.txt deleted file mode 100644 index 1cdef8a0..00000000 --- a/src/dusk/ui/screen/battle/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uibattlemenu.c - uibattlehud.c -) diff --git a/src/dusk/ui/screen/battle/uibattlehud.c b/src/dusk/ui/screen/battle/uibattlehud.c deleted file mode 100644 index 8793a0ac..00000000 --- a/src/dusk/ui/screen/battle/uibattlehud.c +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uibattlehud.h" -#include "rpg/battle/battle.h" -#include "rpg/battle/party.h" -#include "scene/battle/scenebattle.h" -#include "scene/scene.h" -#include "display/text/text.h" -#include "display/color.h" -#include "display/spritebatch/spritebatch.h" -#include "util/string.h" -#include "util/memory.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -#define UI_BATTLE_HUD_TEXT_MAX 32 -#define UI_BATTLE_HUD_LINE_HEIGHT 12.0f - -uibattlehud_t UI_BATTLE_HUD; - -errorret_t uiBattleHudInit(void) { - memoryZero(&UI_BATTLE_HUD, sizeof(uibattlehud_t)); - - errorChain(localeManagerGetString( - "ui.battle.hud.hp_format", - UI_BATTLE_HUD.hpFormat, - UI_BATTLE_HUD_FORMAT_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.battle.hud.mp_format", - UI_BATTLE_HUD.mpFormat, - UI_BATTLE_HUD_FORMAT_MAX, - 0 - )); - - errorOk(); -} - -errorret_t uiBattleHudDraw(void) { - if(BATTLE.state == BATTLE_STATE_NULL) errorOk(); - - scenebattle_t *battle = &SCENE.data.battle; - - for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) { - const battlefighter_t *fighter = BATTLE.fighters[i]; - if(fighter == NULL) continue; - if(fighter->status == BATTLE_FIGHTER_STATUS_NULL) continue; - - vec3 position; - sceneBattleGetFighterPosition(i, position); - glm_vec3_add(position, (vec3){ 0.5f, 1.3f, 0.0f }, position); - - vec2 screenPos; - sceneBattleWorldToScreen(battle, position, screenPos); - - float_t lineY = screenPos[1]; - if(fighter->team == BATTLE_FIGHTER_TEAM_ALLY) { - errorChain(textDraw( - screenPos[0], lineY, partyGetMemberKey(fighter->id), COLOR_WHITE, NULL - )); - lineY += UI_BATTLE_HUD_LINE_HEIGHT; - } - - char_t hpText[UI_BATTLE_HUD_TEXT_MAX]; - stringFormat( - hpText, UI_BATTLE_HUD_TEXT_MAX - 1, UI_BATTLE_HUD.hpFormat, - fighter->stats.health, fighter->stats.healthMax - ); - errorChain(textDraw(screenPos[0], lineY, hpText, COLOR_WHITE, NULL)); - lineY += UI_BATTLE_HUD_LINE_HEIGHT; - - char_t mpText[UI_BATTLE_HUD_TEXT_MAX]; - stringFormat( - mpText, UI_BATTLE_HUD_TEXT_MAX - 1, UI_BATTLE_HUD.mpFormat, - fighter->stats.mp, fighter->stats.mpMax - ); - errorChain(textDraw(screenPos[0], lineY, mpText, COLOR_CYAN, NULL)); - } - - errorChain(spriteBatchFlush()); - errorOk(); -} diff --git a/src/dusk/ui/screen/battle/uibattlehud.h b/src/dusk/ui/screen/battle/uibattlehud.h deleted file mode 100644 index 47c38f17..00000000 --- a/src/dusk/ui/screen/battle/uibattlehud.h +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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_BATTLE_HUD_FORMAT_MAX 32 - -typedef struct { - char_t hpFormat[UI_BATTLE_HUD_FORMAT_MAX]; - char_t mpFormat[UI_BATTLE_HUD_FORMAT_MAX]; -} uibattlehud_t; - -extern uibattlehud_t UI_BATTLE_HUD; - -/** - * Loads the HP/MP format strings from the active locale. - * - * @return Any error that occurs. - */ -errorret_t uiBattleHudInit(void); - -/** - * Draws each living battle fighter's HP/MP as text above its position in - * the battle scene. No-op if no battle is active. - * - * @return An error if drawing failed, or errorOk() if it succeeded. - */ -errorret_t uiBattleHudDraw(void); diff --git a/src/dusk/ui/screen/battle/uibattlemenu.c b/src/dusk/ui/screen/battle/uibattlemenu.c deleted file mode 100644 index bc7bc077..00000000 --- a/src/dusk/ui/screen/battle/uibattlemenu.c +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uibattlemenu.h" -#include "ui/widget/uiframe.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "display/spritebatch/spritebatch.h" -#include "display/screen/screen.h" -#include "display/text/text.h" -#include "display/color.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" -#include "rpg/battle/party.h" - -uibattlemenu_t UI_BATTLE_MENU; - -void uiBattleMenuTargetSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - assertTrue(index < UI_BATTLE_MENU_TARGET_ITEM_COUNT, "index out of bounds"); - battleStateSelectionPlayerAttack(UI_BATTLE_MENU.targetFighterIndex[index]); - uiMenuClose(&UI_BATTLE_MENU.targetMenu); - uiMenuClose(&UI_BATTLE_MENU.actionMenu); -} - -void uiBattleMenuOpenTargets(void) { - battlefighter_t *current = battleStateSelectionGetCurrentFighter(); - if(current == NULL) return; - - const battlefighterteam_t enemyTeam = - current->team == BATTLE_FIGHTER_TEAM_ALLY ? - BATTLE_FIGHTER_TEAM_ENEMY : BATTLE_FIGHTER_TEAM_ALLY; - - MENU_BEGIN( - &UI_BATTLE_MENU.targetMenu, UI_BATTLE_MENU.targetItems, - uiBattleMenuTargetSelected, NULL, NULL - ); - - for(uint8_t i = 0; i < BATTLE_FIGHTER_POS_COUNT; i++) { - battlefighter_t *candidate = BATTLE.fighters[i]; - if(candidate == NULL) continue; - if(candidate->team != enemyTeam) continue; - if(!battleFighterIsAlive(candidate)) continue; - - stringFormat( - UI_BATTLE_MENU.targetLabels[menuIndex], - UI_BATTLE_MENU_TARGET_LABEL_MAX - 1, - UI_BATTLE_MENU.targetFormat, - candidate->id, candidate->stats.health, candidate->stats.healthMax - ); - UI_BATTLE_MENU.targetFighterIndex[menuIndex] = i; - MENU_BUTTON(UI_BATTLE_MENU.targetLabels[menuIndex]); - } - - MENU_END(UI_BATTLE_MENU.targetItems, 1); - - uiMenuOpen(&UI_BATTLE_MENU.targetMenu); -} - -bool_t uiBattleMenuActionCancel(const uimenu_t *menu) { - battleStateSelectionGoBack(); - return true; -} - -void uiBattleMenuActionSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - switch(index) { - case UI_BATTLE_MENU_ACTION_INDEX_ATTACK: - uiBattleMenuOpenTargets(); - break; - - case UI_BATTLE_MENU_ACTION_INDEX_FLEE: - battleStateSelectionPlayerFlee(); - uiMenuClose(&UI_BATTLE_MENU.actionMenu); - break; - - default: - break; - } -} - -errorret_t uiBattleMenuInit(void) { - memoryZero(&UI_BATTLE_MENU, sizeof(uibattlemenu_t)); - - errorChain(localeManagerGetString( - "ui.battle.menu.attack", - UI_BATTLE_MENU.attackLabel, - UI_BATTLE_MENU_ACTION_LABEL_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.battle.menu.flee", - UI_BATTLE_MENU.fleeLabel, - UI_BATTLE_MENU_ACTION_LABEL_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.battle.menu.target_format", - UI_BATTLE_MENU.targetFormat, - UI_BATTLE_MENU_TARGET_FORMAT_MAX, - 0 - )); - - MENU_BEGIN( - &UI_BATTLE_MENU.actionMenu, UI_BATTLE_MENU.actionItems, - uiBattleMenuActionSelected, NULL, NULL - ); - MENU_BUTTON(UI_BATTLE_MENU.attackLabel); - MENU_BUTTON(UI_BATTLE_MENU.fleeLabel); - MENU_END(UI_BATTLE_MENU.actionItems, 1); - - uiMenuSetCancelCallback(&UI_BATTLE_MENU.actionMenu, uiBattleMenuActionCancel); - - errorOk(); -} - -errorret_t uiBattleMenuUpdate(void) { - if(uiMenuIsActive(&UI_BATTLE_MENU.actionMenu)) errorOk(); - - battlefighter_t *current = battleStateSelectionGetCurrentFighter(); - if(current == NULL) errorOk(); - if(current->controller != BATTLE_FIGHTER_CONTROLLER_PLAYER) errorOk(); - - uiMenuOpen(&UI_BATTLE_MENU.actionMenu); - errorOk(); -} - -errorret_t uiBattleMenuDraw(void) { - if(!uiMenuIsActive(&UI_BATTLE_MENU.actionMenu)) errorOk(); - - const float_t width = UI_BATTLE_MENU_WIDTH; - const float_t height = UI_BATTLE_MENU_HEIGHT; - const float_t x = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - width; - const float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - height; - - battlefighter_t *current = battleStateSelectionGetCurrentFighter(); - if(current != NULL) { - errorChain(textDraw( - x, y - UI_BATTLE_MENU_NAME_LINE_HEIGHT, - partyGetMemberKey(current->id), COLOR_WHITE, NULL - )); - } - - errorChain(uiFrameDraw(x, y, width, height)); - errorChain(uiMenuDraw( - &UI_BATTLE_MENU.actionMenu, - x + UI_FRAME_START_X, - y + UI_FRAME_START_Y, - width - (UI_FRAME_START_X * 2), - height - (UI_FRAME_START_Y * 2) - )); - - if(uiMenuIsActive(&UI_BATTLE_MENU.targetMenu)) { - const float_t targetY = y - height; - errorChain(uiFrameDraw(x, targetY, width, height)); - errorChain(uiMenuDraw( - &UI_BATTLE_MENU.targetMenu, - x + UI_FRAME_START_X, - targetY + UI_FRAME_START_Y, - width - (UI_FRAME_START_X * 2), - height - (UI_FRAME_START_Y * 2) - )); - } - - errorChain(spriteBatchFlush()); - errorOk(); -} diff --git a/src/dusk/ui/screen/battle/uibattlemenu.h b/src/dusk/ui/screen/battle/uibattlemenu.h deleted file mode 100644 index 6d531a74..00000000 --- a/src/dusk/ui/screen/battle/uibattlemenu.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uimenu.h" -#include "rpg/battle/battle.h" - -#define UI_BATTLE_MENU_ACTION_ITEM_COUNT 2 -#define UI_BATTLE_MENU_ACTION_INDEX_ATTACK 0 -#define UI_BATTLE_MENU_ACTION_INDEX_FLEE 1 -#define UI_BATTLE_MENU_ACTION_LABEL_MAX 32 - -#define UI_BATTLE_MENU_TARGET_ITEM_COUNT BATTLE_FIGHTER_POS_COUNT -#define UI_BATTLE_MENU_TARGET_LABEL_MAX 32 -#define UI_BATTLE_MENU_TARGET_FORMAT_MAX 48 - -#define UI_BATTLE_MENU_WIDTH 160.0f -#define UI_BATTLE_MENU_HEIGHT 96.0f -#define UI_BATTLE_MENU_NAME_LINE_HEIGHT 12.0f - -typedef struct { - uimenu_t actionMenu; - uimenuitem_t actionItems[UI_BATTLE_MENU_ACTION_ITEM_COUNT]; - char_t attackLabel[UI_BATTLE_MENU_ACTION_LABEL_MAX]; - char_t fleeLabel[UI_BATTLE_MENU_ACTION_LABEL_MAX]; - - uimenu_t targetMenu; - uimenuitem_t targetItems[UI_BATTLE_MENU_TARGET_ITEM_COUNT]; - uint8_t targetFighterIndex[UI_BATTLE_MENU_TARGET_ITEM_COUNT]; - char_t targetLabels[UI_BATTLE_MENU_TARGET_ITEM_COUNT] - [UI_BATTLE_MENU_TARGET_LABEL_MAX]; - char_t targetFormat[UI_BATTLE_MENU_TARGET_FORMAT_MAX]; -} uibattlemenu_t; - -extern uibattlemenu_t UI_BATTLE_MENU; - -/** - * Initializes the battle action/target menus. - * - * @return Any error that occurs. - */ -errorret_t uiBattleMenuInit(void); - -/** - * Updates the battle menu: opens the action menu whenever it becomes a - * player-controlled fighter's turn. - * - * @return Any error that occurs. - */ -errorret_t uiBattleMenuUpdate(void); - -/** - * Draws the battle action menu, and the target menu above it when - * open. No-op when the action menu isn't active. - * - * @return Any error that occurs. - */ -errorret_t uiBattleMenuDraw(void); diff --git a/src/dusk/ui/screen/game/CMakeLists.txt b/src/dusk/ui/screen/game/CMakeLists.txt deleted file mode 100644 index 4b79eda6..00000000 --- a/src/dusk/ui/screen/game/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uigamemenu.c -) diff --git a/src/dusk/ui/screen/game/uigamemenu.c b/src/dusk/ui/screen/game/uigamemenu.c deleted file mode 100644 index dbe26561..00000000 --- a/src/dusk/ui/screen/game/uigamemenu.c +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uigamemenu.h" -#include "ui/widget/uiframe.h" -#include "ui/dialog/uiconfirm.h" -#include "ui/screen/backpack/uibackpack.h" -#include "ui/rpg/textbox/uitextboxmain.h" -#include "util/memory.h" -#include "display/spritebatch/spritebatch.h" -#include "display/screen/screen.h" -#include "assert/assert.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" -#include "error/error.h" -#include "util/string.h" - -#define UI_GAME_MENU_INDEX_CHARACTERS 0 -#define UI_GAME_MENU_INDEX_ITEMS 1 - -uigamemenu_t UI_GAME_MENU; - -void uiGameMenuSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - switch(index) { - case UI_GAME_MENU_INDEX_ITEMS: - uiBackpackOpen(); - break; - default: - assertUnreachable("Unhandled game menu index"); - } -} - -errorret_t uiGameMenuInit(void) { - memoryZero(&UI_GAME_MENU, sizeof(uigamemenu_t)); - - errorChain(localeManagerGetString( - "ui.game_menu.characters", - UI_GAME_MENU.charactersLabel, - UI_GAME_MENU_LABEL_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.game_menu.items", - UI_GAME_MENU.itemsLabel, - UI_GAME_MENU_LABEL_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.game_menu.settings", - UI_GAME_MENU.settingsLabel, - UI_GAME_MENU_LABEL_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.game_menu.save_success", - UI_GAME_MENU.saveSuccessText, - UI_GAME_MENU_MESSAGE_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.game_menu.save_cancelled", - UI_GAME_MENU.saveCancelledText, - UI_GAME_MENU_MESSAGE_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.game_menu.save_unavailable", - UI_GAME_MENU.saveUnavailableText, - UI_GAME_MENU_MESSAGE_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.game_menu.save_temporary", - UI_GAME_MENU.saveTemporaryText, - UI_GAME_MENU_MESSAGE_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.game_menu.save_create_confirm", - UI_GAME_MENU.saveCreateConfirmText, - UI_GAME_MENU_MESSAGE_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.game_menu.save_failed_format", - UI_GAME_MENU.saveFailedFormat, - UI_GAME_MENU_MESSAGE_MAX, - 0 - )); - errorChain(localeManagerGetString( - "ui.game_menu.save_check_failed_format", - UI_GAME_MENU.saveCheckFailedFormat, - UI_GAME_MENU_MESSAGE_MAX, - 0 - )); - - MENU_BEGIN( - &UI_GAME_MENU.menu, UI_GAME_MENU.items, uiGameMenuSelected, NULL, NULL - ); - MENU_BUTTON(UI_GAME_MENU.charactersLabel); - MENU_BUTTON(UI_GAME_MENU.itemsLabel); - MENU_BUTTON(UI_GAME_MENU.saveLabel); - - MENU_END(UI_GAME_MENU.items, 1); - - errorOk(); -} - -errorret_t uiGameMenuDraw(void) { - if(!uiMenuIsActive(&UI_GAME_MENU.menu)) errorOk(); - - const float_t width = UI_GAME_MENU_WIDTH; - const float_t height = (float_t)SCREEN.scanHeight; - const float_t x = (float_t)(SCREEN.scanX + SCREEN.scanWidth) - width; - const float_t y = (float_t)SCREEN.scanY; - - errorChain(uiFrameDraw(x, y, width, height)); - errorChain(uiMenuDraw( - &UI_GAME_MENU.menu, - x + UI_FRAME_START_X, - y + UI_FRAME_START_Y, - width - (UI_FRAME_START_X * 2), - height - (UI_FRAME_START_Y * 2) - )); - - errorChain(spriteBatchFlush()); - errorOk(); -} - -bool_t uiGameMenuIsOpen(void) { - return uiMenuIsActive(&UI_GAME_MENU.menu); -} - -void uiGameMenuOpen() { - uiMenuOpen(&UI_GAME_MENU.menu); -} - -void uiGameMenuClose() { - uiMenuClose(&UI_GAME_MENU.menu); -} - -errorret_t uiGameMenuDispose(void) { - errorOk(); -} diff --git a/src/dusk/ui/screen/game/uigamemenu.h b/src/dusk/ui/screen/game/uigamemenu.h deleted file mode 100644 index 87ec2a4e..00000000 --- a/src/dusk/ui/screen/game/uigamemenu.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uimenu.h" - -#define UI_GAME_MENU_ITEM_COUNT 4 -#define UI_GAME_MENU_WIDTH 150.0f -#define UI_GAME_MENU_LABEL_MAX 32 -#define UI_GAME_MENU_MESSAGE_MAX 192 - -typedef struct { - uimenu_t menu; - uimenuitem_t items[UI_GAME_MENU_ITEM_COUNT]; - char_t charactersLabel[UI_GAME_MENU_LABEL_MAX]; - char_t itemsLabel[UI_GAME_MENU_LABEL_MAX]; - char_t settingsLabel[UI_GAME_MENU_LABEL_MAX]; - char_t saveLabel[UI_GAME_MENU_LABEL_MAX]; - char_t saveSuccessText[UI_GAME_MENU_MESSAGE_MAX]; - char_t saveCancelledText[UI_GAME_MENU_MESSAGE_MAX]; - char_t saveUnavailableText[UI_GAME_MENU_MESSAGE_MAX]; - char_t saveTemporaryText[UI_GAME_MENU_MESSAGE_MAX]; - char_t saveCreateConfirmText[UI_GAME_MENU_MESSAGE_MAX]; - char_t saveFailedFormat[UI_GAME_MENU_MESSAGE_MAX]; - char_t saveCheckFailedFormat[UI_GAME_MENU_MESSAGE_MAX]; -} uigamemenu_t; - -extern uigamemenu_t UI_GAME_MENU; - -/** - * Initializes the game menu panel. - * - * @return Any error that occurs. - */ -errorret_t uiGameMenuInit(void); - -/** - * Draws the game menu panel. No-op when not visible. - * - * @return Any error that occurs. - */ -errorret_t uiGameMenuDraw(void); - -/** - * Returns true when the game menu panel is currently open. - * - * @returns True if open. - */ -bool_t uiGameMenuIsOpen(void); - -/** - * Opens the game menu panel. No-op when already open. - */ -void uiGameMenuOpen(); - -/** - * Closes the game menu panel. No-op when already closed. - */ -void uiGameMenuClose(); - -/** - * Disposes of the game menu panel. - * - * @return Any error that occurs. - */ -errorret_t uiGameMenuDispose(void); diff --git a/src/dusk/ui/screen/mainmenu/CMakeLists.txt b/src/dusk/ui/screen/mainmenu/CMakeLists.txt deleted file mode 100644 index 2231cb11..00000000 --- a/src/dusk/ui/screen/mainmenu/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uimainmenu.c -) diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.c b/src/dusk/ui/screen/mainmenu/uimainmenu.c deleted file mode 100644 index 168a1d63..00000000 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.c +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uimainmenu.h" -#include "ui/widget/uiframe.h" -#include "util/memory.h" -#include "display/spritebatch/spritebatch.h" -#include "display/screen/screen.h" -#include "assert/assert.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" -#include "ui/dialog/save/uiselectsave.h" -#include "ui/overlay/uifatalerror.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "scene/scene.h" -#include "asset/asset.h" -#include "save/save.h" -#include "rpg/overworld/map.h" - -#define UI_MAIN_MENU_INDEX_START_GAME 0 -#define UI_MAIN_MENU_INDEX_OPTIONS 1 -#define UI_MAIN_MENU_INDEX_QUIT 2 - -uimainmenu_t UI_MAIN_MENU; - -cutscene_t *uiMainMenuLoadCutscene(void) { - return cutsceneCutsceneResolve("main_menu"); -} - -void uiMainMenuSelectSaveResult(const uint8_t slotIndex, void *user) { - if(slotIndex == UI_SELECT_SAVE_RESULT_NONE) { - cutsceneSystemLoad("cutscenes/main_menu.jsonc"); - return; - } - - SAVE.slotCurrent = slotIndex; - errorret_t initResult = saveSlotInit(&SAVE.slot); - if(errorIsNotOk(initResult)) { - errorCatch(errorPrint(initResult)); - uiFatalErrorOpen(initResult.state->message); - return; - } - - errorret_t result = saveLoadSlot(); - if(errorIsNotOk(result)) { - errorCatch(errorPrint(result)); - uiFatalErrorOpen(result.state->message); - return; - } - - assertStrLenMin( - SAVE.slot.cachedData.mapName, 1, "Save slot has no map name" - ); - errorret_t mapResult = mapSetMap(SAVE.slot.cachedData.mapName); - if(errorIsNotOk(mapResult)) { - errorCatch(errorPrint(mapResult)); - uiFatalErrorOpen(mapResult.state->message); - return; - } - - sceneSet(SCENE_TYPE_OVERWORLD); - - if(SAVE.slot.currentCutscene[0] != '\0') { - cutsceneSystemLoad(SAVE.slot.currentCutscene); - } -} - -void uiMainMenuOpenSelectSave(void *userData) { - uiSelectSaveOpen( - UI_SELECT_SAVE_TYPE_LOAD, uiMainMenuSelectSaveResult, NULL - ); -} - -void uiMainMenuStartGame(void) { - if(CUTSCENE_SYSTEM.scene == NULL) { - cutscene_t *cutscene = uiMainMenuLoadCutscene(); - if(cutscene == NULL) return; - cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "NEW_GAME"); - } else { - cutsceneSystemGoTo("NEW_GAME"); - } - - cutsceneSystemSetOnComplete(uiMainMenuOpenSelectSave); -} - -void uiMainMenuOptions(void) { - if(CUTSCENE_SYSTEM.scene == NULL) { - cutscene_t *cutscene = uiMainMenuLoadCutscene(); - if(cutscene == NULL) return; - cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "OPTIONS"); - } else { - cutsceneSystemGoTo("OPTIONS"); - } -} - -void uiMainMenuQuit(void) { - if(CUTSCENE_SYSTEM.scene == NULL) { - cutscene_t *cutscene = uiMainMenuLoadCutscene(); - if(cutscene == NULL) return; - cutsceneSystemStartCutsceneAndGoToMarker(cutscene, "QUIT"); - } else { - cutsceneSystemGoTo("QUIT"); - } -} - -void uiMainMenuSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - - switch(index) { - case UI_MAIN_MENU_INDEX_START_GAME: - uiMenuClose(&UI_MAIN_MENU.menu); - uiMainMenuStartGame(); - break; - - case UI_MAIN_MENU_INDEX_OPTIONS: - uiMainMenuOptions(); - break; - - case UI_MAIN_MENU_INDEX_QUIT: - uiMenuClose(&UI_MAIN_MENU.menu); - uiMainMenuQuit(); - break; - - default: - break; - } -} - -errorret_t uiMainMenuInit(void) { - memoryZero(&UI_MAIN_MENU, sizeof(uimainmenu_t)); - - errorChain(localeManagerGetString( - "main_menu.start_game", - UI_MAIN_MENU.startGameLabel, - UI_MAIN_MENU_LABEL_MAX, - 0 - )); - errorChain(localeManagerGetString( - "main_menu.options", - UI_MAIN_MENU.optionsLabel, - UI_MAIN_MENU_LABEL_MAX, - 0 - )); - errorChain(localeManagerGetString( - "main_menu.quit", - UI_MAIN_MENU.quitLabel, - UI_MAIN_MENU_LABEL_MAX, - 0 - )); - - MENU_BEGIN( - &UI_MAIN_MENU.menu, UI_MAIN_MENU.items, uiMainMenuSelected, NULL, NULL - ); - MENU_BUTTON(UI_MAIN_MENU.startGameLabel); - MENU_BUTTON(UI_MAIN_MENU.optionsLabel); - MENU_BUTTON(UI_MAIN_MENU.quitLabel); - MENU_END(UI_MAIN_MENU.items, 1); - uiMenuSetDisableBack(&UI_MAIN_MENU.menu, true); - - errorOk(); -} - -void uiMainMenuOpen(void) { - if(uiMenuIsActive(&UI_MAIN_MENU.menu)) return; - uiMenuOpen(&UI_MAIN_MENU.menu); -} - -errorret_t uiMainMenuDraw(void) { - if(!uiMenuIsActive(&UI_MAIN_MENU.menu)) errorOk(); - - const float_t width = UI_MAIN_MENU_WIDTH; - const float_t height = UI_MAIN_MENU_HEIGHT; - const float_t x = - (float_t)SCREEN.scanX + ((float_t)SCREEN.scanWidth - width) * 0.5f; - const float_t y = - (float_t)SCREEN.scanY + ((float_t)SCREEN.scanHeight - height) * 0.5f; - - errorChain(uiFrameDraw(x, y, width, height)); - errorChain(uiMenuDraw( - &UI_MAIN_MENU.menu, - x + UI_FRAME_START_X, - y + UI_FRAME_START_Y, - width - (UI_FRAME_START_X * 2), - height - (UI_FRAME_START_Y * 2) - )); - - errorChain(spriteBatchFlush()); - errorOk(); -} - -errorret_t uiMainMenuDispose(void) { - errorOk(); -} diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.h b/src/dusk/ui/screen/mainmenu/uimainmenu.h deleted file mode 100644 index 82f9b9cb..00000000 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.h +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uimenu.h" - -typedef struct cutscene_s cutscene_t; - -#define UI_MAIN_MENU_ITEM_COUNT 3 -#define UI_MAIN_MENU_WIDTH 200.0f -#define UI_MAIN_MENU_HEIGHT 160.0f -#define UI_MAIN_MENU_LABEL_MAX 32 - -typedef struct { - uimenu_t menu; - uimenuitem_t items[UI_MAIN_MENU_ITEM_COUNT]; - char_t startGameLabel[UI_MAIN_MENU_LABEL_MAX]; - char_t optionsLabel[UI_MAIN_MENU_LABEL_MAX]; - char_t quitLabel[UI_MAIN_MENU_LABEL_MAX]; -} uimainmenu_t; - -extern uimainmenu_t UI_MAIN_MENU; - -/** - * Initializes the main menu panel. - * - * @return Any error that occurs. - */ -errorret_t uiMainMenuInit(void); - -/** - * Opens the main menu panel. No-op when already open. - */ -void uiMainMenuOpen(void); - -/** - * Loads (or re-loads) the main menu cutscene asset (assets/cutscenes/ - * main_menu.jsonc, parsed directly at load time rather than compiled in) - * and returns it - the shared accessor for the "Start Game"/Options/Quit - * flows below to jump/restart it. Only one JSON-authored cutscene is ever - * resident at a time (see CUTSCENE_LOADED_ITEMS_MAX's doc comment in - * cutscenesystem.h), so this resolves fresh on every call rather than - * caching anything. - * - * @return The main menu cutscene, or NULL on failure (already reported via - * uiFatalErrorOpen). - */ -cutscene_t *uiMainMenuLoadCutscene(void); - -/** - * Starts the "Start Game" flow: jumps the already-running main menu - * cutscene to its NEW_GAME marker (or restarts it there if it already ran - * to completion, e.g. the player backed out of the save picker back to - * this menu) to load every save slot - retrying on error, prompting if no - * save device is found - then arms uiMainMenuOpenSelectSave to open the - * load-game picker once that succeeds. Called when the player selects - * Start Game. - */ -void uiMainMenuStartGame(void); - -/** - * Cutscene completion callback armed by uiMainMenuStartGame: opens the - * load-game picker once the NEW_GAME flow has finished loading every save - * slot. - * - * @param userData Unused - required by cutscenecallback_t's signature. - */ -void uiMainMenuOpenSelectSave(void *userData); - -/** - * Result callback for the load-game picker opened by - * uiMainMenuOpenSelectSave: re-opens this panel if backed out of without - * choosing a slot, otherwise starts the game with the chosen slot. - * - * @param slotIndex The chosen slot, or UI_SELECT_SAVE_RESULT_NONE. - * @param user Unused - required by uiselectsaveresultcallback_t's signature. - */ -void uiMainMenuSelectSaveResult(const uint8_t slotIndex, void *user); - -/** - * Draws the main menu panel, centered on screen. No-op when not active. - * - * @return Any error that occurs. - */ -errorret_t uiMainMenuDraw(void); - -/** - * Disposes of the main menu panel. - * - * @return Any error that occurs. - */ -errorret_t uiMainMenuDispose(void); diff --git a/src/dusk/ui/transition/CMakeLists.txt b/src/dusk/ui/transition/CMakeLists.txt deleted file mode 100644 index 0e365038..00000000 --- a/src/dusk/ui/transition/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uitransition.c - uitransitionfade.c -) diff --git a/src/dusk/ui/transition/uitransition.c b/src/dusk/ui/transition/uitransition.c deleted file mode 100644 index 32f5dce9..00000000 --- a/src/dusk/ui/transition/uitransition.c +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uitransition.h" -#include "uitransitionfade.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "time/time.h" -#include "util/math.h" - -uitransition_t UI_TRANSITION; - -uitransitiondrawfunc_t UI_TRANSITION_TYPE_DRAW[UI_TRANSITION_TYPE_COUNT] = { - [UI_TRANSITION_TYPE_FADE] = uiTransitionFadeDraw -}; - -errorret_t uiTransitionInit(void) { - memoryZero(&UI_TRANSITION, sizeof(uitransition_t)); - - // uiTransitionStart((uitransitionstartparams_t){ - // .duration = 5.0f, - // .type = UI_TRANSITION_TYPE_FADE, - // .params = { - // .fade = { - // .easing = EASING_OUT_QUART, - // .fromColor = COLOR_TRANSPARENT_BLACK, - // .toColor = COLOR_BLACK - // } - // }, - // .finished = NULL, - // .user = NULL - // }); - - errorOk(); -} - -errorret_t uiTransitionUpdate(void) { - // Update time. - float_t newTime = UI_TRANSITION.data.time + TIME.delta; - - UI_TRANSITION.data.time = mathClamp( - newTime, - 0.0f, - UI_TRANSITION.data.duration - ); - UI_TRANSITION.data.t = ( - (UI_TRANSITION.data.duration <= 0.0f) ? - 1.0f : - (UI_TRANSITION.data.time / UI_TRANSITION.data.duration) - ); - - if(UI_TRANSITION.data.t >= 1.0f && UI_TRANSITION.finished) { - // Saved and cleared before firing so a callback that immediately - // starts another transition isn't clobbered by this function's own - // cleanup running after it - same reentrancy hazard as uiFocusPop, - // see src/dusk/ui/focus/uifocus.c. Without this, finished would also - // just keep re-firing every frame forever once t reaches 1.0. - uitransitioncallback_t finished = UI_TRANSITION.finished; - void *user = UI_TRANSITION.user; - UI_TRANSITION.finished = NULL; - finished(user); - } - - errorOk(); -} - -errorret_t uiTransitionDraw(void) { - uitransitiondrawfunc_t draw = UI_TRANSITION_TYPE_DRAW[UI_TRANSITION.type]; - if(!draw) errorOk(); - return draw(&UI_TRANSITION.data); -} - -void uiTransitionStart(const uitransitionstartparams_t info) { - assertTrue(info.type > UI_TRANSITION_TYPE_NULL, "Invalid type"); - assertTrue(info.type < UI_TRANSITION_TYPE_COUNT, "Invalid type"); - assertTrue(info.duration >= 0.0f, "Duration must be non-negative"); - - memoryZero(&UI_TRANSITION, sizeof(uitransition_t)); - - UI_TRANSITION.data.duration = info.duration; - UI_TRANSITION.type = info.type; - UI_TRANSITION.data.params = info.params; - UI_TRANSITION.finished = info.finished; - UI_TRANSITION.user = info.user; -} \ No newline at end of file diff --git a/src/dusk/ui/transition/uitransition.h b/src/dusk/ui/transition/uitransition.h deleted file mode 100644 index 305ec5c3..00000000 --- a/src/dusk/ui/transition/uitransition.h +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "display/color.h" -#include "animation/easing.h" -#include "uitransitiondata.h" - -typedef void (*uitransitioncallback_t)(void *user); - -typedef enum { - UI_TRANSITION_TYPE_NULL, - - UI_TRANSITION_TYPE_FADE, - - UI_TRANSITION_TYPE_COUNT -} uitransitiontype_t; - -extern uitransitiondrawfunc_t UI_TRANSITION_TYPE_DRAW[UI_TRANSITION_TYPE_COUNT]; - - -typedef struct { - uitransitiondata_t data; - uitransitiontype_t type; - uitransitioncallback_t finished; - void *user; -} uitransition_t; - -extern uitransition_t UI_TRANSITION; - -/** - * Initializes the transition overlay, zeroing all fields and wiring the - * onTransitionEnd event. - * - * @return Any error that occurs. - */ -errorret_t uiTransitionInit(void); - -/** - * Advances the transition. Fires onTransitionEnd once when it completes. - * Safe to call when no transition is running. - * - * @return Any error that occurs. - */ -errorret_t uiTransitionUpdate(void); - -/** - * Renders the transition overlay. Skipped entirely when the current alpha - * is zero. - * - * @return Any error that occurs. - */ -errorret_t uiTransitionDraw(void); - -typedef struct { - float_t duration; - uitransitiontype_t type; - uitransitionparams_t params; - uitransitioncallback_t finished; - void *user; -} uitransitionstartparams_t; - -/** - * Starts a transition described by an info struct. - * - * @param info Transition parameters. - */ -void uiTransitionStart(const uitransitionstartparams_t info); - diff --git a/src/dusk/ui/transition/uitransitiondata.h b/src/dusk/ui/transition/uitransitiondata.h deleted file mode 100644 index 37e8430f..00000000 --- a/src/dusk/ui/transition/uitransitiondata.h +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "uitransitionfade.h" - -typedef union uitransitionparams_u { - uitransitionfade_t fade; -} uitransitionparams_t; - -typedef struct uitransitiondata_s { - float_t time; - float_t duration; - float_t t; - uitransitionparams_t params; -} uitransitiondata_t; - -typedef errorret_t (*uitransitiondrawfunc_t)(const uitransitiondata_t *data); diff --git a/src/dusk/ui/transition/uitransitionfade.c b/src/dusk/ui/transition/uitransitionfade.c deleted file mode 100644 index 45e3168d..00000000 --- a/src/dusk/ui/transition/uitransitionfade.c +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uitransition.h" -#include "assert/assert.h" -#include "display/screen/screen.h" -#include "display/texture/texture.h" -#include "display/spritebatch/spritebatch.h" -#include "display/shader/shaderunlit.h" - -errorret_t uiTransitionFadeDraw(const uitransitiondata_t *data) { - assertNotNull(data, "data must not be NULL"); - - float_t e = easingApply(data->params.fade.easing, data->t); - - float_t r = (float_t)( - data->params.fade.toColor.r - data->params.fade.fromColor.r - ) * e; - float_t g = (float_t)( - data->params.fade.toColor.g - data->params.fade.fromColor.g - ) * e; - float_t b = (float_t)( - data->params.fade.toColor.b - data->params.fade.fromColor.b - ) * e; - float_t a = (float_t)( - data->params.fade.toColor.a - data->params.fade.fromColor.a - ) * e; - - color_t color = color4b( - data->params.fade.fromColor.r + (uint8_t)r, - data->params.fade.fromColor.g + (uint8_t)g, - data->params.fade.fromColor.b + (uint8_t)b, - data->params.fade.fromColor.a + (uint8_t)a - ); - - if(color.a == 0) errorOk(); - - spritebatchsprite_t sprite = { - .min = { 0.0f, 0.0f, 0.0f }, - .max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - shadermaterial_t material = { - .unlit = { - .color = color, - .texture = NULL - } - }; - errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material)); - return spriteBatchFlush(); -} diff --git a/src/dusk/ui/transition/uitransitionfade.h b/src/dusk/ui/transition/uitransitionfade.h deleted file mode 100644 index a1b2475a..00000000 --- a/src/dusk/ui/transition/uitransitionfade.h +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "display/color.h" -#include "animation/easing.h" - -typedef struct uitransitiondata_s uitransitiondata_t; - -typedef struct { - color_t fromColor; - color_t toColor; - easingtype_t easing; -} uitransitionfade_t; - -/** - * Renders a full-screen color overlay interpolated between fade->fromColor - * and fade->toColor using fade->easing. t is the raw normalized progress - * in [0, 1] before easing is applied. No-op when the resulting alpha is zero. - * - * @param fade The fade parameters. - * @return Any error that occurs. - */ -errorret_t uiTransitionFadeDraw(const uitransitiondata_t *fade); diff --git a/src/dusk/ui/ui.c b/src/dusk/ui/ui.c deleted file mode 100644 index 2cd586d1..00000000 --- a/src/dusk/ui/ui.c +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "ui.h" -#include "util/memory.h" -#include "assert/assert.h" -#include "display/spritebatch/spritebatch.h" -#include "display/screen/screen.h" -#include "ui/uielement.h" -#include "ui/uielementlist.h" -#include "ui/focus/uifocus.h" -#include "log/log.h" - -ui_t UI; - -errorret_t uiInit(void) { - memoryZero(&UI, sizeof(ui_t)); - uiFocusInit(); - - uielement_t *element = &UI_ELEMENTS[0]; - while(!uiElementIsNull(element)) { - errorChain(uiElementInit(element)); - element++; - } - - errorOk(); -} - -errorret_t uiUpdate(void) { - uiFocusUpdate(); - - uielement_t *element = &UI_ELEMENTS[0]; - while(!uiElementIsNull(element)) { - errorChain(uiElementUpdate(element)); - element++; - } - - errorOk(); -} - -errorret_t uiRender(void) { - const uielement_t *element = &UI_ELEMENTS[0]; - while(!uiElementIsNull(element)) { - errorChain(uiElementDraw(element)); - - if(SPRITEBATCH.spriteCount > 0) { - logDebug("Finished UI element but unflushed sprites remain.\n"); - } - - element++; - } - - errorOk(); -} - -errorret_t uiDispose(void) { - uielement_t *element = &UI_ELEMENTS[0]; - while(!uiElementIsNull(element)) { - errorChain(uiElementDispose(element)); - element++; - } - errorOk(); -} - -errorret_t uiUpdateTranslations(void) { - uielement_t *element = &UI_ELEMENTS[0]; - while(!uiElementIsNull(element)) { - errorChain(uiElementUpdateTranslations(element)); - element++; - } - errorOk(); -} \ No newline at end of file diff --git a/src/dusk/ui/ui.h b/src/dusk/ui/ui.h deleted file mode 100644 index 8d6a88ad..00000000 --- a/src/dusk/ui/ui.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef struct { - void *nothing; -} ui_t; - -extern ui_t UI; - -/** - * Initializes the UI system. - */ -errorret_t uiInit(void); - -/** - * Updates the UI system. - * - * @return Any error that occurs. - */ -errorret_t uiUpdate(void); - -/** - * Renders the UI system. - * - * @return Any error that occurs. - */ -errorret_t uiRender(void); - -/** - * Disposes of the UI system. - * - * @return Any error that occurs. - */ -errorret_t uiDispose(void); - -/** - * Notifies the UI system that the active locale has changed, so every - * registered element can re-fetch any localized strings it caches. - * - * @return Any error that occurs. - */ -errorret_t uiUpdateTranslations(void); \ No newline at end of file diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c deleted file mode 100644 index af4c8cf3..00000000 --- a/src/dusk/ui/uielement.c +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uielement.h" -#include "assert/assert.h" -#include "ui/widget/uiframe.h" -#include "engine/engine.h" - -bool_t uiElementIsNull(const uielement_t *element) { - return element->init == NULL && - element->update == NULL && - element->draw == NULL && - element->dispose == NULL && - element->updateTranslations == NULL; -} - -errorret_t uiElementInit(uielement_t *element) { - assertNotNull(element, "element must not be NULL"); - errorChain(uiFrameInit()); - if(element->init != NULL) errorChain(element->init()); - errorOk(); -} - -errorret_t uiElementUpdate(uielement_t *element) { - assertNotNull(element, "element must not be NULL"); - if(element->update != NULL) errorChain(element->update()); - errorOk(); -} - -errorret_t uiElementDraw(const uielement_t *element) { - assertNotNull(element, "element must not be NULL"); - if(element->draw != NULL) errorChain(element->draw()); - errorOk(); -} - -errorret_t uiElementDispose(uielement_t *element) { - assertNotNull(element, "element must not be NULL"); - if(element->dispose != NULL) errorChain(element->dispose()); - errorChain(uiFrameDispose()); - errorOk(); -} - -errorret_t uiElementUpdateTranslations(uielement_t *element) { - assertNotNull(element, "element must not be NULL"); - if(element->updateTranslations != NULL) { - errorChain(element->updateTranslations()); - } - errorOk(); -} \ No newline at end of file diff --git a/src/dusk/ui/uielement.h b/src/dusk/ui/uielement.h deleted file mode 100644 index d4fdc74f..00000000 --- a/src/dusk/ui/uielement.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef struct { - errorret_t (*init)(); - errorret_t (*update)(); - errorret_t (*draw)(); - errorret_t (*dispose)(); - errorret_t (*updateTranslations)(); -} uielement_t; - -/** - * Returns true when all callbacks on the element are NULL, which marks - * the end of the UI_ELEMENTS array. - * - * @param element The element to test. - * @returns True if the element is the null terminator. - */ -bool_t uiElementIsNull(const uielement_t *element); - -/** - * Initializes a UI element, invoking its init callback if set. - * - * @param element The element to initialize. - * @return Any error that occurs. - */ -errorret_t uiElementInit(uielement_t *element); - -/** - * Updates a UI element, calling its update callback if set. - * - * @param element The element to update. - * @return Any error that occurs. - */ -errorret_t uiElementUpdate(uielement_t *element); - -/** - * Draws a UI element, calling its draw callback if set. - * - * @param element The element to render. - * @return Any error that occurs. - */ -errorret_t uiElementDraw(const uielement_t *element); - -/** - * Disposes of a UI element, invoking its dispose callback if set. - * - * @param element The element to dispose. - * @return Any error that occurs. - */ -errorret_t uiElementDispose(uielement_t *element); - -/** - * Notifies a UI element that the active locale has changed, calling its - * updateTranslations callback if set, so it can re-fetch any localized - * strings it caches. - * - * @param element The element to notify. - * @return Any error that occurs. - */ -errorret_t uiElementUpdateTranslations(uielement_t *element); diff --git a/src/dusk/ui/uielementlist.c b/src/dusk/ui/uielementlist.c deleted file mode 100644 index 9f3783dd..00000000 --- a/src/dusk/ui/uielementlist.c +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uielementlist.h" -#include "ui/widget/uiframe.h" -#include "ui/debug/uifps.h" -#include "ui/overlay/uifullbox.h" -#include "ui/overlay/uiloading.h" -#include "ui/overlay/uifatalerror.h" -#include "ui/debug/uiplayerpos.h" -#include "ui/debug/uicutscene.h" -#include "ui/overlay/uicrop.h" -#include "ui/transition/uitransition.h" -#include "ui/debug/uiconsole.h" -#include "ui/screen/game/uigamemenu.h" -#include "ui/screen/mainmenu/uimainmenu.h" -#include "ui/screen/battle/uibattlemenu.h" -#include "ui/screen/battle/uibattlehud.h" -#include "ui/screen/backpack/uibackpack.h" -#include "ui/dialog/uiconfirm.h" -#include "ui/widget/uimodal.h" -#include "ui/dialog/keyboard/uikeyboard.h" -#include "ui/dialog/save/uiselectsave.h" -#include "ui/rpg/textbox/uitextboxmain.h" -#include "ui/rpg/textbox/uitextboxminilist.h" -#include "ui/rpg/uiemoji.h" - -uielement_t UI_ELEMENTS[] = { - // =========================================================================== - // Non-Rendered Components, just needs init and deinit - // =========================================================================== - { - .init = uiFrameInit, - .dispose = uiFrameDispose - }, - - // =========================================================================== - // In-World Gameplay Components, sits below all UI but above gameplay. - // =========================================================================== - { - .init = uiEmojiInit, - .update = uiEmojiUpdate, - .draw = uiEmojiDraw, - .dispose = uiEmojiDispose - }, - - // =========================================================================== - // Low rendered components, usually gameplay non-world components. - // =========================================================================== - { - .init = uiFullboxUnderInit, - .update = uiFullboxUnderUpdate, - .draw = uiFullboxUnderDraw - }, - - // =========================================================================== - // Most menus and non-gameplay components. Hierarchy is very important. - // =========================================================================== - { - .init = uiGameMenuInit, - .draw = uiGameMenuDraw, - .dispose = uiGameMenuDispose - }, - { - .init = uiMainMenuInit, - .draw = uiMainMenuDraw, - .dispose = uiMainMenuDispose - }, - { - .init = uiBattleHudInit, - .draw = uiBattleHudDraw - }, - { - .init = uiBattleMenuInit, - .update = uiBattleMenuUpdate, - .draw = uiBattleMenuDraw - }, - { - .init = uiBackpackInit, - .draw = uiBackpackDraw, - .dispose = uiBackpackDispose - }, - - // =========================================================================== - // Modals, Popups and Dialogs, above most other UI things. - // =========================================================================== - { - .init = uiSelectSaveInit, - .draw = uiSelectSaveDraw, - .dispose = uiSelectSaveDispose - }, - { - .init = uiTextboxMainInit, - .update = uiTextboxMainUpdate, - .draw = uiTextboxMainDraw - }, - { - .init = uiTextboxMiniListInit, - .update = uiTextboxMiniListUpdate, - .draw = uiTextboxMiniListDraw - }, - { - .init = uiKeyboardInit, - .draw = uiKeyboardDraw, - .dispose = uiKeyboardDispose - }, - { - .init = uiModalInit, - .draw = uiModalDraw, - .dispose = uiModalDispose - }, - { - .init = uiConfirmInit, - .dispose = uiConfirmDispose - }, - - // =========================================================================== - // Overlayed components, can even outdraw all UI things. - // =========================================================================== - { - .init = uiTransitionInit, - .update = uiTransitionUpdate, - .draw = uiTransitionDraw - }, - { - .init = uiFullboxOverInit, - .update = uiFullboxOverUpdate, - .draw = uiFullboxOverDraw - }, - - { - .init = uiLoadingInit, - .update = uiLoadingUpdate, - .draw = uiLoadingDraw - }, - { - .init = uiFatalErrorInit, - .draw = uiFatalErrorDraw, - .dispose = uiFatalErrorDispose - }, - - // =========================================================================== - // Important rendering components, lives outside the primary game engine - // =========================================================================== - { - .init = uiCropInit, - .draw = uiCropDraw - }, - - // =========================================================================== - // Debug components, disregards everything else. - // =========================================================================== - { .init = uiConsoleInit, .draw = uiConsoleDraw }, - { .init = uiFPSInit, .draw = uiFPSDraw }, - { .init = uiPlayerPosInit, .draw = uiPlayerPosDraw }, - { .init = uiCutsceneInit, .draw = uiCutsceneDraw }, - - // =========================================================================== - // Null terminator - // =========================================================================== - { 0 } -}; diff --git a/src/dusk/ui/widget/CMakeLists.txt b/src/dusk/ui/widget/CMakeLists.txt deleted file mode 100644 index e15d658a..00000000 --- a/src/dusk/ui/widget/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uibutton.c - uicheckbox.c - uiframe.c - uilabel.c - uitab.c - uislider.c - uidropdown.c - uiscrolling.c - uiitem.c - uiitemlist.c - uimenu.c - uimodal.c -) diff --git a/src/dusk/ui/widget/uibutton.c b/src/dusk/ui/widget/uibutton.c deleted file mode 100644 index 789676af..00000000 --- a/src/dusk/ui/widget/uibutton.c +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uibutton.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "display/color.h" - -void uiButtonInit(uibutton_t *button, const char_t *label) { - assertNotNull(button, "Button cannot be NULL"); - assertNotNull(label, "Label cannot be NULL"); - - memoryZero(button, sizeof(uibutton_t)); - uiLabelInit(&button->label, label, button->sprites, UI_BUTTON_SPRITES_MAX); - button->label.dirty = true; -} - -bool_t uiButtonIsHighlighted(const uibutton_t *button) { - assertNotNull(button, "Button cannot be NULL"); - return button->highlighted; -} - -void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted) { - assertNotNull(button, "Button cannot be NULL"); - button->highlighted = highlighted; -} - -bool_t uiButtonIsActive(const uibutton_t *button) { - assertNotNull(button, "Button cannot be NULL"); - return button->active; -} - -void uiButtonSetActive(uibutton_t *button, const bool_t active) { - assertNotNull(button, "Button cannot be NULL"); - button->active = active; -} - -errorret_t uiButtonDraw( - uibutton_t *button, - const float_t x, - const float_t y -) { - assertNotNull(button, "Button cannot be NULL"); - - uiLabelSetX(&button->label, x); - uiLabelSetY(&button->label, y); - - return uiLabelRender( - &button->label, - button->active ? COLOR_GREEN : - button->highlighted ? COLOR_RED : - COLOR_WHITE - ); -} diff --git a/src/dusk/ui/widget/uibutton.h b/src/dusk/ui/widget/uibutton.h deleted file mode 100644 index 87cd1a21..00000000 --- a/src/dusk/ui/widget/uibutton.h +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uilabel.h" - -#define UI_BUTTON_SPRITES_MAX 32 - -typedef struct { - uilabel_t label; - spritebatchsprite_t sprites[UI_BUTTON_SPRITES_MAX]; - bool_t highlighted; - // Independent of highlighted (which the focus system drives based on - // what's currently focused) - for a button that also needs to show a - // toggled-on state regardless of focus, e.g. the keyboard's CAPS/SHIFT - // keys. - bool_t active; -} uibutton_t; - -/** - * Initializes a button. - * - * @param button The button to initialize. - * @param label Display label; never written to, safe to be a caller- - * owned or immutable string. - */ -void uiButtonInit(uibutton_t *button, const char_t *label); - -/** - * Returns whether the button is highlighted. - * - * @param button The button to query. - * @returns True if highlighted. - */ -bool_t uiButtonIsHighlighted(const uibutton_t *button); - -/** - * Sets the highlighted state of the button. - * - * @param button The button to update. - * @param highlighted The new highlighted state. - */ -void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted); - -/** - * Returns whether the button is active. - * - * @param button The button to query. - * @returns True if active. - */ -bool_t uiButtonIsActive(const uibutton_t *button); - -/** - * Sets the active state of the button - a toggled-on indicator drawn - * the same way as highlighted, but independent of focus. - * - * @param button The button to update. - * @param active The new active state. - */ -void uiButtonSetActive(uibutton_t *button, const bool_t active); - -/** - * Draws the button at the given screen position. - * - * @param button The button to draw. - * @param x Screen x position. - * @param y Screen y position. - * @return Any error that occurs. - */ -errorret_t uiButtonDraw( - uibutton_t *button, - const float_t x, - const float_t y -); diff --git a/src/dusk/ui/widget/uicheckbox.c b/src/dusk/ui/widget/uicheckbox.c deleted file mode 100644 index f9cc8a55..00000000 --- a/src/dusk/ui/widget/uicheckbox.c +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uicheckbox.h" -#include "util/memory.h" -#include "display/text/text.h" -#include "display/color.h" - -void uiCheckboxInit( - uicheckbox_t *checkbox, - const char_t *label -) { - memoryZero(checkbox, sizeof(uicheckbox_t)); - checkbox->label = label; -} - -bool_t uiCheckboxIsChecked(const uicheckbox_t *checkbox) { - return checkbox->checked; -} - -void uiCheckboxSetChecked(uicheckbox_t *checkbox, const bool_t checked) { - checkbox->checked = checked; -} - -void uiCheckboxToggle(uicheckbox_t *checkbox) { - uiCheckboxSetChecked(checkbox, !uiCheckboxIsChecked(checkbox)); -} - -bool_t uiCheckboxIsHighlighted(const uicheckbox_t *checkbox) { - return checkbox->highlighted; -} - -void uiCheckboxSetHighlighted( - uicheckbox_t *checkbox, const bool_t highlighted -) { - checkbox->highlighted = highlighted; -} - -errorret_t uiCheckboxDraw( - const uicheckbox_t *checkbox, - const float_t x, - const float_t y -) { - color_t color = checkbox->highlighted ? COLOR_RED : COLOR_WHITE; - const char_t *mark = checkbox->checked ? "Y " : "N "; - errorChain(textDraw(x, y, mark, color, &FONT_DEFAULT)); - - int32_t markW, markH; - textMeasure(mark, &FONT_DEFAULT, &markW, &markH); - errorChain(textDraw( - x + (float_t)markW, y, checkbox->label, color, &FONT_DEFAULT - )); - - errorOk(); -} diff --git a/src/dusk/ui/widget/uicheckbox.h b/src/dusk/ui/widget/uicheckbox.h deleted file mode 100644 index b32d52cc..00000000 --- a/src/dusk/ui/widget/uicheckbox.h +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef struct uicheckbox_s { - const char_t *label; - bool_t checked; - bool_t highlighted; -} uicheckbox_t; - -/** - * Initializes a checkbox. - * - * @param checkbox The checkbox to initialize. - * @param label Display label. - */ -void uiCheckboxInit( - uicheckbox_t *checkbox, - const char_t *label -); - -/** - * Returns whether the checkbox is checked. - * - * @param checkbox The checkbox to query. - * @returns True if checked. - */ -bool_t uiCheckboxIsChecked(const uicheckbox_t *checkbox); - -/** - * Sets the checked state of the checkbox. - * - * @param checkbox The checkbox to update. - * @param checked The new checked state. - */ -void uiCheckboxSetChecked(uicheckbox_t *checkbox, const bool_t checked); - -/** - * Toggles the checked state of the checkbox. - * - * @param checkbox The checkbox to toggle. - */ -void uiCheckboxToggle(uicheckbox_t *checkbox); - -/** - * Returns whether the checkbox is highlighted. - * - * @param checkbox The checkbox to query. - * @returns True if highlighted. - */ -bool_t uiCheckboxIsHighlighted(const uicheckbox_t *checkbox); - -/** - * Sets the highlighted state of the checkbox. - * - * @param checkbox The checkbox to update. - * @param highlighted The new highlighted state. - */ -void uiCheckboxSetHighlighted(uicheckbox_t *checkbox, const bool_t highlighted); - -/** - * Draws the checkbox at the given screen position. - * - * @param checkbox The checkbox to draw. - * @param x Screen x position. - * @param y Screen y position. - * @return Any error that occurs. - */ -errorret_t uiCheckboxDraw( - const uicheckbox_t *checkbox, - const float_t x, - const float_t y -); diff --git a/src/dusk/ui/widget/uidropdown.c b/src/dusk/ui/widget/uidropdown.c deleted file mode 100644 index ae72bd4e..00000000 --- a/src/dusk/ui/widget/uidropdown.c +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uidropdown.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "display/text/text.h" -#include "display/color.h" - -void uiDropdownInit( - uidropdown_t *dropdown, - const char_t *label, - const char_t *const *options, - const uint8_t optionCount, - const uint8_t selectedIndex -) { - assertNotNull(dropdown, "Dropdown cannot be NULL"); - assertNotNull(label, "Label cannot be NULL"); - assertNotNull(options, "Options cannot be NULL"); - assertTrue(optionCount > 0, "Dropdown must have at least one option"); - - memoryZero(dropdown, sizeof(uidropdown_t)); - dropdown->label = label; - dropdown->options = options; - dropdown->optionCount = optionCount; - dropdown->selectedIndex = selectedIndex < optionCount ? selectedIndex : 0; -} - -uint8_t uiDropdownGetSelectedIndex(const uidropdown_t *dropdown) { - assertNotNull(dropdown, "Dropdown cannot be NULL"); - return dropdown->selectedIndex; -} - -const char_t *uiDropdownGetSelectedOption(const uidropdown_t *dropdown) { - assertNotNull(dropdown, "Dropdown cannot be NULL"); - return dropdown->options[dropdown->selectedIndex]; -} - -void uiDropdownSetSelectedIndex(uidropdown_t *dropdown, const uint8_t index) { - assertNotNull(dropdown, "Dropdown cannot be NULL"); - assertTrue(index < dropdown->optionCount, "Dropdown index out of range"); - dropdown->selectedIndex = index; -} - -void uiDropdownStepNext(uidropdown_t *dropdown) { - assertNotNull(dropdown, "Dropdown cannot be NULL"); - dropdown->selectedIndex = - (dropdown->selectedIndex + 1) % dropdown->optionCount; -} - -void uiDropdownStepPrev(uidropdown_t *dropdown) { - assertNotNull(dropdown, "Dropdown cannot be NULL"); - dropdown->selectedIndex = dropdown->selectedIndex == 0 ? - dropdown->optionCount - 1 : dropdown->selectedIndex - 1; -} - -bool_t uiDropdownIsHighlighted(const uidropdown_t *dropdown) { - assertNotNull(dropdown, "Dropdown cannot be NULL"); - return dropdown->highlighted; -} - -void uiDropdownSetHighlighted( - uidropdown_t *dropdown, - const bool_t highlighted -) { - assertNotNull(dropdown, "Dropdown cannot be NULL"); - dropdown->highlighted = highlighted; -} - -errorret_t uiDropdownDraw( - const uidropdown_t *dropdown, - const float_t x, - const float_t y -) { - assertNotNull(dropdown, "Dropdown cannot be NULL"); - - color_t color = dropdown->highlighted ? COLOR_RED : COLOR_WHITE; - - errorChain(textDraw(x, y, dropdown->label, color, &FONT_DEFAULT)); - - int32_t labelW, labelH; - textMeasure(dropdown->label, &FONT_DEFAULT, &labelW, &labelH); - - char_t valueText[UI_DROPDOWN_VALUE_TEXT_MAX]; - stringFormat( - valueText, UI_DROPDOWN_VALUE_TEXT_MAX - 1, "< %s >", - uiDropdownGetSelectedOption(dropdown) - ); - - errorChain(textDraw( - x + (float_t)labelW + UI_DROPDOWN_GAP, y, valueText, color, &FONT_DEFAULT - )); - - errorOk(); -} diff --git a/src/dusk/ui/widget/uidropdown.h b/src/dusk/ui/widget/uidropdown.h deleted file mode 100644 index 0c6884ac..00000000 --- a/src/dusk/ui/widget/uidropdown.h +++ /dev/null @@ -1,113 +0,0 @@ -/** - * 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_DROPDOWN_GAP 4.0f -#define UI_DROPDOWN_VALUE_TEXT_MAX 64 - -typedef struct { - const char_t *label; - const char_t *const *options; - uint8_t optionCount; - uint8_t selectedIndex; - bool_t highlighted; -} uidropdown_t; - -/** - * Initializes a dropdown. - * - * @param dropdown The dropdown to initialize. - * @param label Display label. - * @param options Array of option display strings; caller-owned, must - * outlive the dropdown. - * @param optionCount Number of entries in options. Must be > 0. - * @param selectedIndex Initial selected option index, clamped to - * [0, optionCount - 1]. - */ -void uiDropdownInit( - uidropdown_t *dropdown, - const char_t *label, - const char_t *const *options, - const uint8_t optionCount, - const uint8_t selectedIndex -); - -/** - * Returns the index of the currently selected option. - * - * @param dropdown The dropdown to query. - * @returns The selected option index. - */ -uint8_t uiDropdownGetSelectedIndex(const uidropdown_t *dropdown); - -/** - * Returns the currently selected option's display string. - * - * @param dropdown The dropdown to query. - * @returns The selected option string. - */ -const char_t *uiDropdownGetSelectedOption(const uidropdown_t *dropdown); - -/** - * Sets the selected option by index. - * - * @param dropdown The dropdown to update. - * @param index The new selected option index. Must be < optionCount. - */ -void uiDropdownSetSelectedIndex(uidropdown_t *dropdown, const uint8_t index); - -/** - * Selects the next option, wrapping around to the first option past - * the last. - * - * @param dropdown The dropdown to update. - */ -void uiDropdownStepNext(uidropdown_t *dropdown); - -/** - * Selects the previous option, wrapping around to the last option - * before the first. - * - * @param dropdown The dropdown to update. - */ -void uiDropdownStepPrev(uidropdown_t *dropdown); - -/** - * Returns whether the dropdown is highlighted. - * - * @param dropdown The dropdown to query. - * @returns True if highlighted. - */ -bool_t uiDropdownIsHighlighted(const uidropdown_t *dropdown); - -/** - * Sets the highlighted state of the dropdown. - * - * @param dropdown The dropdown to update. - * @param highlighted The new highlighted state. - */ -void uiDropdownSetHighlighted( - uidropdown_t *dropdown, - const bool_t highlighted -); - -/** - * Draws the dropdown at the given screen position: label, then the - * currently selected option surrounded by cycle arrows. - * - * @param dropdown The dropdown to draw. - * @param x Screen x position. - * @param y Screen y position. - * @return Any error that occurs. - */ -errorret_t uiDropdownDraw( - const uidropdown_t *dropdown, - const float_t x, - const float_t y -); diff --git a/src/dusk/ui/widget/uiframe.c b/src/dusk/ui/widget/uiframe.c deleted file mode 100644 index 5202b3e1..00000000 --- a/src/dusk/ui/widget/uiframe.c +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiframe.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "display/texture/texture.h" -#include "display/spritebatch/spritebatch.h" -#include "display/shader/shaderunlit.h" -#include "display/color.h" - -uiframe_t UI_FRAME; - -errorret_t uiFrameInit(void) { - memoryZero(&UI_FRAME, sizeof(uiframe_t)); - - // Init texture. - color_t border = color4b(0, 50, 255, 255); - color_t center = color4b(0, 100, 220, 240); - - for(uint8_t y = 0; y < UI_FRAME_TEXTURE_HEIGHT_POW2; y++) { - for(uint8_t x = 0; x < UI_FRAME_TEXTURE_WIDTH_POW2; x++) { - color_t c; - if(x >= UI_FRAME_TEXTURE_WIDTH || y >= UI_FRAME_TEXTURE_HEIGHT) { - c = COLOR_TRANSPARENT; - } else if( - y < UI_FRAME_TILE_HEIGHT || y >= UI_FRAME_TILE_HEIGHT * 2 || - x < UI_FRAME_TILE_WIDTH || x >= UI_FRAME_TILE_WIDTH * 2 - ) { - c = border; - } else { - c = center; - } - UI_FRAME.pixels[y * UI_FRAME_TEXTURE_WIDTH_POW2 + x] = c; - } - } - - errorChain(textureInit( - &UI_FRAME.texture, - UI_FRAME_TEXTURE_WIDTH_POW2, UI_FRAME_TEXTURE_HEIGHT_POW2, - TEXTURE_FORMAT_RGBA, - (texturedata_t){ .rgbaColors = UI_FRAME.pixels } - )); - - UI_FRAME.tileset.tileWidth = UI_FRAME_TILE_WIDTH; - UI_FRAME.tileset.tileHeight = UI_FRAME_TILE_HEIGHT; - UI_FRAME.tileset.columns = 3; - UI_FRAME.tileset.rows = 3; - UI_FRAME.tileset.tileCount = 9; - UI_FRAME.tileset.uv[0] = - (float_t)UI_FRAME_TILE_WIDTH / UI_FRAME_TEXTURE_WIDTH_POW2; - UI_FRAME.tileset.uv[1] = - (float_t)UI_FRAME_TILE_HEIGHT / UI_FRAME_TEXTURE_HEIGHT_POW2; - - errorOk(); -} - -errorret_t uiFrameDraw( - const float_t x, - const float_t y, - const float_t width, - const float_t height -) { - shadermaterial_t material = { - .unlit = { - .color = COLOR_WHITE, - .texture = &UI_FRAME.texture - } - }; - spritebatchsprite_t sprites[9]; - float_t tileW = (float_t)UI_FRAME_BORDER_WIDTH; - float_t tileH = (float_t)UI_FRAME_BORDER_HEIGHT; - - sprites[0] = spriteBatchSpriteTilesetPosition( - &UI_FRAME.tileset, 0, 0, - x, y, tileW, tileH - ); - sprites[1] = spriteBatchSpriteTilesetPosition( - &UI_FRAME.tileset, 1, 0, - x + tileW, y, width - (tileW * 2.0f), tileH - ); - sprites[2] = spriteBatchSpriteTilesetPosition( - &UI_FRAME.tileset, 2, 0, - x + width - tileW, y, tileW, tileH - ); - sprites[3] = spriteBatchSpriteTilesetPosition( - &UI_FRAME.tileset, 0, 1, - x, y + tileH, tileW, height - (tileH * 2.0f) - ); - sprites[4] = spriteBatchSpriteTilesetPosition( - &UI_FRAME.tileset, 1, 1, - x + tileW, y + tileH, - width - (tileW * 2.0f), height - (tileH * 2.0f) - ); - sprites[5] = spriteBatchSpriteTilesetPosition( - &UI_FRAME.tileset, 2, 1, - x + width - tileW, y + tileH, tileW, height - (tileH * 2.0f) - ); - sprites[6] = spriteBatchSpriteTilesetPosition( - &UI_FRAME.tileset, 0, 2, - x, y + height - tileH, tileW, tileH - ); - sprites[7] = spriteBatchSpriteTilesetPosition( - &UI_FRAME.tileset, 1, 2, - x + tileW, y + height - tileH, width - (tileW * 2.0f), tileH - ); - sprites[8] = spriteBatchSpriteTilesetPosition( - &UI_FRAME.tileset, 2, 2, - x + width - tileW, y + height - tileH, tileW, tileH - ); - - return spriteBatchBuffer(sprites, 9, &SHADER_UNLIT, material); -} - -errorret_t uiFrameDispose(void) { - return textureDispose(&UI_FRAME.texture); -} diff --git a/src/dusk/ui/widget/uiframe.h b/src/dusk/ui/widget/uiframe.h deleted file mode 100644 index 626afd64..00000000 --- a/src/dusk/ui/widget/uiframe.h +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "display/texture/texture.h" -#include "display/texture/tileset.h" - -#define UI_FRAME_BORDER_WIDTH 6 -#define UI_FRAME_BORDER_HEIGHT 6 -#define UI_FRAME_PADDING_X 2 -#define UI_FRAME_PADDING_Y 2 -#define UI_FRAME_START_X (UI_FRAME_BORDER_WIDTH + UI_FRAME_PADDING_X) -#define UI_FRAME_START_Y (UI_FRAME_BORDER_HEIGHT + UI_FRAME_PADDING_Y) -#define UI_FRAME_TILE_WIDTH 1 -#define UI_FRAME_TILE_HEIGHT 1 -#define UI_FRAME_TEXTURE_WIDTH (UI_FRAME_TILE_WIDTH * 3) -#define UI_FRAME_TEXTURE_HEIGHT (UI_FRAME_TILE_HEIGHT * 3) -#define UI_FRAME_TEXTURE_WIDTH_POW2 4 -#define UI_FRAME_TEXTURE_HEIGHT_POW2 4 - -typedef struct { - tileset_t tileset; - texture_t texture; - color_t pixels[UI_FRAME_BORDER_WIDTH * UI_FRAME_BORDER_HEIGHT]; -} uiframe_t; - -extern uiframe_t UI_FRAME; - -/** - * Initializes the global UI_FRAME: builds the dummy texture and - * configures the tileset. Call once after displayInit(). - * - * @return Any error that occurs. - */ -errorret_t uiFrameInit(void); - -/** - * Draws UI_FRAME using 9-slice rendering from its 3x3 tileset. - * Pushes quads to the sprite batch without flushing. - * - * @param x Screen x position. - * @param y Screen y position. - * @param width Total width of the frame. - * @param height Total height of the frame. - * @return Any error that occurs. - */ -errorret_t uiFrameDraw( - const float_t x, - const float_t y, - const float_t width, - const float_t height -); - -/** - * Disposes of the global UI_FRAME, releasing its GPU texture. - * - * @return Any error that occurs. - */ -errorret_t uiFrameDispose(void); diff --git a/src/dusk/ui/widget/uiitem.c b/src/dusk/ui/widget/uiitem.c deleted file mode 100644 index 9e73b676..00000000 --- a/src/dusk/ui/widget/uiitem.c +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiitem.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "display/text/text.h" -#include "display/color.h" - -#define UI_ITEM_LABEL_MAX 48 - -errorret_t uiItemInit( - uiitem_t *item, - const itemid_t itemId, - const uint8_t quantity -) { - assertNotNull(item, "Item cannot be NULL"); - memoryZero(item, sizeof(uiitem_t)); - item->item = itemId; - item->quantity = quantity; - - if(itemId == ITEM_ID_NULL) errorOk(); - - errorChain(itemGetName(itemId, item->nameLabel, UI_ITEM_NAME_LABEL_MAX)); - - errorOk(); -} - -itemid_t uiItemGetItem(const uiitem_t *item) { - assertNotNull(item, "Item cannot be NULL"); - return item->item; -} - -uint8_t uiItemGetQuantity(const uiitem_t *item) { - assertNotNull(item, "Item cannot be NULL"); - return item->quantity; -} - -void uiItemSetQuantity(uiitem_t *item, const uint8_t quantity) { - assertNotNull(item, "Item cannot be NULL"); - item->quantity = quantity; -} - -bool_t uiItemIsHighlighted(const uiitem_t *item) { - assertNotNull(item, "Item cannot be NULL"); - return item->highlighted; -} - -void uiItemSetHighlighted(uiitem_t *item, const bool_t highlighted) { - assertNotNull(item, "Item cannot be NULL"); - item->highlighted = highlighted; -} - -errorret_t uiItemDraw( - const uiitem_t *item, - const float_t x, - const float_t y -) { - assertNotNull(item, "Item cannot be NULL"); - if(item->item == ITEM_ID_NULL) errorOk(); - - const color_t color = item->highlighted ? COLOR_RED : COLOR_WHITE; - - char_t text[UI_ITEM_LABEL_MAX]; - stringFormat( - text, UI_ITEM_LABEL_MAX - 1, - "%s x%u", item->nameLabel, item->quantity - ); - - errorChain(textDraw(x, y, text, color, &FONT_DEFAULT)); - errorOk(); -} diff --git a/src/dusk/ui/widget/uiitem.h b/src/dusk/ui/widget/uiitem.h deleted file mode 100644 index 787884c8..00000000 --- a/src/dusk/ui/widget/uiitem.h +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "rpg/item/item.h" - -#define UI_ITEM_NAME_LABEL_MAX 32 - -typedef struct { - itemid_t item; - uint8_t quantity; - bool_t highlighted; - char_t nameLabel[UI_ITEM_NAME_LABEL_MAX]; -} uiitem_t; - -/** - * Initializes an item widget, resolving the item's localized name. - * - * @param item The item widget to initialize. - * @param itemId The item ID to display. ITEM_ID_NULL renders as an - * empty slot. - * @param quantity The stack quantity to display. - * @return Any error that occurs. - */ -errorret_t uiItemInit( - uiitem_t *item, - const itemid_t itemId, - const uint8_t quantity -); - -/** - * Returns the item ID this widget displays. - * - * @param item The item widget to query. - * @returns The item ID. - */ -itemid_t uiItemGetItem(const uiitem_t *item); - -/** - * Returns the stack quantity this widget displays. - * - * @param item The item widget to query. - * @returns The quantity. - */ -uint8_t uiItemGetQuantity(const uiitem_t *item); - -/** - * Sets the stack quantity this widget displays. - * - * @param item The item widget to update. - * @param quantity The new quantity. - */ -void uiItemSetQuantity(uiitem_t *item, const uint8_t quantity); - -/** - * Returns whether the item widget is highlighted. - * - * @param item The item widget to query. - * @returns True if highlighted. - */ -bool_t uiItemIsHighlighted(const uiitem_t *item); - -/** - * Sets the highlighted state of the item widget. - * - * @param item The item widget to update. - * @param highlighted The new highlighted state. - */ -void uiItemSetHighlighted(uiitem_t *item, const bool_t highlighted); - -/** - * Draws the item widget at the given screen position: item name and - * quantity. No-op for an empty (ITEM_ID_NULL) slot. - * - * @param item The item widget to draw. - * @param x Screen x position. - * @param y Screen y position. - * @return Any error that occurs. - */ -errorret_t uiItemDraw( - const uiitem_t *item, - const float_t x, - const float_t y -); diff --git a/src/dusk/ui/widget/uiitemlist.c b/src/dusk/ui/widget/uiitemlist.c deleted file mode 100644 index 2ab2f62a..00000000 --- a/src/dusk/ui/widget/uiitemlist.c +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiitemlist.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "display/text/text.h" - -void uiItemListInit( - uiitemlist_t *list, - const uint8_t columns, - const uint8_t rows, - const uint8_t itemColumns, - uiitemlistselectedcallback_t selected, - uiitemlistchangedcallback_t changed, - uiitemlistclosedcallback_t closed, - uiitemlistcolumncallback_t columnDraw -) { - assertNotNull(list, "Item list cannot be NULL"); - assertTrue(columns > 0, "Item list columns must be > 0"); - assertTrue(rows > 0, "Item list rows must be > 0"); - assertTrue(itemColumns > 0, "Item list itemColumns must be > 0"); - - memoryZero(list, sizeof(uiitemlist_t)); - list->columns = columns; - list->rows = rows; - list->itemColumns = itemColumns; - list->selected = selected; - list->changed = changed; - list->closed = closed; - list->columnDraw = columnDraw; - - uiScrollingInit(&list->scroll); -} - -void uiItemListSetItems( - uiitemlist_t *list, - const uiitem_t *items, - const uint8_t itemCount -) { - assertNotNull(list, "Item list cannot be NULL"); - assertTrue( - itemCount <= UI_ITEM_LIST_CAPACITY_MAX, "Too many items for list" - ); - - memoryCopy(list->items, items, sizeof(uiitem_t) * itemCount); - list->itemCount = itemCount; -} - -errorret_t uiItemListSetItemStacks( - uiitemlist_t *list, - const inventorystack_t *stacks, - const uint8_t stackCount -) { - assertNotNull(list, "Item list cannot be NULL"); - assertTrue( - stackCount <= UI_ITEM_LIST_CAPACITY_MAX, "Too many items for list" - ); - - for(uint8_t i = 0; i < stackCount; i++) { - errorChain( - uiItemInit(&list->items[i], stacks[i].item, stacks[i].quantity) - ); - } - list->itemCount = stackCount; - - errorOk(); -} - -void uiItemListOpen(uiitemlist_t *list) { - assertNotNull(list, "Item list cannot be NULL"); - if(list->focusItem != NULL) return; - - list->focusItem = uiFocusPush( - list->columns, list->rows, - uiItemListFocusSelected, - uiItemListFocusChanged, - uiItemListFocusClosed, - NULL, - list - ); -} - -void uiItemListClose(uiitemlist_t *list) { - assertNotNull(list, "Item list cannot be NULL"); - if(list->focusItem == NULL) return; - uiFocusPopItem(list->focusItem); - list->focusItem = NULL; -} - -bool_t uiItemListIsActive(const uiitemlist_t *list) { - assertNotNull(list, "Item list cannot be NULL"); - return list->focusItem != NULL; -} - -errorret_t uiItemListDraw( - const uiitemlist_t *list, - const float_t x, - const float_t y, - const float_t width -) { - assertNotNull(list, "Item list cannot be NULL"); - if(list->itemCount == 0) errorOk(); - - const float_t colStep = width / (float_t)list->columns; - const float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; - const float_t itemColStep = colStep / (float_t)list->itemColumns; - - const uint8_t visibleMax = list->columns * list->rows; - const uint8_t drawCount = - list->itemCount < visibleMax ? list->itemCount : visibleMax; - - for(uint8_t i = 0; i < drawCount; i++) { - const uint8_t col = i % list->columns; - const uint8_t row = i / list->columns; - - const float_t ix = x + (float_t)col * colStep; - const float_t iy = y + (float_t)row * rowHeight; - - errorChain(uiItemDraw(&list->items[i], ix, iy)); - - for(uint8_t c = 1; c < list->itemColumns; c++) { - if(list->columnDraw == NULL) continue; - errorChain(list->columnDraw( - list, &list->items[i], c, ix + (float_t)c * itemColStep, iy - )); - } - } - - errorOk(); -} - -bool_t uiItemListFocusSelected(const uifocusitem_t *focusItem) { - assertNotNull(focusItem, "Focus item cannot be NULL"); - assertNotNull(focusItem->user, "Focus item user cannot be NULL"); - uiitemlist_t *list = (uiitemlist_t *)focusItem->user; - if(list->selected == NULL) return true; - - const uint8_t index = focusItem->y * list->columns + focusItem->x; - if(index >= list->itemCount) return true; - - list->selected(list, index, &list->items[index]); - return true; -} - -bool_t uiItemListFocusChanged(const uifocusitem_t *focusItem) { - assertNotNull(focusItem, "Focus item cannot be NULL"); - assertNotNull(focusItem->user, "Focus item user cannot be NULL"); - uiitemlist_t *list = (uiitemlist_t *)focusItem->user; - - const uint8_t focusIndex = focusItem->y * list->columns + focusItem->x; - for(uint8_t i = 0; i < list->itemCount; i++) { - uiItemSetHighlighted(&list->items[i], i == focusIndex); - } - - if(list->changed == NULL) return true; - if(focusIndex >= list->itemCount) return true; - - list->changed(list, focusIndex, &list->items[focusIndex]); - return true; -} - -bool_t uiItemListFocusClosed(const uifocusitem_t *focusItem) { - assertNotNull(focusItem, "Focus item cannot be NULL"); - assertNotNull(focusItem->user, "Focus item user cannot be NULL"); - uiitemlist_t *list = (uiitemlist_t *)focusItem->user; - list->focusItem = NULL; - - for(uint8_t i = 0; i < list->itemCount; i++) { - uiItemSetHighlighted(&list->items[i], false); - } - - if(list->closed != NULL) list->closed(list); - return true; -} diff --git a/src/dusk/ui/widget/uiitemlist.h b/src/dusk/ui/widget/uiitemlist.h deleted file mode 100644 index 26b3e350..00000000 --- a/src/dusk/ui/widget/uiitemlist.h +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uiitem.h" -#include "ui/widget/uiscrolling.h" -#include "ui/focus/uifocus.h" -#include "rpg/item/inventory.h" - -#define UI_ITEM_LIST_CAPACITY_MAX 40 - -typedef struct uiitemlist_s uiitemlist_t; - -typedef void (*uiitemlistselectedcallback_t)( - const uiitemlist_t *list, - const uint8_t index, - const uiitem_t *item -); - -typedef void (*uiitemlistchangedcallback_t)( - const uiitemlist_t *list, - const uint8_t index, - const uiitem_t *item -); - -typedef void (*uiitemlistclosedcallback_t)(const uiitemlist_t *list); - -/** - * Called to draw an extra per-item info column (columnIndex >= 1) - * beyond the item's own default uiItemDraw rendering -- e.g. a shop - * menu drawing a price alongside the item name. - * - * @param list The item list being drawn. - * @param item The item slot being drawn. - * @param columnIndex The info column being drawn (>= 1). - * @param x Screen x position for this column. - * @param y Screen y position for this column. - * @return Any error that occurs. - */ -typedef errorret_t (*uiitemlistcolumncallback_t)( - const uiitemlist_t *list, - const uiitem_t *item, - const uint8_t columnIndex, - const float_t x, - const float_t y -); - -struct uiitemlist_s { - uiitem_t items[UI_ITEM_LIST_CAPACITY_MAX]; - uint8_t itemCount; - - // Grid layout: how many item slots wide/tall the list displays. - uint8_t columns; - uint8_t rows; - - // How many info columns are rendered per item slot. 1 means only the - // item's own default name/quantity is drawn; anything beyond that is - // rendered via columnDraw. - uint8_t itemColumns; - - uiscrolling_t scroll; - uifocusitem_t *focusItem; - - uiitemlistselectedcallback_t selected; - uiitemlistchangedcallback_t changed; - uiitemlistclosedcallback_t closed; - uiitemlistcolumncallback_t columnDraw; - - void *user; -}; - -/** - * Initializes an item list. - * - * @param list The item list to initialize. - * @param columns Number of item slots per row. - * @param rows Number of item slot rows. - * @param itemColumns Number of info columns rendered per item slot. - * @param selected Called when an item slot is selected. - * @param changed Called when the focused item slot changes. - * @param closed Called when the item list is closed. - * @param columnDraw Called to draw each extra info column (index >= 1); - * may be NULL if itemColumns is 1. - */ -void uiItemListInit( - uiitemlist_t *list, - const uint8_t columns, - const uint8_t rows, - const uint8_t itemColumns, - uiitemlistselectedcallback_t selected, - uiitemlistchangedcallback_t changed, - uiitemlistclosedcallback_t closed, - uiitemlistcolumncallback_t columnDraw -); - -/** - * Sets the items displayed by the list directly, copying them into - * the list's own storage. - * - * @param list The item list to update. - * @param items The items to display. - * @param itemCount Number of entries in items. Must be <= - * UI_ITEM_LIST_CAPACITY_MAX. - */ -void uiItemListSetItems( - uiitemlist_t *list, - const uiitem_t *items, - const uint8_t itemCount -); - -/** - * Sets the items displayed by the list from an array of item stacks, - * internally creating a uiitem_t for each stack. - * - * @param list The item list to update. - * @param stacks The item stacks to display. - * @param stackCount Number of entries in stacks. Must be <= - * UI_ITEM_LIST_CAPACITY_MAX. - * @return Any error that occurs. - */ -errorret_t uiItemListSetItemStacks( - uiitemlist_t *list, - const inventorystack_t *stacks, - const uint8_t stackCount -); - -/** - * Pushes the item list onto the UI focus stack, making it navigable. - * No-op if already open. - * - * @param list The item list to open. - */ -void uiItemListOpen(uiitemlist_t *list); - -/** - * Pops the item list from the UI focus stack. No-op if already closed. - * - * @param list The item list to close. - */ -void uiItemListClose(uiitemlist_t *list); - -/** - * Returns whether the item list is currently on the UI focus stack. - * - * @param list The item list to query. - * @returns True if active. - */ -bool_t uiItemListIsActive(const uiitemlist_t *list); - -/** - * Draws the item list's grid of item slots at the given position. - * Only the first columns * rows items are drawn. - * - * @param list The item list to draw. - * @param x Screen x position. - * @param y Screen y position. - * @param width Content width, used to lay out columns. - * @return Any error that occurs. - */ -errorret_t uiItemListDraw( - const uiitemlist_t *list, - const float_t x, - const float_t y, - const float_t width -); - -/** - * Internal focus callback -- forwards selection to the list's selected - * handler. - * - * @param focusItem The active focus item; user field must point to - * uiitemlist_t. - * @returns True. - */ -bool_t uiItemListFocusSelected(const uifocusitem_t *focusItem); - -/** - * Internal focus callback -- updates item highlights and fires - * changed. - * - * @param focusItem The active focus item; user field must point to - * uiitemlist_t. - * @returns True. - */ -bool_t uiItemListFocusChanged(const uifocusitem_t *focusItem); - -/** - * Internal focus callback -- clears focusItem and fires the closed - * handler. - * - * @param focusItem The active focus item; user field must point to - * uiitemlist_t. - * @returns True. - */ -bool_t uiItemListFocusClosed(const uifocusitem_t *focusItem); diff --git a/src/dusk/ui/widget/uilabel.c b/src/dusk/ui/widget/uilabel.c deleted file mode 100644 index 48109474..00000000 --- a/src/dusk/ui/widget/uilabel.c +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uilabel.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "display/text/text.h" -#include "display/spritebatch/spritebatch.h" -#include "display/shader/shaderunlit.h" - -void uiLabelInit( - uilabel_t *label, - const char_t *text, - spritebatchsprite_t *sprites, - const int32_t spritesMax -) { - assertNotNull(text, "Text buffer cannot be NULL"); - assertNotNull(sprites, "Sprites buffer cannot be NULL"); - - memoryZero(label, sizeof(uilabel_t)); - label->text = text; - label->sprites = sprites; - label->spritesMax = spritesMax; - label->dirty = true; -} - -void uiLabelSetWrap( - uilabel_t *label, - char_t *wrapBuffer, - const uint32_t wrapBufferMax, - const float_t wrapWidth -) { - assertNotNull(label, "Label cannot be NULL"); - - // A NULL wrapBuffer always disables wrapping outright, regardless of - // wrapWidth - so callers can unconditionally pass NULL to turn wrapping - // off without also having to zero out whatever width they were using. - // Enabling it (a non-NULL buffer) always requires a positive width. - if(wrapBuffer != NULL) { - assertTrue(wrapWidth > 0.0f, "Wrap width must be greater than 0"); - assertTrue(wrapBufferMax >= 1, "Wrap buffer max must be at least 1"); - } - - label->wrapBuffer = wrapBuffer; - label->wrapBufferMax = wrapBufferMax; - label->wrapWidth = wrapBuffer == NULL ? 0.0f : wrapWidth; - label->dirty = true; -} - -void uiLabelSetX(uilabel_t *label, const float_t x) { - if(label->x == x) return; - label->x = x; - label->dirty = true; -} - -float_t uiLabelGetX(const uilabel_t *label) { - return label->x; -} - -void uiLabelSetY(uilabel_t *label, const float_t y) { - if(label->y == y) return; - label->y = y; - label->dirty = true; -} - -float_t uiLabelGetY(const uilabel_t *label) { - return label->y; -} - -void uiLabelRebuffer(uilabel_t *label) { - if(!label->dirty) return; - - const char_t *renderText = label->text; - if(label->wrapWidth > 0.0f) { - assertNotNull( - label->wrapBuffer, "Wrap buffer cannot be NULL when wrapping is enabled" - ); - stringCopy(label->wrapBuffer, label->text, label->wrapBufferMax); - textWrap(label->wrapBuffer, &FONT_DEFAULT, label->wrapWidth); - renderText = label->wrapBuffer; - } - - textMeasure(renderText, &FONT_DEFAULT, &label->width, &label->height); - - int32_t charIndex = 0; - float_t posX = label->x; - float_t posY = label->y; - label->spriteCount = textBuffer( - label->x, label->y, renderText, &FONT_DEFAULT, - label->sprites, label->spritesMax, - &charIndex, &posX, &posY - ); - - label->dirty = false; -} - -errorret_t uiLabelRender(uilabel_t *label, const color_t color) { - uiLabelRebuffer(label); - if(label->spriteCount == 0) errorOk(); - - shadermaterial_t material = { - .unlit = { - .color = color, - .texture = FONT_DEFAULT.texture - } - }; - errorChain(spriteBatchBuffer( - label->sprites, label->spriteCount, &SHADER_UNLIT, material - )); - - errorOk(); -} - -void uiLabelDispose(uilabel_t *label) { - memoryZero(label, sizeof(uilabel_t)); -} diff --git a/src/dusk/ui/widget/uilabel.h b/src/dusk/ui/widget/uilabel.h deleted file mode 100644 index edc16fab..00000000 --- a/src/dusk/ui/widget/uilabel.h +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "display/spritebatch/spritebatchsprite.h" -#include "display/color.h" - -typedef struct { - const char_t *text; - spritebatchsprite_t *sprites; - int32_t spritesMax; - uint32_t spriteCount; - int32_t width; - int32_t height; - float_t x; - float_t y; - bool_t dirty; - - // Wrapping - disabled while wrapWidth is 0 (the default). Enabling it - // requires wrapBuffer, a caller-owned scratch buffer at least as large as - // text: label->text is never written to directly (see uiLabelInit), so - // uiLabelRebuffer instead copies it into wrapBuffer and word-wraps that - // copy in place (via textWrap) before measuring/buffering it. - char_t *wrapBuffer; - uint32_t wrapBufferMax; - float_t wrapWidth; -} uilabel_t; - -/** - * Initializes a label. The caller owns the text buffer and writes its - * contents directly (setting label->dirty afterwards); call - * uiLabelRebuffer or uiLabelRender to rebuild the cached sprites. - * - * @param label The label to initialize. - * @param text Caller-owned, null-terminated string the label reads from - * directly; never written to by the label. Cannot be NULL. - * @param sprites Caller-owned sprite buffer the label writes cached - * glyph sprites into. Cannot be NULL. - * @param spritesMax Number of sprites the sprites buffer can hold. - */ -void uiLabelInit( - uilabel_t *label, - const char_t *text, - spritebatchsprite_t *sprites, - const int32_t spritesMax -); - -/** - * Sets the screen x position of the label. - * - * @param label The label to update. - * @param x Screen x position. - */ -void uiLabelSetX(uilabel_t *label, const float_t x); - -/** - * Returns the screen x position of the label. - * - * @param label The label to query. - * @returns The label's x position. - */ -float_t uiLabelGetX(const uilabel_t *label); - -/** - * Sets the screen y position of the label. - * - * @param label The label to update. - * @param y Screen y position. - */ -void uiLabelSetY(uilabel_t *label, const float_t y); - -/** - * Returns the screen y position of the label. - * - * @param label The label to query. - * @returns The label's y position. - */ -float_t uiLabelGetY(const uilabel_t *label); - -/** - * Enables or disables word-wrap on the label and marks it dirty. Pass - * wrapBuffer NULL to disable wrapping outright (wrapWidth is then - * ignored); enabling it requires both a non-NULL wrapBuffer and a - * wrapWidth greater than 0. - * - * @param label The label to configure. - * @param wrapBuffer Caller-owned scratch buffer, at least as large as the - * label's text, or NULL to disable wrapping. - * @param wrapBufferMax Capacity of wrapBuffer, including the null - * terminator. - * @param wrapWidth Maximum line width in pixels; must be greater than 0 - * when wrapBuffer is non-NULL. - */ -void uiLabelSetWrap( - uilabel_t *label, - char_t *wrapBuffer, - const uint32_t wrapBufferMax, - const float_t wrapWidth -); - -/** - * Rebuffers the label's cached sprites from its current text at its - * current x/y position. No-op unless the label is dirty. Called - * internally by uiLabelRender. - * - * @param label The label to rebuffer. - */ -void uiLabelRebuffer(uilabel_t *label); - -/** - * Renders the label's cached sprites at its current screen position. - * Rebuffers first if the label is dirty (text or position changed). - * - * @param label The label to render. - * @param color The color to render the text in. - * @return Any error that occurs. - */ -errorret_t uiLabelRender(uilabel_t *label, const color_t color); - -/** - * Disposes of a label. - * - * @param label The label to dispose. - */ -void uiLabelDispose(uilabel_t *label); diff --git a/src/dusk/ui/widget/uimenu.c b/src/dusk/ui/widget/uimenu.c deleted file mode 100644 index 6737aff5..00000000 --- a/src/dusk/ui/widget/uimenu.c +++ /dev/null @@ -1,444 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uimenu.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "display/text/text.h" -#include "display/color.h" -#include "ui/widget/uibutton.h" -#include "ui/widget/uitab.h" -#include "ui/widget/uislider.h" -#include "ui/widget/uidropdown.h" - -void uiMenuInit( - uimenu_t *menu, - uimenuselectedcallback_t selected, - uimenuclosedcallback_t closed, - uimenuchangedcallback_t changed -) { - assertNotNull(menu, "Menu cannot be NULL"); - memoryZero(menu, sizeof(uimenu_t)); - menu->selected = selected; - menu->closed = closed; - menu->changed = changed; -} - -void uiMenuSetItems( - uimenu_t *menu, - const uimenuitem_t *items, - const uint8_t itemCount, - const uint8_t columns -) { - assertNotNull(menu, "Menu cannot be NULL"); - assertNotNull(items, "Items cannot be NULL"); - assertTrue(itemCount > 0, "Item count must be > 0"); - assertTrue(columns > 0, "Columns must be > 0"); - menu->items = (uimenuitem_t *)items; - menu->itemCount = itemCount; - menu->columns = columns; -} - -void uiMenuSetPosition(uimenu_t *menu, const uint8_t x, const uint8_t y) { - assertNotNull(menu, "Menu cannot be NULL"); - if(menu->focusItem == NULL) return; - uiFocusSetPosition(menu->focusItem, x, y); -} - -void uiMenuOpen(uimenu_t *menu) { - assertNotNull(menu, "Menu cannot be NULL"); - assertNotNull(menu->items, "Menu items cannot be NULL"); - assertTrue(menu->itemCount > 0, "Menu item count must be > 0"); - assertTrue(menu->columns > 0, "Menu columns must be > 0"); - if(menu->focusItem != NULL) return; - - uint8_t focusable = uiMenuFocusableCount(menu); - uint8_t rows = focusable > 0 ? - (focusable + menu->columns - 1) / menu->columns : 1; - menu->focusItem = uiFocusPush( - menu->columns, rows, - uiMenuFocusSelected, - uiMenuFocusChanged, - uiMenuFocusClosed, - uiMenuFocusDirection, - menu - ); - menu->focusItem->disableBack = menu->disableBack; - menu->focusItem->cancel = menu->cancel != NULL ? uiMenuFocusCancel : NULL; -} - -void uiMenuClose(uimenu_t *menu) { - assertNotNull(menu, "Menu cannot be NULL"); - if(menu->focusItem == NULL) return; - uiFocusPopItem(menu->focusItem); - menu->focusItem = NULL; -} - -void uiMenuSetDisableBack(uimenu_t *menu, const bool_t disableBack) { - assertNotNull(menu, "Menu cannot be NULL"); - menu->disableBack = disableBack; - if(menu->focusItem != NULL) menu->focusItem->disableBack = disableBack; -} - -void uiMenuSetCancelCallback(uimenu_t *menu, uimenucancelcallback_t cancel) { - assertNotNull(menu, "Menu cannot be NULL"); - menu->cancel = cancel; - if(menu->focusItem != NULL) { - menu->focusItem->cancel = cancel != NULL ? uiMenuFocusCancel : NULL; - } -} - -bool_t uiMenuIsActive(const uimenu_t *menu) { - assertNotNull(menu, "Menu cannot be NULL"); - return menu->focusItem != NULL; -} - -errorret_t uiMenuDraw( - const uimenu_t *menu, - const float_t x, - const float_t y, - const float_t width, - const float_t height -) { - assertNotNull(menu, "Menu cannot be NULL"); - if(menu->itemCount == 0) errorOk(); - - float_t colStep = width / (float_t)menu->columns; - float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; - - uint8_t col = 0; - uint8_t row = 0; - - for(uint8_t i = 0; i < menu->itemCount; i++) { - uimenuitem_t *item = &menu->items[i]; - - if(item->type == UI_MENU_WIDGET_TYPE_LABEL) { - if(col > 0) { row++; col = 0; } - errorChain(textDraw( - x, - y + (float_t)row * rowHeight, - item->label, - COLOR_WHITE, - &FONT_DEFAULT - )); - row++; - continue; - } - - if(item->type == UI_MENU_WIDGET_TYPE_SPACER) { - if(col > 0) { row++; col = 0; } - row++; - continue; - } - - float_t ix = x + (float_t)col * colStep; - float_t iy = y + (float_t)row * rowHeight; - - switch(item->type) { - case UI_MENU_WIDGET_TYPE_CHECKBOX: - errorChain(uiCheckboxDraw(&item->checkbox, ix, iy)); - break; - - case UI_MENU_WIDGET_TYPE_BUTTON: - errorChain(uiButtonDraw(&item->button, ix, iy)); - break; - - case UI_MENU_WIDGET_TYPE_TAB: - errorChain(uiTabDraw(&item->tab, ix, iy)); - break; - - case UI_MENU_WIDGET_TYPE_SLIDER: - errorChain(uiSliderDraw(&item->slider, ix, iy)); - break; - - case UI_MENU_WIDGET_TYPE_DROPDOWN: - errorChain(uiDropdownDraw(&item->dropdown, ix, iy)); - break; - - default: - break; - } - - col++; - if(col >= menu->columns) { col = 0; row++; } - } - - errorOk(); -} - -bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem) { - assertNotNull(focusItem, "Focus item cannot be NULL"); - assertNotNull(focusItem->user, "Focus item user cannot be NULL"); - uimenu_t *menu = (uimenu_t *)focusItem->user; - if(menu->selected == NULL) return true; - - uint8_t slot = focusItem->y * menu->columns + focusItem->x; - uint8_t index = uiMenuFocusSlotToIndex(menu, slot); - if(index == 0xFF) return true; - - menu->selected(menu, index, &menu->items[index]); - return true; -} - -bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem) { - assertNotNull(focusItem, "Focus item cannot be NULL"); - assertNotNull(focusItem->user, "Focus item user cannot be NULL"); - uimenu_t *menu = (uimenu_t *)focusItem->user; - - uint8_t focusSlot = focusItem->y * menu->columns + focusItem->x; - uint8_t slot = 0; - - for(uint8_t i = 0; i < menu->itemCount; i++) { - uimenuitem_t *item = &menu->items[i]; - if( - item->type == UI_MENU_WIDGET_TYPE_LABEL || - item->type == UI_MENU_WIDGET_TYPE_SPACER - ) continue; - uiMenuItemSetHighlighted(item, slot == focusSlot); - slot++; - } - - if(menu->changed == NULL) return true; - uint8_t index = uiMenuFocusSlotToIndex(menu, focusSlot); - if(index == 0xFF) return true; - - menu->changed(menu, index, &menu->items[index]); - return true; -} - -bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem) { - assertNotNull(focusItem, "Focus item cannot be NULL"); - assertNotNull(focusItem->user, "Focus item user cannot be NULL"); - uimenu_t *menu = (uimenu_t *)focusItem->user; - menu->focusItem = NULL; - - for(uint8_t i = 0; i < menu->itemCount; i++) { - uiMenuItemSetHighlighted(&menu->items[i], false); - } - - if(menu->closed != NULL) menu->closed(menu); - return true; -} - -bool_t uiMenuFocusDirection( - const uifocusitem_t *focusItem, - const uifocusdirection_t direction -) { - assertNotNull(focusItem, "Focus item cannot be NULL"); - assertNotNull(focusItem->user, "Focus item user cannot be NULL"); - uimenu_t *menu = (uimenu_t *)focusItem->user; - - if( - direction == UI_FOCUS_DIRECTION_LEFT || - direction == UI_FOCUS_DIRECTION_RIGHT - ) { - uint8_t slot = focusItem->y * menu->columns + focusItem->x; - uint8_t index = uiMenuFocusSlotToIndex(menu, slot); - - if(index != 0xFF) { - uimenuitem_t *item = &menu->items[index]; - bool_t right = direction == UI_FOCUS_DIRECTION_RIGHT; - - if(item->type == UI_MENU_WIDGET_TYPE_SLIDER) { - if(right) { - uiSliderStepUp(&item->slider); - } else { - uiSliderStepDown(&item->slider); - } - return true; - } - - if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) { - uiCheckboxToggle(&item->checkbox); - return true; - } - - if(item->type == UI_MENU_WIDGET_TYPE_DROPDOWN) { - if(right) { - uiDropdownStepNext(&item->dropdown); - } else { - uiDropdownStepPrev(&item->dropdown); - } - return true; - } - } - } - - return uiMenuFocusSkipBlanks(menu, focusItem, direction); -} - -bool_t uiMenuItemIsBlank(const uimenuitem_t *item) { - return - item->type == UI_MENU_WIDGET_TYPE_BUTTON && - item->button.label.text[0] == '\0'; -} - -bool_t uiMenuFocusSkipBlanks( - uimenu_t *menu, - const uifocusitem_t *focusItem, - const uifocusdirection_t direction -) { - int8_t dx = 0; - int8_t dy = 0; - for( - const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - m->action != INPUT_ACTION_NULL; - m++ - ) { - if(m->direction != direction) continue; - dx = m->dx; - dy = m->dy; - break; - } - if(dx == 0 && dy == 0) return false; - - // Horizontal: a straight scan along the row. There's no "nearest" - // ambiguity here - the next non-blank cell in the pressed direction is - // always the right one, wrapping within the row as needed. - if(dy == 0) { - int16_t x = focusItem->x; - for(uint8_t i = 0; i < menu->columns; i++) { - x += dx; - if(x < 0) x += menu->columns; - x %= menu->columns; - - uint8_t slot = focusItem->y * menu->columns + (uint8_t)x; - uint8_t index = uiMenuFocusSlotToIndex(menu, slot); - // Treat a missing item (label/spacer slot) the same as a blank - // button - both get skipped over transparently. - if(index != 0xFF && !uiMenuItemIsBlank(&menu->items[index])) { - uiMenuSetPosition(menu, (uint8_t)x, focusItem->y); - return true; - } - } - return false; - } - - // Vertical: search every cell strictly further in the pressed - // direction (no wrap - the rows below are only ever "below", never - // "above via the far edge") and land on whichever non-blank cell has - // the smallest combined row+column distance from where we moved from. - // This lets an exact column match several rows away beat an - // approximate match one row away, while still reaching something like - // SPACE from a key with nothing directly below it. Ties on total - // distance favor the smaller column distance - e.g. moving straight - // down two rows onto an exact column match beats landing one row down - // one column over, even though both are three cells away. - int16_t rowStart = dy > 0 ? focusItem->y + 1 : 0; - int16_t rowEnd = dy > 0 ? focusItem->rows - 1 : focusItem->y - 1; - - bool_t found = false; - uint16_t bestDistance = 0; - uint8_t bestColDistance = 0; - uint8_t bestX = 0; - uint8_t bestY = 0; - - for(int16_t yy = rowStart; yy <= rowEnd; yy++) { - uint8_t rowDistance = dy > 0 ? - (uint8_t)(yy - focusItem->y) : (uint8_t)(focusItem->y - yy); - - for(uint8_t xx = 0; xx < menu->columns; xx++) { - uint8_t colDistance = xx > focusItem->x ? - xx - focusItem->x : focusItem->x - xx; - uint16_t distance = rowDistance + colDistance; - if(found && distance > bestDistance) continue; - if(found && distance == bestDistance && colDistance >= bestColDistance) { - continue; - } - - uint8_t slot = (uint8_t)yy * menu->columns + xx; - uint8_t index = uiMenuFocusSlotToIndex(menu, slot); - if(index == 0xFF || uiMenuItemIsBlank(&menu->items[index])) continue; - - found = true; - bestDistance = distance; - bestColDistance = colDistance; - bestX = xx; - bestY = (uint8_t)yy; - } - } - - // Nothing further in that direction at all (already on the edge row) - - // wrap to the opposite edge as a last resort, landing on its nearest - // column. This only ever kicks in when the non-wrapped search above - // found literally nothing, so it can't hijack a real nearby match the - // way wrapping during the main search could. - if(!found && focusItem->rows > 1) { - uint8_t wrapY = dy > 0 ? 0 : focusItem->rows - 1; - for(uint8_t xx = 0; xx < menu->columns; xx++) { - uint8_t colDistance = xx > focusItem->x ? - xx - focusItem->x : focusItem->x - xx; - if(found && colDistance >= bestDistance) continue; - - uint8_t slot = wrapY * menu->columns + xx; - uint8_t index = uiMenuFocusSlotToIndex(menu, slot); - if(index == 0xFF || uiMenuItemIsBlank(&menu->items[index])) continue; - - found = true; - bestDistance = colDistance; - bestX = xx; - bestY = wrapY; - } - } - - if(found) { - uiMenuSetPosition(menu, bestX, bestY); - return true; - } - - // Nothing non-blank in that direction at all - fall through to the - // default single-step move. - return false; -} - -bool_t uiMenuFocusCancel(const uifocusitem_t *focusItem) { - assertNotNull(focusItem, "Focus item cannot be NULL"); - assertNotNull(focusItem->user, "Focus item user cannot be NULL"); - uimenu_t *menu = (uimenu_t *)focusItem->user; - if(menu->cancel == NULL) return false; - return menu->cancel(menu); -} - -uint8_t uiMenuFocusableCount(const uimenu_t *menu) { - assertNotNull(menu, "Menu cannot be NULL"); - uint8_t count = 0; - for(uint8_t i = 0; i < menu->itemCount; i++) { - uimenuwidgettype_t t = menu->items[i].type; - if(t != UI_MENU_WIDGET_TYPE_LABEL && t != UI_MENU_WIDGET_TYPE_SPACER) { - count++; - } - } - return count; -} - -uint8_t uiMenuFocusSlotToIndex(const uimenu_t *menu, const uint8_t slot) { - assertNotNull(menu, "Menu cannot be NULL"); - uint8_t focusable = 0; - for(uint8_t i = 0; i < menu->itemCount; i++) { - uimenuwidgettype_t t = menu->items[i].type; - if(t == UI_MENU_WIDGET_TYPE_LABEL || t == UI_MENU_WIDGET_TYPE_SPACER) { - continue; - } - if(focusable == slot) return i; - focusable++; - } - return 0xFF; -} - -void uiMenuItemSetHighlighted(uimenuitem_t *item, const bool_t highlighted) { - if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) { - uiCheckboxSetHighlighted(&item->checkbox, highlighted); - } else if(item->type == UI_MENU_WIDGET_TYPE_BUTTON) { - uiButtonSetHighlighted(&item->button, highlighted); - } else if(item->type == UI_MENU_WIDGET_TYPE_TAB) { - uiTabSetActive(&item->tab, highlighted); - } else if(item->type == UI_MENU_WIDGET_TYPE_SLIDER) { - uiSliderSetHighlighted(&item->slider, highlighted); - } else if(item->type == UI_MENU_WIDGET_TYPE_DROPDOWN) { - uiDropdownSetHighlighted(&item->dropdown, highlighted); - } -} diff --git a/src/dusk/ui/widget/uimenu.h b/src/dusk/ui/widget/uimenu.h deleted file mode 100644 index 88abc38f..00000000 --- a/src/dusk/ui/widget/uimenu.h +++ /dev/null @@ -1,386 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/focus/uifocus.h" -#include "ui/widget/uibutton.h" -#include "ui/widget/uicheckbox.h" -#include "ui/widget/uitab.h" -#include "ui/widget/uislider.h" -#include "ui/widget/uidropdown.h" - -#define UI_MENU_LABEL_MAX UI_CHECKBOX_LABEL_MAX - -typedef struct uimenu_s uimenu_t; - -typedef enum { - UI_MENU_WIDGET_TYPE_NONE, - UI_MENU_WIDGET_TYPE_LABEL, - UI_MENU_WIDGET_TYPE_SPACER, - UI_MENU_WIDGET_TYPE_CHECKBOX, - UI_MENU_WIDGET_TYPE_BUTTON, - UI_MENU_WIDGET_TYPE_TAB, - UI_MENU_WIDGET_TYPE_SLIDER, - UI_MENU_WIDGET_TYPE_DROPDOWN, -} uimenuwidgettype_t; - -typedef struct { - uimenuwidgettype_t type; - union { - char_t *label; - uicheckbox_t checkbox; - uibutton_t button; - uitab_t tab; - uislider_t slider; - uidropdown_t dropdown; - }; -} uimenuitem_t; - -typedef void (*uimenuselectedcallback_t)( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -); - -typedef void (*uimenuchangedcallback_t)( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -); - -typedef void (*uimenuclosedcallback_t)(const uimenu_t *menu); - -/** - * Callback invoked when INPUT_ACTION_CANCEL is pressed while the menu is - * focused - see uiMenuSetCancelCallback. - * - * @param menu The menu that was cancelled. - * @returns True if fully handled (skips the default disableBack/pop - * behavior); false to fall through to it. - */ -typedef bool_t (*uimenucancelcallback_t)(const uimenu_t *menu); - -typedef struct uimenu_s { - uimenuitem_t *items; - uint8_t itemCount; - uint8_t columns; - uifocusitem_t *focusItem; - - uimenuselectedcallback_t selected; - uimenuclosedcallback_t closed; - uimenuchangedcallback_t changed; - uimenucancelcallback_t cancel; - - void *user; - - // See uiMenuSetDisableBack. - bool_t disableBack; -} uimenu_t; - -/** - * Initializes a menu, clearing all items and focus state. - * - * @param menu The menu to initialize. - * @param items The list of items to display in the menu. - * @param itemCount The number of items in the list. - * @param columns The number of columns to display the items in. - * @param selected The callback to invoke when an item is selected. - * @param closed The callback to invoke when the menu is closed. - * @param changed The callback to invoke when the menu changes. - */ -void uiMenuInit( - uimenu_t *menu, - uimenuselectedcallback_t selected, - uimenuclosedcallback_t closed, - uimenuchangedcallback_t changed -); - -/** - * Sets the items to display in the menu. - * - * @param menu The menu to update. - * @param items The list of items to display in the menu. - * @param itemCount The number of items in the list. - * @param columns The number of columns to display the items in. - */ -void uiMenuSetItems( - uimenu_t *menu, - const uimenuitem_t *items, - const uint8_t itemCount, - const uint8_t columns -); - -/** - * Sets the position of the menu on the screen. - * - * @param menu The menu to position. - * @param x The x-coordinate to position the menu at. - * @param y The y-coordinate to position the menu at. - */ -void uiMenuSetPosition(uimenu_t *menu, const uint8_t x, const uint8_t y); - -/** - * Pushes a menu onto the UI focus stack, making it the active menu. - * - * @param menu The menu to push. - */ -void uiMenuOpen(uimenu_t *menu); - -/** - * Pops a menu from the UI focus stack, removing it from the active menu. - * - * @param menu The menu to pop. - */ -void uiMenuClose(uimenu_t *menu); - -/** - * Sets whether pressing back/cancel is ignored while this menu is the - * topmost focus item - it simply does nothing, rather than closing the - * menu. Does not affect a deliberate uiMenuClose() call. Safe to call - * before or after uiMenuOpen(). - * - * @param menu The menu to update. - * @param disableBack True to ignore back/cancel input for this menu. - */ -void uiMenuSetDisableBack(uimenu_t *menu, const bool_t disableBack); - -/** - * Sets a callback given first refusal of INPUT_ACTION_CANCEL while this - * menu is the topmost focus item, before disableBack/the default pop is - * applied. Safe to call before or after uiMenuOpen(). - * - * @param menu The menu to update. - * @param cancel The callback to install, or NULL to remove it and - * restore default disableBack/pop behavior. - */ -void uiMenuSetCancelCallback(uimenu_t *menu, uimenucancelcallback_t cancel); - -/** - * Returns whether the menu is currently active (on the UI focus stack). - * - * @param menu The menu to query. - * @returns True if the menu is active. - */ -bool_t uiMenuIsActive(const uimenu_t *menu); - -/** - * Draws the menu at the specified position and size. - * - * @param menu The menu to draw. - * @param x The x-coordinate to draw the menu at. - * @param y The y-coordinate to draw the menu at. - * @param width The width of the menu. - * @param height The height of the menu. - * @returns An error code indicating success or failure. - */ -errorret_t uiMenuDraw( - const uimenu_t *menu, - const float_t x, - const float_t y, - const float_t width, - const float_t height -); - -/** - * Returns the number of focusable (non-label) items in the menu. - * - * @param menu The menu to query. - * @returns Count of non-label items. - */ -uint8_t uiMenuFocusableCount(const uimenu_t *menu); - -/** - * Maps a flat focus slot index to the corresponding item array index, - * skipping over label items which are not focusable. - * - * @param menu The menu to query. - * @param slot The focus slot index (y * columns + x). - * @returns The item array index, or 0xFF if out of range. - */ -uint8_t uiMenuFocusSlotToIndex(const uimenu_t *menu, const uint8_t slot); - -/** - * Sets the highlighted/active state on a menu item, dispatching to the - * matching widget's setter based on its type. No-op for label/spacer - * items and any other type with no highlight concept. - * - * @param item The item to update. - * @param highlighted The new highlighted/active state. - */ -void uiMenuItemSetHighlighted(uimenuitem_t *item, const bool_t highlighted); - -/** - * Internal focus callback - forwards selection to the menu's selected - * handler. - * - * @param focusItem The active focus item; user field must point to uimenu_t. - * @returns True. - */ -bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem); - -/** - * Internal focus callback - updates widget highlights and fires changed. - * - * @param focusItem The active focus item; user field must point to uimenu_t. - * @returns True. - */ -bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem); - -/** - * Internal focus callback - clears focusItem and fires the closed handler. - * - * @param focusItem The active focus item; user field must point to uimenu_t. - * @returns True. - */ -bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem); - -/** - * Internal focus callback - gives the focused item's widget a chance to - * handle LEFT/RIGHT itself (e.g. a slider adjusting its value); failing - * that, moves to the next non-blank cell in the given direction (see - * uiMenuFocusSkipBlanks) rather than the default single-step move, which - * would happily land on a deliberately-blank layout cell (e.g. a - * UI_MENU_WIDGET_TYPE_BUTTON with an empty label, used as filler). - * - * @param focusItem The active focus item; user field must point to uimenu_t. - * @param direction The direction that was pressed or held. - * @returns True if handled (a widget adjusted itself, or focus moved to - * a non-blank cell); false to fall through to the default single-step - * move (only when every cell along this line is blank). - */ -bool_t uiMenuFocusDirection( - const uifocusitem_t *focusItem, - const uifocusdirection_t direction -); - -/** - * True for a menu item that should be transparently skipped during - * d-pad navigation - a UI_MENU_WIDGET_TYPE_BUTTON with an empty label, - * used purely to hold a grid cell's position (the menu/focus grid has no - * concept of a row narrower than its column count, so short rows are - * padded with these instead). Labels/spacers are unaffected by this - - * they're already excluded from the focus grid entirely. - * - * @param item The item to check. - * @returns True if this item is blank filler. - */ -bool_t uiMenuItemIsBlank(const uimenuitem_t *item); - -/** - * Moves focusItem to a non-blank cell (see uiMenuItemIsBlank) in the - * given direction, wrapping the grid as many times as needed - so, from - * the menu's perspective, a blank cell simply isn't there. - * - * LEFT/RIGHT scan straight along the row, since there's no ambiguity - * about which non-blank cell is "next" (wrapping within the row as - * needed). UP/DOWN instead considers every cell strictly further in the - * pressed direction and lands on whichever non-blank cell has the - * smallest combined row+column distance from the one moved from - e.g. - * moving down off a densely-packed row onto a mostly-blank one lands on - * whatever's closest instead of only ever reaching a cell that happens - * to share the exact same column, while an exact column match a couple - * of rows down still wins over an approximate match one row down. Ties - * on total distance favor the smaller column distance, so a straight - * move down onto an exact column match beats an equally-far diagonal - * one. This search never wraps in either axis while there's still a - * real cell further in the pressed direction - column/row 0 is never - * treated as adjacent to the far edge just because the grid wraps in - * general. Only once already on the edge row (nothing further to search - * at all) does it fall back to wrapping to the opposite edge's nearest - * column, so pressing up from the top row still goes somewhere instead - * of doing nothing. - * - * Gives up (returning false, so the default single-step move applies - * instead) only if every cell on the grid is blank. - * - * @param menu The menu to move within. - * @param focusItem The active focus item; must belong to menu. - * @param direction The direction that was pressed or held. - * @returns True if a non-blank cell was found and focus moved there. - */ -bool_t uiMenuFocusSkipBlanks( - uimenu_t *menu, - const uifocusitem_t *focusItem, - const uifocusdirection_t direction -); - -/** - * Internal focus callback - forwards INPUT_ACTION_CANCEL to the menu's - * cancel handler, if one is set. - * - * @param focusItem The active focus item; user field must point to uimenu_t. - * @returns The cancel handler's result, or false if none is set. - */ -bool_t uiMenuFocusCancel(const uifocusitem_t *focusItem); - -// Helper macros -#define MENU_BEGIN(menuPtr, itemsArray, selected, closed, changed) \ - uimenu_t *menu = (menuPtr); \ - uiMenuInit(menu, selected, closed, changed); \ - menu->items = (itemsArray); \ - uint8_t menuIndex = 0; \ - uint8_t menuCapacity = sizeof(itemsArray) / sizeof((itemsArray)[0]) - -#define MENU_LABEL(text) \ - assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \ - menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_LABEL; \ - menu->items[menuIndex].label = text; \ - ++menuIndex - -#define MENU_SPACER() \ - assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \ - menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SPACER; \ - ++menuIndex - -#define MENU_CHECKBOX(label) \ - assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \ - menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_CHECKBOX; \ - uiCheckboxInit(&menu->items[menuIndex].checkbox, label); \ - ++menuIndex - -#define MENU_BUTTON(label) \ - assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \ - menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_BUTTON; \ - uiButtonInit(&menu->items[menuIndex].button, label); \ - ++menuIndex - -#define MENU_TAB(label) \ - assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \ - menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_TAB; \ - uiTabInit(&menu->items[menuIndex].tab, label); \ - ++menuIndex - -#define MENU_SLIDER_FLOAT(label, value, min, max, step) \ - assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \ - menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SLIDER; \ - uiSliderInitFloat( \ - &menu->items[menuIndex].slider, label, value, min, max, step \ - ); \ - ++menuIndex - -#define MENU_SLIDER_INT(label, value, min, max, step) \ - assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \ - menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SLIDER; \ - uiSliderInitInt( \ - &menu->items[menuIndex].slider, label, value, min, max, step \ - ); \ - ++menuIndex - -#define MENU_DROPDOWN(label, options, optionCount, selectedIndex) \ - assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \ - menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_DROPDOWN; \ - uiDropdownInit( \ - &menu->items[menuIndex].dropdown, label, options, optionCount, \ - selectedIndex \ - ); \ - ++menuIndex - -#define MENU_END(itemsArray, columns) \ - uiMenuSetItems(menu, (itemsArray), menuIndex, (columns)) - -//EOF diff --git a/src/dusk/ui/widget/uimodal.c b/src/dusk/ui/widget/uimodal.c deleted file mode 100644 index 9276f26b..00000000 --- a/src/dusk/ui/widget/uimodal.c +++ /dev/null @@ -1,264 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uimodal.h" -#include "ui/widget/uiframe.h" -#include "ui/focus/uifocus.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "util/math.h" -#include "display/screen/screen.h" -#include "display/text/text.h" -#include "display/color.h" -#include "display/spritebatch/spritebatch.h" -#include "display/texture/texture.h" -#include "display/shader/shaderunlit.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -#define UI_MODAL_BACKDROP_COLOR color4b(0, 0, 0, 160) - -uimodal_t UI_MODAL; - -// Focus item backing a message-only modal (no options) - lets a plain -// MODAL still block on player confirmation. NULL whenever a -// message-only modal isn't open. Mirrors uitextboxmain.c's pattern. -static uifocusitem_t *messageFocus = NULL; - -void uiModalSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - UI_MODAL.result = index; - uiModalClose(NULL); -} - -void uiModalClosed(const uimenu_t *menu) { - if(UI_MODAL.callback != NULL) { - UI_MODAL.callback(UI_MODAL.result, UI_MODAL.user); - } -} - -bool_t uiModalMessageFocusSelected(const uifocusitem_t *item) { - uiFocusPopItem(messageFocus); - return true; -} - -bool_t uiModalMessageFocusClosed(const uifocusitem_t *item) { - messageFocus = NULL; - UI_MODAL.open = false; - uiModalClosed(NULL); - return true; -} - -errorret_t uiModalInit(void) { - memoryZero(&UI_MODAL, sizeof(uimodal_t)); - - uiLabelInit( - &UI_MODAL.titleLabel, UI_MODAL.titleText, - UI_MODAL.titleSprites, UI_MODAL_TITLE_SPRITES_MAX - ); - uiLabelInit( - &UI_MODAL.messageLabel, UI_MODAL.messageText, - UI_MODAL.messageSprites, UI_MODAL_MESSAGE_SPRITES_MAX - ); - - errorOk(); -} - -errorret_t uiModalDraw(void) { - if(!UI_MODAL.open) errorOk(); - - bool_t hasOptions = UI_MODAL.menu.itemCount > 0; - - spritebatchsprite_t backdropSprite = { - .min = { 0.0f, 0.0f, 0.0f }, - .max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - shadermaterial_t backdropMaterial = { - .unlit = { - .color = UI_MODAL_BACKDROP_COLOR, - .texture = &TEXTURE_WHITE - } - }; - errorChain( - spriteBatchBuffer(&backdropSprite, 1, &SHADER_UNLIT, backdropMaterial) - ); - errorChain(spriteBatchFlush()); - - float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; - float_t contentWidth = mathMax( - mathMax( - (float_t)UI_MODAL.titleLabel.width, (float_t)UI_MODAL.messageLabel.width - ), - UI_MODAL_MIN_WIDTH - (UI_FRAME_START_X * 2) - ); - float_t width = contentWidth + (UI_FRAME_START_X * 2); - float_t height = (UI_FRAME_START_Y * 2) - + (UI_MODAL.hasTitle ? - (float_t)UI_MODAL.titleLabel.height + UI_FRAME_PADDING_Y : 0.0f) - + (float_t)UI_MODAL.messageLabel.height - + (hasOptions ? UI_FRAME_PADDING_Y + rowHeight : 0.0f); - - float_t x = (float_t)SCREEN.scanX + - ((float_t)SCREEN.scanWidth - width) * 0.5f; - float_t y = (float_t)SCREEN.scanY + - ((float_t)SCREEN.scanHeight - height) * 0.5f; - - errorChain(uiFrameDraw(x, y, width, height)); - - float_t contentX = x + UI_FRAME_START_X; - float_t contentY = y + UI_FRAME_START_Y; - - float_t messageY = contentY; - if(UI_MODAL.hasTitle) { - uiLabelSetX(&UI_MODAL.titleLabel, contentX); - uiLabelSetY(&UI_MODAL.titleLabel, contentY); - errorChain(uiLabelRender(&UI_MODAL.titleLabel, COLOR_WHITE)); - - messageY = contentY + (float_t)UI_MODAL.titleLabel.height + - UI_FRAME_PADDING_Y; - } - - uiLabelSetX(&UI_MODAL.messageLabel, contentX); - uiLabelSetY(&UI_MODAL.messageLabel, messageY); - errorChain(uiLabelRender(&UI_MODAL.messageLabel, COLOR_WHITE)); - - if(hasOptions) { - float_t buttonsY = messageY + (float_t)UI_MODAL.messageLabel.height + - UI_FRAME_PADDING_Y; - errorChain( - uiMenuDraw(&UI_MODAL.menu, contentX, buttonsY, contentWidth, rowHeight) - ); - } - - errorChain(spriteBatchFlush()); - errorOk(); -} - -void uiModalLocalize( - const char_t *key, - char_t *buffer, - const size_t bufferSize -) { - if(key[0] == '\0') { - buffer[0] = '\0'; - return; - } - - errorret_t result = localeManagerGetString(key, buffer, bufferSize, 0); - if(errorIsNotOk(result)) { - errorCatch(result); - stringCopy(buffer, key, bufferSize - 1); - } -} - -bool_t uiModalIsOpen(void) { - return UI_MODAL.open; -} - -uint8_t uiModalGetResult(void) { - return UI_MODAL.result; -} - -void uiModalOpen( - const char_t *title, - const char_t *message, - const char_t **options, - const uint8_t optionCount, - uimodaloptioncallback_t callback, - uimodalopenedcallback_t onOpen, - void *user -) { - assertNotNull(message, "Message cannot be NULL"); - assertTrue( - options != NULL || optionCount == 0, - "Options cannot be NULL when optionCount > 0" - ); - assertTrue( - optionCount <= UI_MODAL_OPTIONS_MAX, "Too many options for modal" - ); - - float_t maxContentWidth = - ((float_t)SCREEN.scanWidth * UI_MODAL_MAX_WIDTH_RATIO) - - (UI_FRAME_START_X * 2.0f); - - UI_MODAL.hasTitle = title != NULL; - if(UI_MODAL.hasTitle) { - uiModalLocalize(title, UI_MODAL.titleText, UI_MODAL_TITLE_TEXT_MAX); - textWrap(UI_MODAL.titleText, &FONT_DEFAULT, maxContentWidth); - } else { - UI_MODAL.titleText[0] = '\0'; - } - UI_MODAL.titleLabel.dirty = true; - uiLabelRebuffer(&UI_MODAL.titleLabel); - - uiModalLocalize(message, UI_MODAL.messageText, UI_MODAL_MESSAGE_TEXT_MAX); - textWrap(UI_MODAL.messageText, &FONT_DEFAULT, maxContentWidth); - UI_MODAL.messageLabel.dirty = true; - uiLabelRebuffer(&UI_MODAL.messageLabel); - - UI_MODAL.callback = callback; - UI_MODAL.onOpen = onOpen; - UI_MODAL.user = user; - UI_MODAL.result = UI_MODAL_RESULT_NONE; - UI_MODAL.open = true; - - if(options != NULL) { - MENU_BEGIN( - &UI_MODAL.menu, UI_MODAL.options, uiModalSelected, uiModalClosed, NULL - ); - for(uint8_t i = 0; i < optionCount; i++) { - uiModalLocalize( - options[i], UI_MODAL.optionsText[i], UI_MODAL_OPTION_TEXT_MAX - ); - MENU_BUTTON(UI_MODAL.optionsText[i]); - } - MENU_END(UI_MODAL.options, menuIndex); - - // A dialog with options must be dismissed by picking one, not by - // backing out of it - see uiMenuSetDisableBack. - uiMenuSetDisableBack(&UI_MODAL.menu, true); - uiMenuOpen(&UI_MODAL.menu); - } else { - uiMenuInit(&UI_MODAL.menu, uiModalSelected, uiModalClosed, NULL); - messageFocus = uiFocusPush( - 1, 1, - uiModalMessageFocusSelected, NULL, uiModalMessageFocusClosed, - NULL, NULL - ); - } - - if(UI_MODAL.onOpen != NULL) { - UI_MODAL.onOpen(UI_MODAL.user); - } -} - -void uiModalClose(uimodalclosedcallback_t callback) { - if(!UI_MODAL.open) return; - UI_MODAL.open = false; - - if(uiMenuIsActive(&UI_MODAL.menu)) { - uiMenuClose(&UI_MODAL.menu); - } else if(messageFocus != NULL) { - uiFocusPopItem(messageFocus); - } else { - uiModalClosed(NULL); - } - - if(callback != NULL) { - callback(UI_MODAL.user); - } -} - -errorret_t uiModalDispose(void) { - errorOk(); -} diff --git a/src/dusk/ui/widget/uimodal.h b/src/dusk/ui/widget/uimodal.h deleted file mode 100644 index 2a10b13f..00000000 --- a/src/dusk/ui/widget/uimodal.h +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" -#include "ui/widget/uilabel.h" -#include "ui/widget/uimenu.h" - -#define UI_MODAL_TITLE_TEXT_MAX 64 -#define UI_MODAL_TITLE_SPRITES_MAX UI_MODAL_TITLE_TEXT_MAX -#define UI_MODAL_MESSAGE_TEXT_MAX 256 -#define UI_MODAL_MESSAGE_SPRITES_MAX UI_MODAL_MESSAGE_TEXT_MAX -#define UI_MODAL_OPTIONS_MAX 4 -#define UI_MODAL_OPTION_TEXT_MAX 32 -#define UI_MODAL_MIN_WIDTH 160.0f -// Title/message text wraps at word boundaries to stay within this -// fraction of the screen's scan width, so long (e.g. translated) -// strings don't run off-screen. -#define UI_MODAL_MAX_WIDTH_RATIO 0.8f -#define UI_MODAL_RESULT_NONE 0xFF - -/** - * Callback invoked once a modal is dismissed. - * - * @param optionIndex Index into the options array passed to uiModalOpen - * that was selected. A dialog with options ignores back/cancel input - * (see uiMenuSetDisableBack) and must be dismissed by picking one, so - * UI_MODAL_RESULT_NONE only reaches here via a direct uiModalClose() - * call made before any option was picked. - * @param user Arbitrary pointer passed to uiModalOpen. - */ -typedef void (*uimodaloptioncallback_t)(const uint8_t optionIndex, void *user); - -/** - * Callback invoked when a modal opens. Fired immediately for now, but - * gives a hook point for a future open transition to key off of. - * - * @param user Arbitrary pointer passed to uiModalOpen. - */ -typedef void (*uimodalopenedcallback_t)(void *user); - -/** - * Callback invoked when a modal closes, passed to uiModalClose. - * - * @param user Arbitrary pointer passed to uiModalOpen. - */ -typedef void (*uimodalclosedcallback_t)(void *user); - -typedef struct { - uilabel_t titleLabel; - char_t titleText[UI_MODAL_TITLE_TEXT_MAX]; - spritebatchsprite_t titleSprites[UI_MODAL_TITLE_SPRITES_MAX]; - - uilabel_t messageLabel; - char_t messageText[UI_MODAL_MESSAGE_TEXT_MAX]; - spritebatchsprite_t messageSprites[UI_MODAL_MESSAGE_SPRITES_MAX]; - - uimenu_t menu; - uimenuitem_t options[UI_MODAL_OPTIONS_MAX]; - char_t optionsText[UI_MODAL_OPTIONS_MAX][UI_MODAL_OPTION_TEXT_MAX]; - - uimodaloptioncallback_t callback; - uimodalopenedcallback_t onOpen; - void *user; - uint8_t result; - bool_t open; - bool_t hasTitle; -} uimodal_t; - -extern uimodal_t UI_MODAL; - -/** - * Initializes the modal dialog. - * - * @return Any error that occurs. - */ -errorret_t uiModalInit(void); - -/** - * Draws the modal dialog: a semi-transparent black backdrop covering the - * whole screen, then its own centered frame with the title, message, and - * option buttons. No-op when not open. - * - * @return Any error that occurs. - */ -errorret_t uiModalDraw(void); - -/** - * Returns true when the modal dialog is currently open. - * - * @returns True if open. - */ -bool_t uiModalIsOpen(void); - -/** - * Returns the result of the most recently dismissed modal. - * - * @returns The selected option index, or UI_MODAL_RESULT_NONE. - */ -uint8_t uiModalGetResult(void); - -/** - * Opens the modal dialog with the given title, message, and options. - * callback is invoked exactly once with the selected option. A dialog - * with options > 0 disables back/cancel (see uiMenuSetDisableBack), so - * it must be dismissed by picking one. - * - * title, message, and each option are treated as locale message IDs - * first: if one matches an entry in the active locale file its - * translated string is used, otherwise the string itself is displayed - * verbatim. See uiModalLocalize. - * - * @param title Display title, or a locale message ID; copied - * internally, safe to be transient. May be NULL for a dialog with no - * title row. - * @param message Display message, or a locale message ID; copied - * internally, safe to be transient. - * @param options Array of option label strings/locale message IDs; - * copied internally, safe to be transient. May be NULL for a - * message-only dialog with no option buttons, in which case optionCount - * must be 0. - * @param optionCount Number of options, from 0 to UI_MODAL_OPTIONS_MAX. - * @param callback Called with the result once the dialog closes. May be - * NULL. - * @param onOpen Called once the modal has opened. May be NULL. - * @param user Arbitrary pointer passed through to callback and onOpen, - * and to whatever callback is later passed to uiModalClose. - */ -void uiModalOpen( - const char_t *title, - const char_t *message, - const char_t **options, - const uint8_t optionCount, - uimodaloptioncallback_t callback, - uimodalopenedcallback_t onOpen, - void *user -); - -/** - * Closes the modal dialog. No-op when already closed. - * - * @param callback Called with the modal's user pointer once the dialog - * has closed, after the option-result callback set by uiModalOpen. May - * be NULL. Not called if the modal was already closed. - */ -void uiModalClose(uimodalclosedcallback_t callback); - -/** - * Disposes of the modal dialog. - * - * @return Any error that occurs. - */ -errorret_t uiModalDispose(void); - -/** - * Resolves display text for uiModalOpen: if key matches a locale - * message ID in the active locale file, buffer is filled with its - * translated string, otherwise key is copied into buffer verbatim. An - * empty key (e.g. an omitted JSON title/message, which defaults to "" - * rather than NULL - see cutscenemodal_t/cutscenemodaloptionsmarkers_t) - * is left as an empty buffer rather than looked up, since - * assetLocaleGetString rejects an empty message ID outright. - * - * @param key Display string or locale message ID. - * @param buffer Destination buffer. - * @param bufferSize Size of buffer, in bytes. - */ -void uiModalLocalize( - const char_t *key, - char_t *buffer, - const size_t bufferSize -); diff --git a/src/dusk/ui/widget/uiscrolling.c b/src/dusk/ui/widget/uiscrolling.c deleted file mode 100644 index 516caf00..00000000 --- a/src/dusk/ui/widget/uiscrolling.c +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiscrolling.h" -#include "assert/assert.h" -#include "util/memory.h" - -void uiScrollingInit(uiscrolling_t *scrolling) { - assertNotNull(scrolling, "Scrolling container cannot be NULL"); - memoryZero(scrolling, sizeof(uiscrolling_t)); -} - -void uiScrollingEnsureVisible( - uiscrolling_t *scrolling, - const uint8_t index, - const uint8_t itemCount, - const uint8_t visibleCount -) { - assertNotNull(scrolling, "Scrolling container cannot be NULL"); - assertTrue(visibleCount > 0, "Visible count must be > 0"); - - if(index < scrolling->offset) { - scrolling->offset = index; - } else if(index >= (uint8_t)(scrolling->offset + visibleCount)) { - scrolling->offset = (uint8_t)(index - visibleCount + 1); - } - - uint8_t maxOffset = itemCount > visibleCount ? itemCount - visibleCount : 0; - if(scrolling->offset > maxOffset) scrolling->offset = maxOffset; -} diff --git a/src/dusk/ui/widget/uiscrolling.h b/src/dusk/ui/widget/uiscrolling.h deleted file mode 100644 index da47729a..00000000 --- a/src/dusk/ui/widget/uiscrolling.h +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef struct { - uint8_t offset; -} uiscrolling_t; - -/** - * Initializes a scrolling container with a zero offset. - * - * @param scrolling The scrolling container to initialize. - */ -void uiScrollingInit(uiscrolling_t *scrolling); - -/** - * Scrolls by the minimum amount needed to bring index within the visible - * window of visibleCount rows, then clamps so the window never scrolls - * past the end of itemCount rows. - * - * @param scrolling The scrolling container to update. - * @param index Row index that must end up visible. - * @param itemCount Total number of rows in the list. - * @param visibleCount Number of rows visible at once. - */ -void uiScrollingEnsureVisible( - uiscrolling_t *scrolling, - const uint8_t index, - const uint8_t itemCount, - const uint8_t visibleCount -); diff --git a/src/dusk/ui/widget/uislider.c b/src/dusk/ui/widget/uislider.c deleted file mode 100644 index 5b30bfe9..00000000 --- a/src/dusk/ui/widget/uislider.c +++ /dev/null @@ -1,244 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uislider.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/math.h" -#include "util/string.h" -#include "display/text/text.h" -#include "display/color.h" -#include "display/spritebatch/spritebatch.h" -#include "display/texture/texture.h" -#include "display/shader/shaderunlit.h" - -void uiSliderInitFloat( - uislider_t *slider, - const char_t *label, - const float_t value, - const float_t min, - const float_t max, - const float_t step -) { - assertNotNull(slider, "Slider cannot be NULL"); - assertNotNull(label, "Label cannot be NULL"); - assertTrue(max > min, "Slider max must be greater than min"); - assertTrue(step > 0.0f, "Slider step must be greater than zero"); - - memoryZero(slider, sizeof(uislider_t)); - slider->label = label; - slider->type = UI_SLIDER_TYPE_FLOAT; - slider->min.f = min; - slider->max.f = max; - slider->step.f = step; - slider->value.f = mathClamp(value, min, max); -} - -void uiSliderInitInt( - uislider_t *slider, - const char_t *label, - const int32_t value, - const int32_t min, - const int32_t max, - const int32_t step -) { - assertNotNull(slider, "Slider cannot be NULL"); - assertNotNull(label, "Label cannot be NULL"); - assertTrue(max > min, "Slider max must be greater than min"); - assertTrue(step > 0, "Slider step must be greater than zero"); - - memoryZero(slider, sizeof(uislider_t)); - slider->label = label; - slider->type = UI_SLIDER_TYPE_INT; - slider->min.i = min; - slider->max.i = max; - slider->step.i = step; - slider->value.i = mathClamp(value, min, max); -} - -float_t uiSliderGetFloat(const uislider_t *slider) { - assertNotNull(slider, "Slider cannot be NULL"); - return slider->type == UI_SLIDER_TYPE_INT ? - (float_t)slider->value.i : slider->value.f; -} - -int32_t uiSliderGetInt(const uislider_t *slider) { - assertNotNull(slider, "Slider cannot be NULL"); - assertTrue( - slider->type == UI_SLIDER_TYPE_INT, "Slider is not an int slider" - ); - return slider->value.i; -} - -void uiSliderSetFloat(uislider_t *slider, const float_t value) { - assertNotNull(slider, "Slider cannot be NULL"); - assertTrue( - slider->type == UI_SLIDER_TYPE_FLOAT, "Slider is not a float slider" - ); - slider->value.f = mathClamp(value, slider->min.f, slider->max.f); -} - -void uiSliderSetInt(uislider_t *slider, const int32_t value) { - assertNotNull(slider, "Slider cannot be NULL"); - assertTrue( - slider->type == UI_SLIDER_TYPE_INT, "Slider is not an int slider" - ); - slider->value.i = mathClamp(value, slider->min.i, slider->max.i); -} - -void uiSliderStepUp(uislider_t *slider) { - assertNotNull(slider, "Slider cannot be NULL"); - if(slider->type == UI_SLIDER_TYPE_INT) { - int32_t next = slider->value.i + slider->step.i; - uiSliderSetInt(slider, next > slider->max.i ? slider->min.i : next); - } else { - float_t next = slider->value.f + slider->step.f; - uiSliderSetFloat(slider, next > slider->max.f ? slider->min.f : next); - } -} - -void uiSliderStepDown(uislider_t *slider) { - assertNotNull(slider, "Slider cannot be NULL"); - if(slider->type == UI_SLIDER_TYPE_INT) { - int32_t next = slider->value.i - slider->step.i; - uiSliderSetInt(slider, next < slider->min.i ? slider->max.i : next); - } else { - float_t next = slider->value.f - slider->step.f; - uiSliderSetFloat(slider, next < slider->min.f ? slider->max.f : next); - } -} - -float_t uiSliderGetRatio(const uislider_t *slider) { - assertNotNull(slider, "Slider cannot be NULL"); - if(slider->type == UI_SLIDER_TYPE_INT) { - if(slider->max.i == slider->min.i) return 0.0f; - return (float_t)(slider->value.i - slider->min.i) / - (float_t)(slider->max.i - slider->min.i); - } - - if(slider->max.f == slider->min.f) return 0.0f; - return (slider->value.f - slider->min.f) / (slider->max.f - slider->min.f); -} - -int32_t uiSliderGetStepCount(const uislider_t *slider) { - assertNotNull(slider, "Slider cannot be NULL"); - if(slider->type != UI_SLIDER_TYPE_INT || slider->step.i <= 0) return 0; - return (slider->max.i - slider->min.i) / slider->step.i; -} - -bool_t uiSliderIsHighlighted(const uislider_t *slider) { - assertNotNull(slider, "Slider cannot be NULL"); - return slider->highlighted; -} - -void uiSliderSetHighlighted(uislider_t *slider, const bool_t highlighted) { - assertNotNull(slider, "Slider cannot be NULL"); - slider->highlighted = highlighted; -} - -errorret_t uiSliderDraw( - const uislider_t *slider, - const float_t x, - const float_t y -) { - assertNotNull(slider, "Slider cannot be NULL"); - - color_t color = slider->highlighted ? COLOR_RED : COLOR_WHITE; - - errorChain(textDraw(x, y, slider->label, color, &FONT_DEFAULT)); - - int32_t labelW, labelH; - textMeasure(slider->label, &FONT_DEFAULT, &labelW, &labelH); - - float_t trackX = x + (float_t)labelW + UI_SLIDER_GAP; - float_t trackY = y + ((float_t)labelH - UI_SLIDER_TRACK_HEIGHT) * 0.5f; - - spritebatchsprite_t trackSprite = { - .min = { trackX, trackY, 0.0f }, - .max = { - trackX + UI_SLIDER_TRACK_WIDTH, trackY + UI_SLIDER_TRACK_HEIGHT, 0.0f - }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - shadermaterial_t trackMaterial = { - .unlit = { - .color = COLOR_DARK_GRAY, - .texture = &TEXTURE_WHITE - } - }; - errorChain(spriteBatchBuffer(&trackSprite, 1, &SHADER_UNLIT, trackMaterial)); - errorChain(spriteBatchFlush()); - - float_t fillWidth = UI_SLIDER_TRACK_WIDTH * uiSliderGetRatio(slider); - if(fillWidth > 0.0f) { - spritebatchsprite_t fillSprite = { - .min = { trackX, trackY, 0.0f }, - .max = { trackX + fillWidth, trackY + UI_SLIDER_TRACK_HEIGHT, 0.0f }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - shadermaterial_t fillMaterial = { - .unlit = { - .color = color, - .texture = &TEXTURE_WHITE - } - }; - errorChain(spriteBatchBuffer(&fillSprite, 1, &SHADER_UNLIT, fillMaterial)); - errorChain(spriteBatchFlush()); - } - - int32_t stepCount = uiSliderGetStepCount(slider); - if(stepCount > 0 && stepCount < UI_SLIDER_STEP_MARKERS_MAX) { - for(int32_t i = 0; i <= stepCount; i++) { - float_t markerX = trackX + - UI_SLIDER_TRACK_WIDTH * (float_t)i / (float_t)stepCount; - spritebatchsprite_t markerSprite = { - .min = { - markerX - UI_SLIDER_STEP_MARKER_WIDTH * 0.5f, - trackY - UI_SLIDER_STEP_MARKER_OVERHANG, - 0.0f - }, - .max = { - markerX + UI_SLIDER_STEP_MARKER_WIDTH * 0.5f, - trackY + UI_SLIDER_TRACK_HEIGHT + UI_SLIDER_STEP_MARKER_OVERHANG, - 0.0f - }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - shadermaterial_t markerMaterial = { - .unlit = { - .color = COLOR_WHITE, - .texture = &TEXTURE_WHITE - } - }; - errorChain( - spriteBatchBuffer(&markerSprite, 1, &SHADER_UNLIT, markerMaterial) - ); - errorChain(spriteBatchFlush()); - } - } - - char_t valueText[UI_SLIDER_VALUE_TEXT_MAX]; - if(slider->type == UI_SLIDER_TYPE_INT) { - stringFormat( - valueText, UI_SLIDER_VALUE_TEXT_MAX - 1, "%d", slider->value.i - ); - } else { - stringFormat( - valueText, UI_SLIDER_VALUE_TEXT_MAX - 1, "%.2f", slider->value.f - ); - } - - errorChain(textDraw( - trackX + UI_SLIDER_TRACK_WIDTH + UI_SLIDER_GAP, y, - valueText, color, &FONT_DEFAULT - )); - - errorOk(); -} diff --git a/src/dusk/ui/widget/uislider.h b/src/dusk/ui/widget/uislider.h deleted file mode 100644 index d53dee00..00000000 --- a/src/dusk/ui/widget/uislider.h +++ /dev/null @@ -1,185 +0,0 @@ -/** - * 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_SLIDER_TRACK_WIDTH 80.0f -#define UI_SLIDER_TRACK_HEIGHT 4.0f -#define UI_SLIDER_STEP_MARKER_WIDTH 2.0f -#define UI_SLIDER_STEP_MARKER_OVERHANG 1.0f -#define UI_SLIDER_GAP 4.0f -#define UI_SLIDER_VALUE_TEXT_MAX 16 - -/** - * Number of discrete steps below which an int slider renders individual - * step markers on its track instead of a smooth fill. - */ -#define UI_SLIDER_STEP_MARKERS_MAX 10 - -typedef enum { - UI_SLIDER_TYPE_FLOAT, - UI_SLIDER_TYPE_INT, - - UI_SLIDER_TYPE_COUNT -} uislidertype_t; - -typedef union { - float_t f; - int32_t i; -} uislidervalue_t; - -typedef struct { - const char_t *label; - uislidertype_t type; - uislidervalue_t value; - uislidervalue_t min; - uislidervalue_t max; - uislidervalue_t step; - bool_t highlighted; -} uislider_t; - -/** - * Initializes a floating point slider. - * - * @param slider The slider to initialize. - * @param label Display label. - * @param value Initial value, clamped to [min, max]. - * @param min Minimum value. - * @param max Maximum value. - * @param step The amount a single step moves the value by. - */ -void uiSliderInitFloat( - uislider_t *slider, - const char_t *label, - const float_t value, - const float_t min, - const float_t max, - const float_t step -); - -/** - * Initializes an integer slider. - * - * @param slider The slider to initialize. - * @param label Display label. - * @param value Initial value, clamped to [min, max]. - * @param min Minimum value. - * @param max Maximum value. - * @param step The amount a single step moves the value by. - */ -void uiSliderInitInt( - uislider_t *slider, - const char_t *label, - const int32_t value, - const int32_t min, - const int32_t max, - const int32_t step -); - -/** - * Returns the slider's current value as a float. Works for both slider - * types; int values are widened to float. - * - * @param slider The slider to query. - * @returns The current value. - */ -float_t uiSliderGetFloat(const uislider_t *slider); - -/** - * Returns the slider's current value as an int. Only valid for - * UI_SLIDER_TYPE_INT sliders. - * - * @param slider The slider to query. - * @returns The current value. - */ -int32_t uiSliderGetInt(const uislider_t *slider); - -/** - * Sets the slider's value, clamped to [min, max]. Only valid for - * UI_SLIDER_TYPE_FLOAT sliders. - * - * @param slider The slider to update. - * @param value The new value. - */ -void uiSliderSetFloat(uislider_t *slider, const float_t value); - -/** - * Sets the slider's value, clamped to [min, max]. Only valid for - * UI_SLIDER_TYPE_INT sliders. - * - * @param slider The slider to update. - * @param value The new value. - */ -void uiSliderSetInt(uislider_t *slider, const int32_t value); - -/** - * Moves the slider's value up by one step, wrapping around to its min - * if the step would exceed its max. - * - * @param slider The slider to update. - */ -void uiSliderStepUp(uislider_t *slider); - -/** - * Moves the slider's value down by one step, wrapping around to its - * max if the step would go below its min. - * - * @param slider The slider to update. - */ -void uiSliderStepDown(uislider_t *slider); - -/** - * Returns the slider's current value normalized to a 0..1 range based - * on its min/max. - * - * @param slider The slider to query. - * @returns The normalized value. - */ -float_t uiSliderGetRatio(const uislider_t *slider); - -/** - * Returns the number of discrete steps between min and max. Always 0 - * for float sliders. - * - * @param slider The slider to query. - * @returns The step count. - */ -int32_t uiSliderGetStepCount(const uislider_t *slider); - -/** - * Returns whether the slider is highlighted. - * - * @param slider The slider to query. - * @returns True if highlighted. - */ -bool_t uiSliderIsHighlighted(const uislider_t *slider); - -/** - * Sets the highlighted state of the slider. - * - * @param slider The slider to update. - * @param highlighted The new highlighted state. - */ -void uiSliderSetHighlighted(uislider_t *slider, const bool_t highlighted); - -/** - * Draws the slider at the given screen position: label, then track, - * then the current value as text. Int sliders with fewer than - * UI_SLIDER_STEP_MARKERS_MAX discrete steps render a marker per step - * instead of a plain track. - * - * @param slider The slider to draw. - * @param x Screen x position. - * @param y Screen y position. - * @return Any error that occurs. - */ -errorret_t uiSliderDraw( - const uislider_t *slider, - const float_t x, - const float_t y -); diff --git a/src/dusk/ui/widget/uitab.c b/src/dusk/ui/widget/uitab.c deleted file mode 100644 index 12073ef2..00000000 --- a/src/dusk/ui/widget/uitab.c +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uitab.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "display/text/text.h" -#include "display/color.h" -#include "display/spritebatch/spritebatch.h" -#include "display/texture/texture.h" -#include "display/shader/shaderunlit.h" - -void uiTabInit(uitab_t *tab, const char_t *label) { - assertNotNull(tab, "Tab cannot be NULL"); - assertNotNull(label, "Label cannot be NULL"); - memoryZero(tab, sizeof(uitab_t)); - tab->label = label; -} - -bool_t uiTabIsActive(const uitab_t *tab) { - assertNotNull(tab, "Tab cannot be NULL"); - return tab->active; -} - -void uiTabSetActive(uitab_t *tab, const bool_t active) { - assertNotNull(tab, "Tab cannot be NULL"); - tab->active = active; -} - -errorret_t uiTabDraw( - const uitab_t *tab, - const float_t x, - const float_t y -) { - assertNotNull(tab, "Tab cannot be NULL"); - - int32_t labelW, labelH; - textMeasure(tab->label, &FONT_DEFAULT, &labelW, &labelH); - - spritebatchsprite_t sprite = { - .min = { x, y, 0.0f }, - .max = { x + (float_t)labelW, y + (float_t)labelH, 0.0f }, - .uvMin = { 0.0f, 0.0f }, - .uvMax = { 1.0f, 1.0f } - }; - shadermaterial_t material = { - .unlit = { - .color = tab->active ? COLOR_GREEN : COLOR_RED, - .texture = &TEXTURE_WHITE - } - }; - errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material)); - errorChain(spriteBatchFlush()); - - errorChain(textDraw(x, y, tab->label, COLOR_WHITE, &FONT_DEFAULT)); - - errorOk(); -} diff --git a/src/dusk/ui/widget/uitab.h b/src/dusk/ui/widget/uitab.h deleted file mode 100644 index 678f54b6..00000000 --- a/src/dusk/ui/widget/uitab.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "error/error.h" - -typedef struct { - const char_t *label; - bool_t active; -} uitab_t; - -/** - * Initializes a tab. - * - * @param tab The tab to initialize. - * @param label Display label. - */ -void uiTabInit(uitab_t *tab, const char_t *label); - -/** - * Returns whether the tab is active. - * - * @param tab The tab to query. - * @returns True if active. - */ -bool_t uiTabIsActive(const uitab_t *tab); - -/** - * Sets the active state of the tab. - * - * @param tab The tab to update. - * @param active The new active state. - */ -void uiTabSetActive(uitab_t *tab, const bool_t active); - -/** - * Draws the tab at the given screen position. Active tabs draw with a - * green background; inactive tabs draw with a red background. - * - * @param tab The tab to draw. - * @param x Screen x position. - * @param y Screen y position. - * @return Any error that occurs. - */ -errorret_t uiTabDraw( - const uitab_t *tab, - const float_t x, - const float_t y -); diff --git a/test/duskscript/CMakeLists.txt b/test/duskscript/CMakeLists.txt index 71aa5620..16b4edea 100644 --- a/test/duskscript/CMakeLists.txt +++ b/test/duskscript/CMakeLists.txt @@ -13,6 +13,7 @@ dusktest(test_duskscript.c) # game's own dusk.dsk - this exercises the real compiler -> pack -> asset -> # runtime -> consolePrint pipeline rather than a shortcut around it. set(DUSKSCRIPT_TEST_ASSETS_DIR ${CMAKE_CURRENT_LIST_DIR}/assets) +file(GLOB DUSKSCRIPT_TEST_FIXTURES CONFIGURE_DEPENDS ${DUSKSCRIPT_TEST_ASSETS_DIR}/*.ds) set(DUSKSCRIPT_TEST_DSK ${CMAKE_CURRENT_BINARY_DIR}/test.dsk) add_custom_command( @@ -24,8 +25,7 @@ add_custom_command( WORKING_DIRECTORY ${DUSK_ROOT_DIR} DEPENDS duskscriptcc - ${DUSKSCRIPT_TEST_ASSETS_DIR}/fixture.ds - ${DUSKSCRIPT_TEST_ASSETS_DIR}/fixture_numbers.ds + ${DUSKSCRIPT_TEST_FIXTURES} COMMENT "Packing duskscript test fixtures" ) add_custom_target(duskscript_test_fixture DEPENDS ${DUSKSCRIPT_TEST_DSK}) diff --git a/test/duskscript/assets/fail_abs_string.ds b/test/duskscript/assets/fail_abs_string.ds new file mode 100644 index 00000000..f6195855 --- /dev/null +++ b/test/duskscript/assets/fail_abs_string.ds @@ -0,0 +1 @@ +abs("a"); diff --git a/test/duskscript/assets/fail_add_array.ds b/test/duskscript/assets/fail_add_array.ds new file mode 100644 index 00000000..d927c6e8 --- /dev/null +++ b/test/duskscript/assets/fail_add_array.ds @@ -0,0 +1 @@ +var r = [1] + 1; diff --git a/test/duskscript/assets/fail_add_bool.ds b/test/duskscript/assets/fail_add_bool.ds new file mode 100644 index 00000000..4d7f6482 --- /dev/null +++ b/test/duskscript/assets/fail_add_bool.ds @@ -0,0 +1 @@ +var r = true + 1; diff --git a/test/duskscript/assets/fail_add_null.ds b/test/duskscript/assets/fail_add_null.ds new file mode 100644 index 00000000..bf25865b --- /dev/null +++ b/test/duskscript/assets/fail_add_null.ds @@ -0,0 +1 @@ +var r = 1 + null; diff --git a/test/duskscript/assets/fail_array_too_long.ds b/test/duskscript/assets/fail_array_too_long.ds new file mode 100644 index 00000000..ad22e244 --- /dev/null +++ b/test/duskscript/assets/fail_array_too_long.ds @@ -0,0 +1,4 @@ +var a = []; +for(var i = 0; i < 513; i++) { + push(a, i); +} diff --git a/test/duskscript/assets/fail_array_too_long_merge.ds b/test/duskscript/assets/fail_array_too_long_merge.ds new file mode 100644 index 00000000..98dcc4df --- /dev/null +++ b/test/duskscript/assets/fail_array_too_long_merge.ds @@ -0,0 +1,3 @@ +var a = []; +fill(a, 0, 400); +merge(a, a); diff --git a/test/duskscript/assets/fail_assert_empty_string.ds b/test/duskscript/assets/fail_assert_empty_string.ds new file mode 100644 index 00000000..c65fb83e --- /dev/null +++ b/test/duskscript/assets/fail_assert_empty_string.ds @@ -0,0 +1 @@ +assert(""); diff --git a/test/duskscript/assets/fail_assert_false.ds b/test/duskscript/assets/fail_assert_false.ds new file mode 100644 index 00000000..fc7dea4f --- /dev/null +++ b/test/duskscript/assets/fail_assert_false.ds @@ -0,0 +1 @@ +assert(false); diff --git a/test/duskscript/assets/fail_assert_no_arguments.ds b/test/duskscript/assets/fail_assert_no_arguments.ds new file mode 100644 index 00000000..c43fd961 --- /dev/null +++ b/test/duskscript/assets/fail_assert_no_arguments.ds @@ -0,0 +1 @@ +assert(); diff --git a/test/duskscript/assets/fail_assert_null.ds b/test/duskscript/assets/fail_assert_null.ds new file mode 100644 index 00000000..2c00475c --- /dev/null +++ b/test/duskscript/assets/fail_assert_null.ds @@ -0,0 +1 @@ +assert(null); diff --git a/test/duskscript/assets/fail_assert_zero.ds b/test/duskscript/assets/fail_assert_zero.ds new file mode 100644 index 00000000..8e1a0677 --- /dev/null +++ b/test/duskscript/assets/fail_assert_zero.ds @@ -0,0 +1 @@ +assert(0); diff --git a/test/duskscript/assets/fail_async_error.ds b/test/duskscript/assets/fail_async_error.ds new file mode 100644 index 00000000..d7b44999 --- /dev/null +++ b/test/duskscript/assets/fail_async_error.ds @@ -0,0 +1,4 @@ +async function f() { + assert(false); +} +await f(); diff --git a/test/duskscript/assets/fail_async_error_nested.ds b/test/duskscript/assets/fail_async_error_nested.ds new file mode 100644 index 00000000..a13ba570 --- /dev/null +++ b/test/duskscript/assets/fail_async_error_nested.ds @@ -0,0 +1,7 @@ +async function inner() { + return 1 / 0; +} +async function outer() { + return await inner(); +} +await outer(); diff --git a/test/duskscript/assets/fail_bitwise_string.ds b/test/duskscript/assets/fail_bitwise_string.ds new file mode 100644 index 00000000..5d5b7b68 --- /dev/null +++ b/test/duskscript/assets/fail_bitwise_string.ds @@ -0,0 +1,2 @@ +var s = "a"; +var r = s & 1; diff --git a/test/duskscript/assets/fail_call_missing_member.ds b/test/duskscript/assets/fail_call_missing_member.ds new file mode 100644 index 00000000..7b0a75d6 --- /dev/null +++ b/test/duskscript/assets/fail_call_missing_member.ds @@ -0,0 +1,2 @@ +var s = {}; +s.f(); diff --git a/test/duskscript/assets/fail_call_null.ds b/test/duskscript/assets/fail_call_null.ds new file mode 100644 index 00000000..9cba74d6 --- /dev/null +++ b/test/duskscript/assets/fail_call_null.ds @@ -0,0 +1,2 @@ +var n = null; +n(); diff --git a/test/duskscript/assets/fail_call_number.ds b/test/duskscript/assets/fail_call_number.ds new file mode 100644 index 00000000..732b29b6 --- /dev/null +++ b/test/duskscript/assets/fail_call_number.ds @@ -0,0 +1,2 @@ +var n = 5; +n(); diff --git a/test/duskscript/assets/fail_callback_raises.ds b/test/duskscript/assets/fail_callback_raises.ds new file mode 100644 index 00000000..c62ad77f --- /dev/null +++ b/test/duskscript/assets/fail_callback_raises.ds @@ -0,0 +1,3 @@ +map([1], function(v) { + assert(false); +}); diff --git a/test/duskscript/assets/fail_chr_fraction.ds b/test/duskscript/assets/fail_chr_fraction.ds new file mode 100644 index 00000000..03b96a13 --- /dev/null +++ b/test/duskscript/assets/fail_chr_fraction.ds @@ -0,0 +1 @@ +chr(65.5); diff --git a/test/duskscript/assets/fail_chr_too_big.ds b/test/duskscript/assets/fail_chr_too_big.ds new file mode 100644 index 00000000..a56feb97 --- /dev/null +++ b/test/duskscript/assets/fail_chr_too_big.ds @@ -0,0 +1 @@ +chr(256); diff --git a/test/duskscript/assets/fail_chr_zero.ds b/test/duskscript/assets/fail_chr_zero.ds new file mode 100644 index 00000000..022b3b3e --- /dev/null +++ b/test/duskscript/assets/fail_chr_zero.ds @@ -0,0 +1 @@ +chr(0); diff --git a/test/duskscript/assets/fail_compare_mixed.ds b/test/duskscript/assets/fail_compare_mixed.ds new file mode 100644 index 00000000..6f590119 --- /dev/null +++ b/test/duskscript/assets/fail_compare_mixed.ds @@ -0,0 +1 @@ +var r = 1 < "a"; diff --git a/test/duskscript/assets/fail_compare_null.ds b/test/duskscript/assets/fail_compare_null.ds new file mode 100644 index 00000000..0eab84a3 --- /dev/null +++ b/test/duskscript/assets/fail_compare_null.ds @@ -0,0 +1 @@ +var r = null > 1; diff --git a/test/duskscript/assets/fail_compare_strings.ds b/test/duskscript/assets/fail_compare_strings.ds new file mode 100644 index 00000000..c2bd3f1e --- /dev/null +++ b/test/duskscript/assets/fail_compare_strings.ds @@ -0,0 +1 @@ +var r = "a" < "b"; diff --git a/test/duskscript/assets/fail_const_array_alias_assign.ds b/test/duskscript/assets/fail_const_array_alias_assign.ds new file mode 100644 index 00000000..1f011a06 --- /dev/null +++ b/test/duskscript/assets/fail_const_array_alias_assign.ds @@ -0,0 +1,3 @@ +const LIST = [1]; +var alias = LIST; +alias[0] = 2; diff --git a/test/duskscript/assets/fail_const_array_pop.ds b/test/duskscript/assets/fail_const_array_pop.ds new file mode 100644 index 00000000..bad3fd54 --- /dev/null +++ b/test/duskscript/assets/fail_const_array_pop.ds @@ -0,0 +1,2 @@ +const LIST = [1]; +pop(LIST); diff --git a/test/duskscript/assets/fail_const_array_push.ds b/test/duskscript/assets/fail_const_array_push.ds new file mode 100644 index 00000000..8ec538fc --- /dev/null +++ b/test/duskscript/assets/fail_const_array_push.ds @@ -0,0 +1,2 @@ +const LIST = [1]; +push(LIST, 2); diff --git a/test/duskscript/assets/fail_const_array_sort.ds b/test/duskscript/assets/fail_const_array_sort.ds new file mode 100644 index 00000000..686ececd --- /dev/null +++ b/test/duskscript/assets/fail_const_array_sort.ds @@ -0,0 +1,2 @@ +const LIST = [2, 1]; +sort(LIST); diff --git a/test/duskscript/assets/fail_const_struct_alias_assign.ds b/test/duskscript/assets/fail_const_struct_alias_assign.ds new file mode 100644 index 00000000..1a3a9795 --- /dev/null +++ b/test/duskscript/assets/fail_const_struct_alias_assign.ds @@ -0,0 +1,3 @@ +const CFG = {a: 1}; +var alias = CFG; +alias.a = 2; diff --git a/test/duskscript/assets/fail_const_struct_alias_new_key.ds b/test/duskscript/assets/fail_const_struct_alias_new_key.ds new file mode 100644 index 00000000..1ecacf7b --- /dev/null +++ b/test/duskscript/assets/fail_const_struct_alias_new_key.ds @@ -0,0 +1,3 @@ +const CFG = {a: 1}; +var alias = CFG; +alias.b = 2; diff --git a/test/duskscript/assets/fail_contains_number.ds b/test/duskscript/assets/fail_contains_number.ds new file mode 100644 index 00000000..6c409195 --- /dev/null +++ b/test/duskscript/assets/fail_contains_number.ds @@ -0,0 +1 @@ +contains("abc", 1); diff --git a/test/duskscript/assets/fail_deepclone_cycle.ds b/test/duskscript/assets/fail_deepclone_cycle.ds new file mode 100644 index 00000000..449dc496 --- /dev/null +++ b/test/duskscript/assets/fail_deepclone_cycle.ds @@ -0,0 +1,3 @@ +var a = []; +push(a, a); +deepclone(a); diff --git a/test/duskscript/assets/fail_deepclone_struct_cycle.ds b/test/duskscript/assets/fail_deepclone_struct_cycle.ds new file mode 100644 index 00000000..d960d816 --- /dev/null +++ b/test/duskscript/assets/fail_deepclone_struct_cycle.ds @@ -0,0 +1,3 @@ +var s = {}; +s.self = s; +deepclone(s); diff --git a/test/duskscript/assets/fail_div_zero.ds b/test/duskscript/assets/fail_div_zero.ds new file mode 100644 index 00000000..e3d529c2 --- /dev/null +++ b/test/duskscript/assets/fail_div_zero.ds @@ -0,0 +1,2 @@ +var z = 0; +var r = 1 / z; diff --git a/test/duskscript/assets/fail_error_after_output.ds b/test/duskscript/assets/fail_error_after_output.ds new file mode 100644 index 00000000..2b3fe9ed --- /dev/null +++ b/test/duskscript/assets/fail_error_after_output.ds @@ -0,0 +1,3 @@ +print("before"); +assert(false); +print("after"); diff --git a/test/duskscript/assets/fail_error_in_loop.ds b/test/duskscript/assets/fail_error_in_loop.ds new file mode 100644 index 00000000..91245d91 --- /dev/null +++ b/test/duskscript/assets/fail_error_in_loop.ds @@ -0,0 +1,7 @@ +var total = 0; +for(var i = 0; i < 10; i++) { + total += i; + if(i == 5) { + assert(false); + } +} diff --git a/test/duskscript/assets/fail_error_inside_function.ds b/test/duskscript/assets/fail_error_inside_function.ds new file mode 100644 index 00000000..02504f34 --- /dev/null +++ b/test/duskscript/assets/fail_error_inside_function.ds @@ -0,0 +1,5 @@ +function f() { + var a = []; + return a[0]; +} +f(); diff --git a/test/duskscript/assets/fail_error_inside_nested_calls.ds b/test/duskscript/assets/fail_error_inside_nested_calls.ds new file mode 100644 index 00000000..d148b05d --- /dev/null +++ b/test/duskscript/assets/fail_error_inside_nested_calls.ds @@ -0,0 +1,7 @@ +function inner() { + return 1 / 0; +} +function outer() { + return inner(); +} +outer(); diff --git a/test/duskscript/assets/fail_fill_negative.ds b/test/duskscript/assets/fail_fill_negative.ds new file mode 100644 index 00000000..e6b4e9ce --- /dev/null +++ b/test/duskscript/assets/fail_fill_negative.ds @@ -0,0 +1 @@ +fill([], 0, -1); diff --git a/test/duskscript/assets/fail_fill_too_many.ds b/test/duskscript/assets/fail_fill_too_many.ds new file mode 100644 index 00000000..2b08e926 --- /dev/null +++ b/test/duskscript/assets/fail_fill_too_many.ds @@ -0,0 +1 @@ +fill([], 0, 513); diff --git a/test/duskscript/assets/fail_filter_non_function.ds b/test/duskscript/assets/fail_filter_non_function.ds new file mode 100644 index 00000000..392fc73e --- /dev/null +++ b/test/duskscript/assets/fail_filter_non_function.ds @@ -0,0 +1 @@ +filter([1], 5); diff --git a/test/duskscript/assets/fail_flatten_non_array_items.ds b/test/duskscript/assets/fail_flatten_non_array_items.ds new file mode 100644 index 00000000..ab31d9c0 --- /dev/null +++ b/test/duskscript/assets/fail_flatten_non_array_items.ds @@ -0,0 +1 @@ +flatten([1]); diff --git a/test/duskscript/assets/fail_foreach_null.ds b/test/duskscript/assets/fail_foreach_null.ds new file mode 100644 index 00000000..1f0da99c --- /dev/null +++ b/test/duskscript/assets/fail_foreach_null.ds @@ -0,0 +1,2 @@ +foreach(var v : null) { +} diff --git a/test/duskscript/assets/fail_foreach_number.ds b/test/duskscript/assets/fail_foreach_number.ds new file mode 100644 index 00000000..7c5a4cab --- /dev/null +++ b/test/duskscript/assets/fail_foreach_number.ds @@ -0,0 +1,2 @@ +foreach(var v : 5) { +} diff --git a/test/duskscript/assets/fail_foreach_struct.ds b/test/duskscript/assets/fail_foreach_struct.ds new file mode 100644 index 00000000..b9aacf91 --- /dev/null +++ b/test/duskscript/assets/fail_foreach_struct.ds @@ -0,0 +1,2 @@ +foreach(var v : {a: 1}) { +} diff --git a/test/duskscript/assets/fail_include_missing.ds b/test/duskscript/assets/fail_include_missing.ds new file mode 100644 index 00000000..97067518 --- /dev/null +++ b/test/duskscript/assets/fail_include_missing.ds @@ -0,0 +1 @@ +include("definitely_not_a_script.dsb"); diff --git a/test/duskscript/assets/fail_index_assign_negative.ds b/test/duskscript/assets/fail_index_assign_negative.ds new file mode 100644 index 00000000..be117293 --- /dev/null +++ b/test/duskscript/assets/fail_index_assign_negative.ds @@ -0,0 +1,2 @@ +var a = [1]; +a[-1] = 2; diff --git a/test/duskscript/assets/fail_index_assign_oob.ds b/test/duskscript/assets/fail_index_assign_oob.ds new file mode 100644 index 00000000..3f6b79a1 --- /dev/null +++ b/test/duskscript/assets/fail_index_assign_oob.ds @@ -0,0 +1,2 @@ +var a = [1]; +a[5] = 2; diff --git a/test/duskscript/assets/fail_index_fraction.ds b/test/duskscript/assets/fail_index_fraction.ds new file mode 100644 index 00000000..84381d04 --- /dev/null +++ b/test/duskscript/assets/fail_index_fraction.ds @@ -0,0 +1,2 @@ +var a = [1, 2]; +var r = a[0.5]; diff --git a/test/duskscript/assets/fail_index_negative.ds b/test/duskscript/assets/fail_index_negative.ds new file mode 100644 index 00000000..3b53990c --- /dev/null +++ b/test/duskscript/assets/fail_index_negative.ds @@ -0,0 +1,2 @@ +var a = [1, 2]; +var r = a[-1]; diff --git a/test/duskscript/assets/fail_index_number.ds b/test/duskscript/assets/fail_index_number.ds new file mode 100644 index 00000000..c8997ea0 --- /dev/null +++ b/test/duskscript/assets/fail_index_number.ds @@ -0,0 +1,2 @@ +var n = 5; +var r = n[0]; diff --git a/test/duskscript/assets/fail_index_read_oob.ds b/test/duskscript/assets/fail_index_read_oob.ds new file mode 100644 index 00000000..1e5f6523 --- /dev/null +++ b/test/duskscript/assets/fail_index_read_oob.ds @@ -0,0 +1,2 @@ +var a = [1, 2]; +var r = a[2]; diff --git a/test/duskscript/assets/fail_index_string_key.ds b/test/duskscript/assets/fail_index_string_key.ds new file mode 100644 index 00000000..e71dd17c --- /dev/null +++ b/test/duskscript/assets/fail_index_string_key.ds @@ -0,0 +1,2 @@ +var a = [1, 2]; +var r = a["x"]; diff --git a/test/duskscript/assets/fail_indexof_non_array.ds b/test/duskscript/assets/fail_indexof_non_array.ds new file mode 100644 index 00000000..e960d9b6 --- /dev/null +++ b/test/duskscript/assets/fail_indexof_non_array.ds @@ -0,0 +1 @@ +indexof(5, 1); diff --git a/test/duskscript/assets/fail_indexof_string_long_needle.ds b/test/duskscript/assets/fail_indexof_string_long_needle.ds new file mode 100644 index 00000000..bd3a7274 --- /dev/null +++ b/test/duskscript/assets/fail_indexof_string_long_needle.ds @@ -0,0 +1 @@ +indexof("abc", "ab"); diff --git a/test/duskscript/assets/fail_insert_negative.ds b/test/duskscript/assets/fail_insert_negative.ds new file mode 100644 index 00000000..96aea483 --- /dev/null +++ b/test/duskscript/assets/fail_insert_negative.ds @@ -0,0 +1 @@ +insert([1], -1, 0); diff --git a/test/duskscript/assets/fail_insert_out_of_range.ds b/test/duskscript/assets/fail_insert_out_of_range.ds new file mode 100644 index 00000000..d19e2085 --- /dev/null +++ b/test/duskscript/assets/fail_insert_out_of_range.ds @@ -0,0 +1 @@ +insert([1], 5, 0); diff --git a/test/duskscript/assets/fail_insertarr_out_of_range.ds b/test/duskscript/assets/fail_insertarr_out_of_range.ds new file mode 100644 index 00000000..592860cd --- /dev/null +++ b/test/duskscript/assets/fail_insertarr_out_of_range.ds @@ -0,0 +1 @@ +insertarr([1], 5, [0]); diff --git a/test/duskscript/assets/fail_invert_string.ds b/test/duskscript/assets/fail_invert_string.ds new file mode 100644 index 00000000..5f00ae6b --- /dev/null +++ b/test/duskscript/assets/fail_invert_string.ds @@ -0,0 +1,2 @@ +var s = "a"; +var r = ~s; diff --git a/test/duskscript/assets/fail_join_non_array.ds b/test/duskscript/assets/fail_join_non_array.ds new file mode 100644 index 00000000..8eb9c834 --- /dev/null +++ b/test/duskscript/assets/fail_join_non_array.ds @@ -0,0 +1 @@ +join(5, ","); diff --git a/test/duskscript/assets/fail_join_non_string_separator.ds b/test/duskscript/assets/fail_join_non_string_separator.ds new file mode 100644 index 00000000..98162857 --- /dev/null +++ b/test/duskscript/assets/fail_join_non_string_separator.ds @@ -0,0 +1 @@ +join([1], 5); diff --git a/test/duskscript/assets/fail_len_null.ds b/test/duskscript/assets/fail_len_null.ds new file mode 100644 index 00000000..236e597e --- /dev/null +++ b/test/duskscript/assets/fail_len_null.ds @@ -0,0 +1 @@ +len(null); diff --git a/test/duskscript/assets/fail_len_number.ds b/test/duskscript/assets/fail_len_number.ds new file mode 100644 index 00000000..5f15e0ee --- /dev/null +++ b/test/duskscript/assets/fail_len_number.ds @@ -0,0 +1 @@ +len(5); diff --git a/test/duskscript/assets/fail_map_non_array.ds b/test/duskscript/assets/fail_map_non_array.ds new file mode 100644 index 00000000..55fbad23 --- /dev/null +++ b/test/duskscript/assets/fail_map_non_array.ds @@ -0,0 +1 @@ +map(5, function(v) { return v; }); diff --git a/test/duskscript/assets/fail_map_non_function.ds b/test/duskscript/assets/fail_map_non_function.ds new file mode 100644 index 00000000..8c10f526 --- /dev/null +++ b/test/duskscript/assets/fail_map_non_function.ds @@ -0,0 +1 @@ +map([1], 5); diff --git a/test/duskscript/assets/fail_member_of_null.ds b/test/duskscript/assets/fail_member_of_null.ds new file mode 100644 index 00000000..b1a20d7c --- /dev/null +++ b/test/duskscript/assets/fail_member_of_null.ds @@ -0,0 +1,2 @@ +var n = null; +var r = n.x; diff --git a/test/duskscript/assets/fail_member_of_number.ds b/test/duskscript/assets/fail_member_of_number.ds new file mode 100644 index 00000000..9f5471a6 --- /dev/null +++ b/test/duskscript/assets/fail_member_of_number.ds @@ -0,0 +1,2 @@ +var n = 5; +var r = n.x; diff --git a/test/duskscript/assets/fail_min_string.ds b/test/duskscript/assets/fail_min_string.ds new file mode 100644 index 00000000..cc248515 --- /dev/null +++ b/test/duskscript/assets/fail_min_string.ds @@ -0,0 +1 @@ +min("a", 1); diff --git a/test/duskscript/assets/fail_missing_parameter.ds b/test/duskscript/assets/fail_missing_parameter.ds new file mode 100644 index 00000000..b240221f --- /dev/null +++ b/test/duskscript/assets/fail_missing_parameter.ds @@ -0,0 +1,4 @@ +function f(a) { + return a; +} +f(); diff --git a/test/duskscript/assets/fail_missing_second_parameter.ds b/test/duskscript/assets/fail_missing_second_parameter.ds new file mode 100644 index 00000000..5270b003 --- /dev/null +++ b/test/duskscript/assets/fail_missing_second_parameter.ds @@ -0,0 +1,4 @@ +function f(a, b) { + return a + b; +} +f(1); diff --git a/test/duskscript/assets/fail_mod_zero.ds b/test/duskscript/assets/fail_mod_zero.ds new file mode 100644 index 00000000..eb34a03e --- /dev/null +++ b/test/duskscript/assets/fail_mod_zero.ds @@ -0,0 +1,2 @@ +var z = 0; +var r = 5 % z; diff --git a/test/duskscript/assets/fail_mul_string.ds b/test/duskscript/assets/fail_mul_string.ds new file mode 100644 index 00000000..d7c1618e --- /dev/null +++ b/test/duskscript/assets/fail_mul_string.ds @@ -0,0 +1 @@ +var r = "a" * 2; diff --git a/test/duskscript/assets/fail_mutual_recursion_too_deep.ds b/test/duskscript/assets/fail_mutual_recursion_too_deep.ds new file mode 100644 index 00000000..ca84632b --- /dev/null +++ b/test/duskscript/assets/fail_mutual_recursion_too_deep.ds @@ -0,0 +1,7 @@ +function a(n) { + return b(n); +} +function b(n) { + return a(n); +} +a(0); diff --git a/test/duskscript/assets/fail_negate_string.ds b/test/duskscript/assets/fail_negate_string.ds new file mode 100644 index 00000000..59c6065c --- /dev/null +++ b/test/duskscript/assets/fail_negate_string.ds @@ -0,0 +1,2 @@ +var s = "a"; +var r = -s; diff --git a/test/duskscript/assets/fail_pop_string.ds b/test/duskscript/assets/fail_pop_string.ds new file mode 100644 index 00000000..a9596c77 --- /dev/null +++ b/test/duskscript/assets/fail_pop_string.ds @@ -0,0 +1 @@ +pop("abc"); diff --git a/test/duskscript/assets/fail_pow_fraction.ds b/test/duskscript/assets/fail_pow_fraction.ds new file mode 100644 index 00000000..c09aa6a6 --- /dev/null +++ b/test/duskscript/assets/fail_pow_fraction.ds @@ -0,0 +1 @@ +pow(2, 0.5); diff --git a/test/duskscript/assets/fail_push_string.ds b/test/duskscript/assets/fail_push_string.ds new file mode 100644 index 00000000..c20d79f4 --- /dev/null +++ b/test/duskscript/assets/fail_push_string.ds @@ -0,0 +1 @@ +push("abc", "d"); diff --git a/test/duskscript/assets/fail_rand_backwards.ds b/test/duskscript/assets/fail_rand_backwards.ds new file mode 100644 index 00000000..61354b8d --- /dev/null +++ b/test/duskscript/assets/fail_rand_backwards.ds @@ -0,0 +1 @@ +rand(5, 1); diff --git a/test/duskscript/assets/fail_rand_empty_range.ds b/test/duskscript/assets/fail_rand_empty_range.ds new file mode 100644 index 00000000..768ecb38 --- /dev/null +++ b/test/duskscript/assets/fail_rand_empty_range.ds @@ -0,0 +1 @@ +rand(5, 5); diff --git a/test/duskscript/assets/fail_rand_fraction.ds b/test/duskscript/assets/fail_rand_fraction.ds new file mode 100644 index 00000000..373bd99e --- /dev/null +++ b/test/duskscript/assets/fail_rand_fraction.ds @@ -0,0 +1 @@ +rand(1.5, 3); diff --git a/test/duskscript/assets/fail_randf_empty_range.ds b/test/duskscript/assets/fail_randf_empty_range.ds new file mode 100644 index 00000000..e77cd4ef --- /dev/null +++ b/test/duskscript/assets/fail_randf_empty_range.ds @@ -0,0 +1 @@ +randf(1, 1); diff --git a/test/duskscript/assets/fail_recursion_too_deep.ds b/test/duskscript/assets/fail_recursion_too_deep.ds new file mode 100644 index 00000000..2f2bd603 --- /dev/null +++ b/test/duskscript/assets/fail_recursion_too_deep.ds @@ -0,0 +1,4 @@ +function f(n) { + return f(n + 1); +} +f(0); diff --git a/test/duskscript/assets/fail_reduce_non_function.ds b/test/duskscript/assets/fail_reduce_non_function.ds new file mode 100644 index 00000000..a34b9fa1 --- /dev/null +++ b/test/duskscript/assets/fail_reduce_non_function.ds @@ -0,0 +1 @@ +reduce([1], 5, 0); diff --git a/test/duskscript/assets/fail_reverse_string.ds b/test/duskscript/assets/fail_reverse_string.ds new file mode 100644 index 00000000..e5493cdd --- /dev/null +++ b/test/duskscript/assets/fail_reverse_string.ds @@ -0,0 +1 @@ +reverse("abc"); diff --git a/test/duskscript/assets/fail_sort_bad_comparer_result.ds b/test/duskscript/assets/fail_sort_bad_comparer_result.ds new file mode 100644 index 00000000..8fd4e910 --- /dev/null +++ b/test/duskscript/assets/fail_sort_bad_comparer_result.ds @@ -0,0 +1 @@ +sort([2, 1], false, function(a, b) { return "x"; }); diff --git a/test/duskscript/assets/fail_sort_mixed.ds b/test/duskscript/assets/fail_sort_mixed.ds new file mode 100644 index 00000000..f694bfd6 --- /dev/null +++ b/test/duskscript/assets/fail_sort_mixed.ds @@ -0,0 +1 @@ +sort([1, "a"]); diff --git a/test/duskscript/assets/fail_sort_non_array.ds b/test/duskscript/assets/fail_sort_non_array.ds new file mode 100644 index 00000000..f711e0a8 --- /dev/null +++ b/test/duskscript/assets/fail_sort_non_array.ds @@ -0,0 +1 @@ +sort(5); diff --git a/test/duskscript/assets/fail_sort_structs.ds b/test/duskscript/assets/fail_sort_structs.ds new file mode 100644 index 00000000..20af8dd7 --- /dev/null +++ b/test/duskscript/assets/fail_sort_structs.ds @@ -0,0 +1 @@ +sort([{a: 1}, {a: 2}]); diff --git a/test/duskscript/assets/fail_splice_negative_count.ds b/test/duskscript/assets/fail_splice_negative_count.ds new file mode 100644 index 00000000..f474864b --- /dev/null +++ b/test/duskscript/assets/fail_splice_negative_count.ds @@ -0,0 +1 @@ +splice([1], 0, -1); diff --git a/test/duskscript/assets/fail_splice_out_of_range.ds b/test/duskscript/assets/fail_splice_out_of_range.ds new file mode 100644 index 00000000..11f333b4 --- /dev/null +++ b/test/duskscript/assets/fail_splice_out_of_range.ds @@ -0,0 +1 @@ +splice([1], 5); diff --git a/test/duskscript/assets/fail_split_non_string.ds b/test/duskscript/assets/fail_split_non_string.ds new file mode 100644 index 00000000..420e9c90 --- /dev/null +++ b/test/duskscript/assets/fail_split_non_string.ds @@ -0,0 +1 @@ +split(5, ","); diff --git a/test/duskscript/assets/fail_sqrt_negative.ds b/test/duskscript/assets/fail_sqrt_negative.ds new file mode 100644 index 00000000..31608b7c --- /dev/null +++ b/test/duskscript/assets/fail_sqrt_negative.ds @@ -0,0 +1 @@ +sqrt(-1); diff --git a/test/duskscript/assets/fail_string_assign.ds b/test/duskscript/assets/fail_string_assign.ds new file mode 100644 index 00000000..c4b2027e --- /dev/null +++ b/test/duskscript/assets/fail_string_assign.ds @@ -0,0 +1,2 @@ +var s = "abc"; +s[0] = "x"; diff --git a/test/duskscript/assets/fail_string_index_oob.ds b/test/duskscript/assets/fail_string_index_oob.ds new file mode 100644 index 00000000..264050d4 --- /dev/null +++ b/test/duskscript/assets/fail_string_index_oob.ds @@ -0,0 +1,2 @@ +var s = "abc"; +var r = s[3]; diff --git a/test/duskscript/assets/fail_string_too_long.ds b/test/duskscript/assets/fail_string_too_long.ds new file mode 100644 index 00000000..9492cf93 --- /dev/null +++ b/test/duskscript/assets/fail_string_too_long.ds @@ -0,0 +1,4 @@ +var s = "x"; +for(var i = 0; i < 10; i++) { + s = s + s; +} diff --git a/test/duskscript/assets/fail_struct_number_key.ds b/test/duskscript/assets/fail_struct_number_key.ds new file mode 100644 index 00000000..7d7c3889 --- /dev/null +++ b/test/duskscript/assets/fail_struct_number_key.ds @@ -0,0 +1,2 @@ +var s = {a: 1}; +var r = s[1]; diff --git a/test/duskscript/assets/fail_struct_number_key_assign.ds b/test/duskscript/assets/fail_struct_number_key_assign.ds new file mode 100644 index 00000000..b25c55cc --- /dev/null +++ b/test/duskscript/assets/fail_struct_number_key_assign.ds @@ -0,0 +1,2 @@ +var s = {a: 1}; +s[1] = 2; diff --git a/test/duskscript/assets/fail_struct_too_many_keys.ds b/test/duskscript/assets/fail_struct_too_many_keys.ds new file mode 100644 index 00000000..5d9e9ca2 --- /dev/null +++ b/test/duskscript/assets/fail_struct_too_many_keys.ds @@ -0,0 +1,4 @@ +var s = {}; +for(var i = 0; i < 17; i++) { + s["k" + i] = i; +} diff --git a/test/duskscript/assets/fail_sub_string.ds b/test/duskscript/assets/fail_sub_string.ds new file mode 100644 index 00000000..c2c25535 --- /dev/null +++ b/test/duskscript/assets/fail_sub_string.ds @@ -0,0 +1 @@ +var r = "a" - 1; diff --git a/test/duskscript/assets/fail_subarr_backwards.ds b/test/duskscript/assets/fail_subarr_backwards.ds new file mode 100644 index 00000000..22228358 --- /dev/null +++ b/test/duskscript/assets/fail_subarr_backwards.ds @@ -0,0 +1 @@ +subarr([1, 2], 2, 1); diff --git a/test/duskscript/assets/fail_subarr_negative.ds b/test/duskscript/assets/fail_subarr_negative.ds new file mode 100644 index 00000000..6d91b975 --- /dev/null +++ b/test/duskscript/assets/fail_subarr_negative.ds @@ -0,0 +1 @@ +subarr([1, 2], -1); diff --git a/test/duskscript/assets/fail_subarr_past_end.ds b/test/duskscript/assets/fail_subarr_past_end.ds new file mode 100644 index 00000000..234dd66a --- /dev/null +++ b/test/duskscript/assets/fail_subarr_past_end.ds @@ -0,0 +1 @@ +subarr([1, 2], 0, 5); diff --git a/test/duskscript/assets/fail_subarr_string_past_end.ds b/test/duskscript/assets/fail_subarr_string_past_end.ds new file mode 100644 index 00000000..051bbce2 --- /dev/null +++ b/test/duskscript/assets/fail_subarr_string_past_end.ds @@ -0,0 +1 @@ +subarr("ab", 0, 5); diff --git a/test/duskscript/assets/fail_tobase_bad_base.ds b/test/duskscript/assets/fail_tobase_bad_base.ds new file mode 100644 index 00000000..f4657b7b --- /dev/null +++ b/test/duskscript/assets/fail_tobase_bad_base.ds @@ -0,0 +1 @@ +tobase(5, 1); diff --git a/test/duskscript/assets/fail_tobase_big_base.ds b/test/duskscript/assets/fail_tobase_big_base.ds new file mode 100644 index 00000000..36f70d9d --- /dev/null +++ b/test/duskscript/assets/fail_tobase_big_base.ds @@ -0,0 +1 @@ +tobase(5, 37); diff --git a/test/duskscript/assets/fail_tobase_fraction.ds b/test/duskscript/assets/fail_tobase_fraction.ds new file mode 100644 index 00000000..dddabbe5 --- /dev/null +++ b/test/duskscript/assets/fail_tobase_fraction.ds @@ -0,0 +1 @@ +tobase(1.5, 2); diff --git a/test/duskscript/assets/fail_tobase_string.ds b/test/duskscript/assets/fail_tobase_string.ds new file mode 100644 index 00000000..3cc40d6e --- /dev/null +++ b/test/duskscript/assets/fail_tobase_string.ds @@ -0,0 +1 @@ +tobase("5", 2); diff --git a/test/duskscript/assets/fail_tobool_invalid.ds b/test/duskscript/assets/fail_tobool_invalid.ds new file mode 100644 index 00000000..4279ae85 --- /dev/null +++ b/test/duskscript/assets/fail_tobool_invalid.ds @@ -0,0 +1 @@ +tobool("maybe"); diff --git a/test/duskscript/assets/fail_tobool_null.ds b/test/duskscript/assets/fail_tobool_null.ds new file mode 100644 index 00000000..04560e3a --- /dev/null +++ b/test/duskscript/assets/fail_tobool_null.ds @@ -0,0 +1 @@ +tobool(null); diff --git a/test/duskscript/assets/fail_tonum_bad_digit.ds b/test/duskscript/assets/fail_tonum_bad_digit.ds new file mode 100644 index 00000000..47b578e5 --- /dev/null +++ b/test/duskscript/assets/fail_tonum_bad_digit.ds @@ -0,0 +1 @@ +tonum("12", 2); diff --git a/test/duskscript/assets/fail_tonum_bool.ds b/test/duskscript/assets/fail_tonum_bool.ds new file mode 100644 index 00000000..97999c9f --- /dev/null +++ b/test/duskscript/assets/fail_tonum_bool.ds @@ -0,0 +1 @@ +tonum(true); diff --git a/test/duskscript/assets/fail_tonum_empty.ds b/test/duskscript/assets/fail_tonum_empty.ds new file mode 100644 index 00000000..502ed2bd --- /dev/null +++ b/test/duskscript/assets/fail_tonum_empty.ds @@ -0,0 +1 @@ +tonum(""); diff --git a/test/duskscript/assets/fail_tonum_invalid.ds b/test/duskscript/assets/fail_tonum_invalid.ds new file mode 100644 index 00000000..4c2cb84a --- /dev/null +++ b/test/duskscript/assets/fail_tonum_invalid.ds @@ -0,0 +1 @@ +tonum("abc"); diff --git a/test/duskscript/assets/fail_tonum_too_big.ds b/test/duskscript/assets/fail_tonum_too_big.ds new file mode 100644 index 00000000..5c7a205c --- /dev/null +++ b/test/duskscript/assets/fail_tonum_too_big.ds @@ -0,0 +1 @@ +tonum("99999999999", 10); diff --git a/test/duskscript/assets/fail_tonum_trailing.ds b/test/duskscript/assets/fail_tonum_trailing.ds new file mode 100644 index 00000000..bdaa9b02 --- /dev/null +++ b/test/duskscript/assets/fail_tonum_trailing.ds @@ -0,0 +1 @@ +tonum("12abc"); diff --git a/test/duskscript/assets/fail_too_few_arguments.ds b/test/duskscript/assets/fail_too_few_arguments.ds new file mode 100644 index 00000000..7494a853 --- /dev/null +++ b/test/duskscript/assets/fail_too_few_arguments.ds @@ -0,0 +1 @@ +push([1]); diff --git a/test/duskscript/assets/fail_too_many_arguments.ds b/test/duskscript/assets/fail_too_many_arguments.ds new file mode 100644 index 00000000..d8ef6e63 --- /dev/null +++ b/test/duskscript/assets/fail_too_many_arguments.ds @@ -0,0 +1 @@ +len([], 1); diff --git a/test/duskscript/assets/fail_tostr_decimals_on_string.ds b/test/duskscript/assets/fail_tostr_decimals_on_string.ds new file mode 100644 index 00000000..016adea0 --- /dev/null +++ b/test/duskscript/assets/fail_tostr_decimals_on_string.ds @@ -0,0 +1 @@ +tostr("a", 2); diff --git a/test/duskscript/assets/fail_tostr_negative_decimals.ds b/test/duskscript/assets/fail_tostr_negative_decimals.ds new file mode 100644 index 00000000..0df7dab5 --- /dev/null +++ b/test/duskscript/assets/fail_tostr_negative_decimals.ds @@ -0,0 +1 @@ +tostr(1.5, -1); diff --git a/test/duskscript/assets/fail_tostr_too_many_decimals.ds b/test/duskscript/assets/fail_tostr_too_many_decimals.ds new file mode 100644 index 00000000..9207ed22 --- /dev/null +++ b/test/duskscript/assets/fail_tostr_too_many_decimals.ds @@ -0,0 +1 @@ +tostr(1.5, 7); diff --git a/test/duskscript/assets/fail_trim_null.ds b/test/duskscript/assets/fail_trim_null.ds new file mode 100644 index 00000000..308f05eb --- /dev/null +++ b/test/duskscript/assets/fail_trim_null.ds @@ -0,0 +1 @@ +trim(null); diff --git a/test/duskscript/assets/fail_undefined_function_call.ds b/test/duskscript/assets/fail_undefined_function_call.ds new file mode 100644 index 00000000..c04c23c6 --- /dev/null +++ b/test/duskscript/assets/fail_undefined_function_call.ds @@ -0,0 +1 @@ +thisDoesNotExist(); diff --git a/test/duskscript/assets/fail_undefined_variable_read.ds b/test/duskscript/assets/fail_undefined_variable_read.ds new file mode 100644 index 00000000..a09b682d --- /dev/null +++ b/test/duskscript/assets/fail_undefined_variable_read.ds @@ -0,0 +1 @@ +var x = thisDoesNotExist; diff --git a/test/duskscript/assets/fail_undefined_variable_write.ds b/test/duskscript/assets/fail_undefined_variable_write.ds new file mode 100644 index 00000000..b3d0ae62 --- /dev/null +++ b/test/duskscript/assets/fail_undefined_variable_write.ds @@ -0,0 +1 @@ +thisDoesNotExist = 5; diff --git a/test/duskscript/assets/fail_upcase_number.ds b/test/duskscript/assets/fail_upcase_number.ds new file mode 100644 index 00000000..d89a2c41 --- /dev/null +++ b/test/duskscript/assets/fail_upcase_number.ds @@ -0,0 +1 @@ +upcase(5); diff --git a/test/duskscript/assets/pass_arrays.ds b/test/duskscript/assets/pass_arrays.ds new file mode 100644 index 00000000..496522c4 --- /dev/null +++ b/test/duskscript/assets/pass_arrays.ds @@ -0,0 +1,218 @@ +// Creating and reading +var a = [1, 2, 3]; +assert(len(a) == 3); +assert(a[0] == 1 && a[2] == 3); +assert(len([]) == 0); +var nested = [[1, 2], [3, [4, 5]]]; +assert(nested[1][1][0] == 4); +assert(len(nested[1]) == 2); + +// Writing, assigning at the end appends +a[1] = 20; +assert(a[1] == 20); +a[3] = 4; +assert(len(a) == 4 && a[3] == 4); + +// push, pop, shift, unshift return and remove as expected +var q = []; +assert(push(q, "a") == 1); +assert(push(q, "b") == 2); +assert(unshift(q, "z") == 3); +assert(q[0] == "z" && q[1] == "a" && q[2] == "b"); +assert(pop(q) == "b"); +assert(shift(q) == "z"); +assert(len(q) == 1 && q[0] == "a"); +assert(pop(q) == "a"); +assert(isundefined(pop(q))); +assert(isundefined(shift(q))); +assert(len(q) == 0); + +// insert, insertarr, splice +var b = [1, 2, 5]; +assert(insert(b, 2, 3) == 4); +assert(deepequal(b, [1, 2, 3, 5])); +insert(b, 3, 4); +assert(deepequal(b, [1, 2, 3, 4, 5])); +insert(b, 0, 0); +assert(deepequal(b, [0, 1, 2, 3, 4, 5])); +insert(b, 6, 6); +assert(deepequal(b, [0, 1, 2, 3, 4, 5, 6])); +var c = [1, 4]; +assert(insertarr(c, 1, [2, 3]) == 4); +assert(deepequal(c, [1, 2, 3, 4])); +var d = [1, 2, 3, 4, 5]; +var removed = splice(d, 1, 2); +assert(deepequal(removed, [2, 3])); +assert(deepequal(d, [1, 4, 5])); +removed = splice(d, 2); +assert(deepequal(removed, [5])); +assert(deepequal(d, [1, 4])); +removed = splice(d, 0, 100); +assert(deepequal(removed, [1, 4])); +assert(len(d) == 0); +removed = splice(d, 0, 0); +assert(len(removed) == 0); + +// subarr copies a range +var e = [10, 20, 30, 40]; +assert(deepequal(subarr(e, 1, 3), [20, 30])); +assert(deepequal(subarr(e, 2), [30, 40])); +assert(deepequal(subarr(e, 0, 0), [])); +assert(deepequal(subarr(e, 4), [])); +var slice = subarr(e, 0, 2); +slice[0] = 99; +assert(e[0] == 10); + +// indexof, lastindexof +var f = [1, 2, 3, 2, 1]; +assert(indexof(f, 2) == 1); +assert(lastindexof(f, 2) == 3); +assert(indexof(f, 9) == -1); +assert(lastindexof(f, 9) == -1); +assert(indexof(["a", "b"], "b") == 1); +assert(indexof([null, 1], null) == 0); + +// reverse and sort +var g = [1, 2, 3, 4]; +reverse(g); +assert(deepequal(g, [4, 3, 2, 1])); +var odd = [1, 2, 3]; +reverse(odd); +assert(deepequal(odd, [3, 2, 1])); +reverse([]); +var h = [3, 1, 2, 10, -5, 2.5]; +sort(h); +assert(deepequal(h, [-5, 1, 2, 2.5, 3, 10])); +sort(h, true); +assert(deepequal(h, [10, 3, 2.5, 2, 1, -5])); +var words = ["banana", "Apple", "cherry", "apple"]; +sort(words); +assert(words[0] == "Apple" || words[0] == "apple"); +assert(words[3] == "cherry"); +assert(words[2] == "banana"); +var byLength = ["ccc", "a", "bb"]; +sort(byLength, false, function(x, y) { return len(x) - len(y); }); +assert(deepequal(byLength, ["a", "bb", "ccc"])); +var one = [7]; +sort(one); +assert(one[0] == 7); +var emptySort = []; +sort(emptySort); +// sort is stable: equal keys keep their order +var records = [{k: 1, n: "a"}, {k: 0, n: "b"}, {k: 1, n: "c"}, {k: 0, n: "d"}]; +sort(records, false, function(x, y) { return x.k - y.k; }); +assert(records[0].n == "b" && records[1].n == "d" && records[2].n == "a" && records[3].n == "c"); +// a long sort +var big = []; +for(var i = 0; i < 100; i++) push(big, (i * 37) % 101); +sort(big); +var ordered = true; +for(var j = 1; j < len(big); j++) { + if(big[j - 1] > big[j]) ordered = false; +} +assert(ordered); + +// fill, merge, empty +var filled = []; +fill(filled, 7, 3); +assert(deepequal(filled, [7, 7, 7])); +fill(filled, "x", 1); +assert(deepequal(filled, ["x"])); +fill(filled, 0, 0); +assert(len(filled) == 0); +var m1 = [1, 2]; +assert(merge(m1, [3, 4]) == 4); +assert(deepequal(m1, [1, 2, 3, 4])); +merge(m1, []); +assert(len(m1) == 4); +merge(m1, m1); +assert(len(m1) == 8); +var toEmpty = [1, 2, 3]; +empty(toEmpty); +assert(len(toEmpty) == 0); +push(toEmpty, "fresh"); +assert(toEmpty[0] == "fresh"); + +// filter, map, reduce +var nums = [1, 2, 3, 4, 5, 6]; +var evens = clone(nums); +filter(evens, function(v) { return v % 2 == 0; }); +assert(deepequal(evens, [2, 4, 6])); +assert(len(nums) == 6); +var withNulls = [1, null, 2, null]; +filter(withNulls); +assert(deepequal(withNulls, [1, 2])); +var indexes = clone(nums); +filter(indexes, function(v, idx) { return idx < 2; }); +assert(deepequal(indexes, [1, 2])); +assert(deepequal(map(nums, function(v) { return v * 10; }), [10, 20, 30, 40, 50, 60])); +assert(deepequal(map(nums, function(v, idx) { return idx; }), [0, 1, 2, 3, 4, 5])); +assert(len(map([], function(v) { return v; })) == 0); +assert(reduce(nums, function(acc, v) { return acc + v; }, 0) == 21); +assert(reduce(nums, function(acc, v) { return acc * v; }, 1) == 720); +assert(reduce([], function(acc, v) { return acc + v; }, "start") == "start"); +assert(reduce(["a", "b"], function(acc, v) { return acc + v; }, "") == "ab"); + +// flatten and join +assert(deepequal(flatten([[1], [2, 3], []]), [1, 2, 3])); +assert(len(flatten([])) == 0); +assert(deepequal(flatten([[[1]], [2]]), [[1], 2])); +assert(join([1, 2, 3], ", ") == "1, 2, 3"); + +// Arrays are shared, clone is shallow, deepclone is not +var original = [1, [2, 3]]; +var alias = original; +alias[0] = 100; +assert(original[0] == 100); +var shallow = clone(original); +shallow[0] = 5; +assert(original[0] == 100); +shallow[1][0] = 22; +assert(original[1][0] == 22); +var deep = deepclone(original); +deep[1][0] = 33; +assert(original[1][0] == 22); +assert(deep[1][0] == 33); +assert(deepequal(deepclone(original), original)); +assert(clone(original) != original); + +// deepequal +assert(deepequal([], [])); +assert(deepequal([1, "a", [true, null]], [1, "a", [true, null]])); +assert(!deepequal([1, 2], [1, 3])); +assert(!deepequal([1, 2], [1, 2, 3])); +assert(!deepequal([1], ["1"])); +assert(deepequal(1, 1)); +assert(!deepequal(1, 2)); +assert(deepequal({a: 1, b: [1]}, {b: [1], a: 1})); +assert(!deepequal({a: 1}, {a: 2})); +assert(!deepequal({a: 1}, {b: 1})); +assert(!deepequal([], {})); + +// Growing past the first capacity +var grown = []; +for(var n = 0; n < 200; n++) push(grown, n); +assert(len(grown) == 200); +assert(grown[0] == 0 && grown[199] == 199 && grown[100] == 100); +var shrunk = 0; +while(len(grown) > 0) { + pop(grown); + shrunk++; +} +assert(shrunk == 200); + +// Many shifts +var queue = []; +for(var k = 0; k < 50; k++) push(queue, k); +var taken = 0; +while(len(queue) > 0) { + assert(shift(queue) == taken); + taken++; +} +assert(taken == 50); + +// Looping while modifying a copy +var source = [1, 2, 3]; +var copy = clone(source); +foreach(var item : source) push(copy, item * 2); +assert(deepequal(copy, [1, 2, 3, 2, 4, 6])); diff --git a/test/duskscript/assets/pass_async.ds b/test/duskscript/assets/pass_async.ds new file mode 100644 index 00000000..4515a386 --- /dev/null +++ b/test/duskscript/assets/pass_async.ds @@ -0,0 +1,26 @@ +async function giveFive() { + return 5; +} +async function addAsync(a, b) { + return a + b; +} +async function chain() { + var first = await giveFive(); + var second = await addAsync(first, 10); + return second; +} +var value = await giveFive(); +assert(value == 5); +assert(await addAsync(2, 3) == 5); +assert(await chain() == 15); + +var promiseValue = giveFive(); +assert(!isnull(promiseValue)); +assert(await promiseValue == 5); + +// Looping with awaits +var total = 0; +for(var i = 0; i < 5; i++) { + total += await addAsync(i, 1); +} +assert(total == 15); diff --git a/test/duskscript/assets/pass_constants.ds b/test/duskscript/assets/pass_constants.ds new file mode 100644 index 00000000..33277672 --- /dev/null +++ b/test/duskscript/assets/pass_constants.ds @@ -0,0 +1,70 @@ +const LIMIT = 10; +const NAME = "dusk"; +const FLAG = true; +const NOTHING = null; +const RATIO = 2.5; +assert(LIMIT == 10); +assert(NAME == "dusk"); +assert(FLAG); +assert(isnull(NOTHING)); +assert(RATIO * 2 == 5); +assert(LIMIT + 1 == 11); +assert(NAME + "!" == "dusk!"); +assert(len(NAME) == 4); +var total = 0; +for(var i = 0; i < LIMIT; i++) total += 1; +assert(total == LIMIT); + +// Constant arrays and structs can be read +const LIST = [1, "two", [3, 4], {five: 5}]; +assert(len(LIST) == 4); +assert(LIST[0] == 1); +assert(LIST[1] == "two"); +assert(LIST[2][1] == 4); +assert(LIST[3].five == 5); +const CONFIG = {name: "cfg", nested: {depth: 2, items: [10, 20]}, on: true}; +assert(CONFIG.name == "cfg"); +assert(CONFIG.nested.depth == 2); +assert(CONFIG.nested.items[1] == 20); +assert(CONFIG["nested"]["items"][0] == 10); +assert(CONFIG.on); +assert(isundefined(CONFIG.missing)); +foreach(var item : LIST) assert(!isundefined(item)); +assert(join(map(CONFIG.nested.items, function(v) { return v; }), ",") == "10,20"); +assert(indexof(LIST, 1) == 0); + +// Constant functions +function helper(n) { + return n + LIMIT; +} +assert(helper(1) == 11); + +// Copies of constants can be changed, the constant itself stays as it was +var copy = clone(LIST); +push(copy, "extra"); +copy[0] = "changed"; +assert(len(copy) == 5 && copy[0] == "changed"); +assert(len(LIST) == 4 && LIST[0] == 1); +var deep = deepclone(CONFIG); +deep.name = "other"; +deep.nested.items[0] = 99; +assert(CONFIG.name == "cfg"); +assert(CONFIG.nested.items[0] == 10); +assert(deep.nested.items[0] == 99); +var fromConst = LIMIT; +fromConst++; +assert(fromConst == 11); +assert(LIMIT == 10); + +// A constant in a function that is called more than once +function useName() { + return NAME + "?"; +} +assert(useName() == "dusk?"); +assert(useName() == "dusk?"); + +// A constant built from other constants +const DOUBLE = LIMIT * 2; +assert(DOUBLE == 20); +const GREETING = NAME + " " + "engine"; +assert(GREETING == "dusk engine"); diff --git a/test/duskscript/assets/pass_control.ds b/test/duskscript/assets/pass_control.ds new file mode 100644 index 00000000..9fcb1d93 --- /dev/null +++ b/test/duskscript/assets/pass_control.ds @@ -0,0 +1,196 @@ +// if / else +var result = 0; +if(true) result = 1; +assert(result == 1); +if(false) result = 2; else result = 3; +assert(result == 3); +if(0) { result = 4; } else if(1) { result = 5; } else { result = 6; } +assert(result == 5); +if(null) { result = 7; } else if("") { result = 8; } else { result = 9; } +assert(result == 9); +if(1) { + if(0) { + result = 10; + } else { + result = 11; + } +} +assert(result == 11); + +// while +var i = 0; +var sum = 0; +while(i < 5) { + sum += i; + i++; +} +assert(sum == 10); +assert(i == 5); +while(false) { assert(false); } + +// for +sum = 0; +for(var j = 0; j < 10; j++) sum += j; +assert(sum == 45); +sum = 0; +for(var k = 10; k > 0; k -= 3) sum += k; +assert(sum == 10 + 7 + 4 + 1); +var count = 0; +for(;;) { + count++; + if(count == 5) break; +} +assert(count == 5); +var outer = 0; +var inner = 0; +for(var a = 0; a < 3; a++) { + outer++; + for(var b = 0; b < 4; b++) inner++; +} +assert(outer == 3 && inner == 12); + +// break and continue +sum = 0; +for(var m = 0; m < 10; m++) { + if(m % 2 == 0) continue; + if(m > 7) break; + sum += m; +} +assert(sum == 1 + 3 + 5 + 7); +i = 0; +sum = 0; +while(true) { + i++; + if(i < 3) continue; + sum += i; + if(i >= 5) break; +} +assert(sum == 3 + 4 + 5); + +// break only leaves the loop it is in +var pairs = 0; +for(var p = 0; p < 3; p++) { + for(var q = 0; q < 10; q++) { + if(q == 2) break; + pairs++; + } +} +assert(pairs == 6); + +// foreach +sum = 0; +foreach(var v : [1, 2, 3, 4]) sum += v; +assert(sum == 10); +var seen = []; +foreach(var item : ["a", 2, true, null, [1]]) push(seen, item); +assert(len(seen) == 5); +assert(seen[0] == "a" && seen[1] == 2 && seen[2] == true && isnull(seen[3])); +foreach(var nothing : []) assert(false); +sum = 0; +foreach(var w : [1, 2, 3, 4, 5, 6]) { + if(w == 2) continue; + if(w == 5) break; + sum += w; +} +assert(sum == 1 + 3 + 4); +var letters = ""; +foreach(var ch : "xyz") letters += ch; +assert(letters == "xyz"); +sum = 0; +foreach(var row : [[1, 2], [3, 4]]) { + foreach(var cell : row) sum += cell; +} +assert(sum == 10); + +// Block scope: a variable of a block is gone after it +var shadow = "outer"; +{ + var inside = 1; + assert(inside == 1); +} +assert(shadow == "outer"); + +// switch +function kind(value) { + var out = ""; + switch(value) { + case 1: + out = "one"; + break; + case 2: + out = "two"; + break; + case "s": + out = "string"; + break; + case true: + out = "bool"; + break; + default: + out = "other"; + } + return out; +} +assert(kind(1) == "one"); +assert(kind(2) == "two"); +assert(kind("s") == "string"); +assert(kind(true) == "bool"); +assert(kind(99) == "other"); +assert(kind(null) == "other"); +assert(kind("1") == "other"); + +// switch falls through without a break +function fall(value) { + var trace = ""; + switch(value) { + case 1: + trace += "a"; + case 2: + trace += "b"; + break; + case 3: + trace += "c"; + } + return trace; +} +assert(fall(1) == "ab"); +assert(fall(2) == "b"); +assert(fall(3) == "c"); +assert(fall(4) == ""); + +// switch in a loop: break leaves the switch, not the loop +var hits = 0; +for(var s = 0; s < 4; s++) { + switch(s) { + case 1: + break; + default: + hits++; + } +} +assert(hits == 3); +// continue inside a switch continues the loop +hits = 0; +for(var t = 0; t < 4; t++) { + switch(t) { + case 0: + continue; + } + hits++; +} +assert(hits == 3); + +// switch with a default in the middle +function middle(value) { + switch(value) { + case 1: + return "one"; + default: + return "default"; + case 2: + return "two"; + } +} +assert(middle(1) == "one"); +assert(middle(2) == "two"); +assert(middle(3) == "default"); diff --git a/test/duskscript/assets/pass_conversion.ds b/test/duskscript/assets/pass_conversion.ds new file mode 100644 index 00000000..eeb6d9c6 --- /dev/null +++ b/test/duskscript/assets/pass_conversion.ds @@ -0,0 +1,69 @@ +// tostr +assert(tostr(5) == "5"); +assert(tostr(-5) == "-5"); +assert(tostr(0) == "0"); +assert(tostr(1.5) == "1.5"); +assert(tostr(0.25) == "0.25"); +assert(tostr(100) == "100"); +assert(tostr(true) == "true"); +assert(tostr(false) == "false"); +assert(tostr(null) == "null"); +assert(tostr(undefined) == "undefined"); +assert(tostr("same") == "same"); +assert(tostr([1, "a", true]) == "[1, \"a\", true]"); +assert(tostr([]) == "[]"); +assert(tostr({}) == "{}"); +assert(tostr({a: 1}) == "{\"a\": 1}"); +assert(tostr({a: 1, b: "x"}) == "{\"a\": 1, \"b\": \"x\"}"); +assert(tostr([[1], [2, 3]]) == "[[1], [2, 3]]"); +assert(tostr([1, null, [true]]) == "[1, null, [true]]"); + +// tostr with decimals +assert(tostr(3.14159, 2) == "3.14"); +assert(tostr(3, 2) == "3.00"); +assert(tostr(2.5, 0) == "2" || tostr(2.5, 0) == "3"); +assert(tostr(1.5, 1) == "1.5"); +assert(tostr(0, 3) == "0.000"); +assert(tostr(-1.25, 2) == "-1.25"); + +// tobase +assert(tobase(255, 16) == "FF"); +assert(tobase(255, 2) == "11111111"); +assert(tobase(8, 8) == "10"); +assert(tobase(0, 2) == "0"); +assert(tobase(-5, 2) == "-101"); +assert(tobase(35, 36) == "Z"); +assert(tobase(true, 2) == "1"); +assert(tobase(false, 2) == "0"); +assert(tobase(10, 10) == "10"); + +// tonum +assert(tonum("42") == 42); +assert(tonum("-42") == -42); +assert(tonum("3.5") == 3.5); +assert(tonum("-0.5") == -0.5); +assert(tonum("0") == 0); +assert(tonum("007") == 7); +assert(tonum(5) == 5); +assert(tonum("ff", 16) == 255); +assert(tonum("FF", 16) == 255); +assert(tonum("0xff", 16) == 255); +assert(tonum("101", 2) == 5); +assert(tonum("0b101", 2) == 5); +assert(tonum("z", 36) == 35); +assert(tonum("-10", 10) == -10); +assert(tonum(tostr(1234)) == 1234); +assert(tonum(tobase(77, 2), 2) == 77); + +// tobool +assert(tobool(true) == true); +assert(tobool(false) == false); +assert(tobool(1) == true); +assert(tobool(0) == false); +assert(tobool(-3) == true); +assert(tobool("true") == true); +assert(tobool("false") == false); + +// Round trips +assert(tonum(tostr(12.5)) == 12.5); +assert(tobool(tostr(true)) == true); diff --git a/test/duskscript/assets/pass_functions.ds b/test/duskscript/assets/pass_functions.ds new file mode 100644 index 00000000..0a5f9ff7 --- /dev/null +++ b/test/duskscript/assets/pass_functions.ds @@ -0,0 +1,184 @@ +// Declaration, calling and returning +function add(a, b) { + return a + b; +} +assert(add(1, 2) == 3); +assert(add("a", "b") == "ab"); +assert(add(add(1, 2), add(3, 4)) == 10); + +// Functions are there from the start of their block +assert(later(2) == 4); +function later(n) { + return n * 2; +} + +// No return, or a bare return, gives undefined +function nothing() { +} +function bare() { + return; +} +assert(isundefined(nothing())); +assert(isundefined(bare())); +function early(flag) { + if(flag) return "early"; + return "late"; +} +assert(early(true) == "early"); +assert(early(false) == "late"); + +// Recursion +function factorial(n) { + if(n <= 1) return 1; + return n * factorial(n - 1); +} +assert(factorial(1) == 1); +assert(factorial(5) == 120); +function countdown(n) { + if(n == 0) return 0; + return 1 + countdown(n - 1); +} +assert(countdown(5) == 5); + +// Default parameters +function greet(name, greeting = "Hello") { + return greeting + " " + name; +} +assert(greet("Ann") == "Hello Ann"); +assert(greet("Ann", "Hi") == "Hi Ann"); +function scale(value, factor = 2, offset = 1) { + return value * factor + offset; +} +assert(scale(3) == 7); +assert(scale(3, 3) == 10); +assert(scale(3, 3, 0) == 9); +function defaultsToArray(list = [1, 2]) { + return len(list); +} +assert(defaultsToArray() == 2); +assert(defaultsToArray([1]) == 1); + +// Extra arguments are ignored +function one(a) { + return a; +} +assert(one(1, 2, 3) == 1); + +// Undefined argument takes the default +assert(greet("Bo", undefined) == "Hello Bo"); + +// Functions are values +var fn = add; +assert(fn(2, 3) == 5); +assert(isfunction(fn)); +assert(isfunction(add)); +assert(!isfunction(1)); +assert(!isfunction("add")); +assert(!isfunction(null)); +var anon = function(x) { return x * x; }; +assert(anon(4) == 16); +assert(isfunction(anon)); +var list = [function() { return 1; }, function() { return 2; }]; +assert(list[0]() == 1); +assert(list[1]() == 2); +var table = {double: function(x) { return x * 2; }, name: "t"}; +assert(table.double(4) == 8); +assert(table["double"](5) == 10); + +// Passing and returning functions +function apply(f, v) { + return f(v); +} +assert(apply(anon, 5) == 25); +assert(apply(function(v) { return v + 1; }, 1) == 2); +function pick(flag) { + if(flag) return add; + return anon; +} +assert(pick(true)(1, 1) == 2); +assert(pick(false)(3) == 9); + +// A function sees and changes the variables of the script +var counter = 0; +function increment() { + counter++; + return counter; +} +increment(); +increment(); +assert(counter == 2); +assert(increment() == 3); + +// Arguments are by value, but arrays and structs are shared +function reassign(v) { + v = 99; + return v; +} +var original = 1; +assert(reassign(original) == 99); +assert(original == 1); +function append(l) { + push(l, 4); +} +var shared = [1, 2, 3]; +append(shared); +assert(len(shared) == 4); +function rename(s) { + s.name = "changed"; +} +var record = {name: "start"}; +rename(record); +assert(record.name == "changed"); +function replace(l) { + l = []; + return len(l); +} +assert(replace(shared) == 0); +assert(len(shared) == 4); + +// Locals are not shared between calls +function local() { + var inner = 0; + inner++; + return inner; +} +assert(local() == 1); +assert(local() == 1); + +// Parameter named like an outer variable +var value = "outer"; +function shadows(value) { + return value; +} +assert(shadows("inner") == "inner"); +assert(value == "outer"); + +// Name of a function +function named() {} +assert(tostr(named) == "named"); + +// Mutual recursion +function isEven(n) { + if(n == 0) return true; + return isOdd(n - 1); +} +function isOdd(n) { + if(n == 0) return false; + return isEven(n - 1); +} +assert(isEven(4)); +assert(isOdd(3)); +assert(!isEven(3)); + +// inline and noinline +inline function twice(n) { + return n * 2; +} +noinline function thrice(n) { + return n * 3; +} +assert(twice(4) == 8); +assert(thrice(4) == 12); +var t = 0; +for(var i = 0; i < 3; i++) t += twice(i) + thrice(i); +assert(t == 0 + 5 + 10); diff --git a/test/duskscript/assets/pass_logic.ds b/test/duskscript/assets/pass_logic.ds new file mode 100644 index 00000000..bf353ddf --- /dev/null +++ b/test/duskscript/assets/pass_logic.ds @@ -0,0 +1,85 @@ +// Truthiness +assert(true); +assert(!false); +assert(1); +assert(-1); +assert(0.5); +assert(!0); +assert("text"); +assert(!""); +assert(!null); +assert(!undefined); +assert([1]); +assert(!isnull(1)); +assert({}); +assert(!!1); +assert(!!"a"); +assert(!!{}); +assert(![]); +assert(!(!true)); + +// And, or and not give bools +assert((1 && 2) == true); +assert((0 && 2) == false); +assert(("a" || "b") == true); +assert((0 || "b") == true); +assert((0 || 0) == false); +assert((null || undefined) == false); +assert((true && true) == true); +assert((true && false) == false); +assert((false || true) == true); +assert((false || false) == false); + +// Precedence: && binds tighter than || +assert((true || false && false) == true); +assert(((true || false) && false) == false); +assert((false && true || true) == true); + +// Short circuit: the right side never runs when the left decides +var calls = 0; +function bump() { + calls++; + return true; +} +var r = false && bump(); +assert(calls == 0); +r = true || bump(); +assert(calls == 0); +r = true && bump(); +assert(calls == 1); +r = false || bump(); +assert(calls == 2); +r = (1 > 2) && bump(); +assert(calls == 2); + +// Guarding an operation that would fail +var none = []; +assert(!(len(none) > 0 && none[0] == 1)); +var holder = {value: null}; +assert(!(!isnull(holder.value) && holder.value > 1)); + +// Equality of different types is false, never an error +assert(1 != "1"); +assert(true != 1); +assert(null != 0); +assert(null != false); +assert(null != ""); +assert(null != undefined); +assert(null == null); +assert(undefined == undefined); +assert([] != ""); +assert({} != []); + +// Arrays, structs and functions compare by identity +var a = [1]; +var b = [1]; +var c = a; +assert(a != b); +assert(a == c); +var sa = {k: 1}; +var sb = {k: 1}; +var sc = sa; +assert(sa != sb); +assert(sa == sc); +assert(bump == bump); +assert(bump != isnull); diff --git a/test/duskscript/assets/pass_math.ds b/test/duskscript/assets/pass_math.ds new file mode 100644 index 00000000..ea496956 --- /dev/null +++ b/test/duskscript/assets/pass_math.ds @@ -0,0 +1,83 @@ +assert(abs(-5) == 5); +assert(abs(5) == 5); +assert(abs(0) == 0); +assert(abs(-2.5) == 2.5); + +assert(sign(-9) == -1); +assert(sign(9) == 1); +assert(sign(0) == 0); +assert(sign(-0.1) == -1); + +assert(min(1, 2) == 1); +assert(min(2, 1) == 1); +assert(min(-1, 1) == -1); +assert(min(3, 3) == 3); +assert(max(1, 2) == 2); +assert(max(2, 1) == 2); +assert(max(-1, -2) == -1); + +assert(clamp(5, 0, 10) == 5); +assert(clamp(-5, 0, 10) == 0); +assert(clamp(50, 0, 10) == 10); +assert(clamp(0, 0, 10) == 0); +assert(clamp(10, 0, 10) == 10); + +assert(pow(2, 0) == 1); +assert(pow(2, 1) == 2); +assert(pow(2, 10) == 1024); +assert(pow(3, 3) == 27); +assert(pow(-2, 3) == -8); +assert(pow(0, 0) == 1); +assert(pow(1.5, 2) == 2.25); +assert(pow(2, -1) == 0.5); + +assert(floor(3.7) == 3); +assert(floor(3) == 3); +assert(floor(-3.2) == -4); +assert(ceil(3.2) == 4); +assert(ceil(3) == 3); +assert(ceil(-3.7) == -3); +assert(round(3.4) == 3); +assert(round(3.6) == 4); +assert(round(-3.4) == -3); +assert(round(-3.6) == -4); +assert(round(2.5) == 3); + +assert(sqrt(16) == 4); +assert(sqrt(0) == 0); +assert(sqrt(2.25) == 1.5); +assert(abs(sin(0)) < 0.0001); +assert(abs(cos(0) - 1) < 0.0001); +assert(abs(tan(0)) < 0.0001); +assert(abs(sin(PI / 2) - 1) < 0.0001); +assert(abs(cos(PI) + 1) < 0.0001); +assert(abs(atan2(1, 1) - PI / 4) < 0.0001); +assert(abs(PI - 3.14159) < 0.0001); + +// Random numbers stay in range +for(var i = 0; i < 200; i++) { + var r = rand(5, 10); + assert(r >= 5 && r < 10); + assert(r == floor(r)); + var f = randf(0, 1); + assert(f >= 0 && f <= 1); +} +assert(rand(3, 4) == 3); + +// The same seed gives the same numbers +randseed(1234); +var seedA = [rand(0, 1000), rand(0, 1000), rand(0, 1000)]; +randseed(1234); +var seedB = [rand(0, 1000), rand(0, 1000), rand(0, 1000)]; +assert(deepequal(seedA, seedB)); +randseed(0); +var zeroSeeded = rand(0, 1000); +assert(zeroSeeded >= 0 && zeroSeeded < 1000); + +// Shuffle keeps every element +var deck = [1, 2, 3, 4, 5, 6, 7, 8]; +shuffle(deck); +assert(len(deck) == 8); +var sum = 0; +foreach(var card : deck) sum += card; +assert(sum == 36); diff --git a/test/duskscript/assets/pass_mixed.ds b/test/duskscript/assets/pass_mixed.ds new file mode 100644 index 00000000..7be13fc7 --- /dev/null +++ b/test/duskscript/assets/pass_mixed.ds @@ -0,0 +1,131 @@ +// Arrays hold any mix of values, in any order +var a = [1, "two", true, null, [4, 5], {x: 6}, undefined, function() { return 8; }]; +assert(len(a) == 8); +assert(a[0] == 1); +assert(a[1] == "two"); +assert(a[2] == true); +assert(isnull(a[3])); +assert(a[4][1] == 5); +assert(a[5].x == 6); +assert(isundefined(a[6])); +assert(a[7]() == 8); + +// Pushing every type, one after the other, onto one array +var b = []; +push(b, 1); +push(b, "s"); +push(b, false); +push(b, null); +push(b, [1]); +push(b, {k: 1}); +push(b, undefined); +push(b, tostr); +push(b, 2.5); +push(b, "again"); +assert(len(b) == 10); +assert(b[0] == 1 && b[1] == "s" && b[2] == false && isnull(b[3])); +assert(b[4][0] == 1 && b[5].k == 1 && isundefined(b[6]) && isfunction(b[7])); +assert(b[8] == 2.5 && b[9] == "again"); + +// An element can be replaced by a value of a different type +var c = [1, 2, 3]; +c[0] = "text"; +c[1] = [true]; +c[2] = null; +assert(c[0] == "text" && c[1][0] == true && isnull(c[2])); +c[0] = 1; +c[1] = {a: 1}; +c[2] = false; +assert(c[0] == 1 && c[1].a == 1 && c[2] == false); + +// Every way of adding to an array takes any type +var d = [1]; +unshift(d, "front"); +insert(d, 1, null); +insertarr(d, 0, [true, {z: 1}]); +merge(d, ["tail", 3]); +assert(len(d) == 7); +assert(d[0] == true && d[1].z == 1 && d[2] == "front" && isnull(d[3]) && d[4] == 1 && d[5] == "tail" && d[6] == 3); +fill(d, "x", 2); +push(d, 1); +assert(deepequal(d, ["x", "x", 1])); + +// Arrays that were emptied or created by builtins take any type too +empty(d); +push(d, "a"); +push(d, 1); +assert(deepequal(d, ["a", 1])); +var parts = split("a,b", ","); +push(parts, 3); +push(parts, null); +assert(len(parts) == 4 && parts[2] == 3); +var mapped = map([1, 2], function(v) { return v; }); +push(mapped, "text"); +assert(mapped[2] == "text"); +var sliced = subarr([1, 2, 3], 0, 2); +push(sliced, "text"); +assert(sliced[2] == "text"); +var cloned = clone([1, 2]); +push(cloned, "text"); +assert(cloned[2] == "text"); +var deep = deepclone([1, 2]); +push(deep, [1]); +assert(len(deep) == 3); +var flat = flatten([[1], [2]]); +push(flat, "text"); +assert(flat[2] == "text"); +var taken = splice([1, 2, 3], 0, 2); +push(taken, "text"); +assert(taken[2] == "text"); + +// flatten and merge of arrays of different types +var flatMixed = flatten([[1, "a"], [true], [null, [2]]]); +assert(len(flatMixed) == 5); +assert(flatMixed[1] == "a" && flatMixed[2] == true && isnull(flatMixed[3]) && flatMixed[4][0] == 2); +var mergedMixed = [1, 2]; +merge(mergedMixed, ["a", "b"]); +assert(deepequal(mergedMixed, [1, 2, "a", "b"])); + +// Mixed arrays join, print and search +assert(join([1, "a", true, null], ",") == "1,a,true,null"); +assert(tostr([1, "a", true, null]) == "[1, \"a\", true, null]"); +assert(indexof([1, "1", true], "1") == 1); +assert(indexof([1, "1", true], true) == 2); +assert(indexof([0, "", false, null], false) == 2); +assert(indexof([0, "", false, null], null) == 3); + +// Iterating a mixed array +var seen = ""; +foreach(var v : [1, "b", true, null, [1]]) seen += tostr(v) + ";"; +assert(seen == "1;b;true;null;[1];"); + +// Mixed arrays filter, map and reduce +var kinds = map([1, "a", true, null], function(v) { + if(isnull(v)) return "null"; + if(v == 1) return "one"; + return tostr(v); +}); +assert(deepequal(kinds, ["one", "a", "true", "null"])); +var onlyStrings = [1, "a", 2, "b", null, "c"]; +filter(onlyStrings, function(v) { return v != null && v != 1 && v != 2; }); +assert(deepequal(onlyStrings, ["a", "b", "c"])); +var count = reduce([1, "a", null, true, [1]], function(acc, v) { return acc + 1; }, 0); +assert(count == 5); + +// Mixed arrays reverse, clone and compare +var m = [1, "a", null]; +reverse(m); +assert(isnull(m[0]) && m[1] == "a" && m[2] == 1); +assert(deepequal(clone(m), m)); +assert(!deepequal([1, "a"], ["a", 1])); + +// Sorting a mix with a comparer that puts numbers before strings +var sortable = ["b", 2, "a", 1]; +sort(sortable, false, function(x, y) { + var xs = x == tostr(x); + var ys = y == tostr(y); + if(xs == ys) return 0; + if(xs) return 1; + return -1; +}); +assert(deepequal(sortable, [2, 1, "b", "a"])); diff --git a/test/duskscript/assets/pass_numbers.ds b/test/duskscript/assets/pass_numbers.ds new file mode 100644 index 00000000..522f9647 --- /dev/null +++ b/test/duskscript/assets/pass_numbers.ds @@ -0,0 +1,97 @@ +// Arithmetic and precedence +assert((3 + 2) == 5); +assert((10 - 4) == 6); +assert((6 * 7) == 42); +assert((10 / 4) == 2.5); +assert((7 % 3) == 1); +assert((-7 % 3) == -1); +assert((7.5 % 2) == 1.5); +assert((2 + 3 * 4) == 14); +assert(((2 + 3) * 4) == 20); +assert((10 - 2 - 3) == 5); +assert((100 / 10 / 5) == 2); +assert((1.5 + 2.25) == 3.75); +assert((0.5 * 4) == 2); +assert(-5 + 5 == 0); +var n = 5; +assert(-n == -5); +assert(-(-n) == 5); +assert((n - -3) == 8); + +// Bitwise +assert((5 & 3) == 1); +assert((5 | 3) == 7); +assert((5 ^ 3) == 6); +assert(~5 == -6); +assert(~0 == -1); +assert((1 << 4) == 16); +assert((256 >> 4) == 16); +assert((-8 >> 1) == -4); +assert((1 << 32) == 0); +assert((255 & 15) == 15); +assert((1 | 2 | 4 | 8) == 15); +assert((6 & 3 | 8) == 10); + +// Comparison +assert(1 < 2); +assert(!(2 < 1)); +assert(2 > 1); +assert(2 <= 2); +assert(2 >= 2); +assert(!(2 < 2)); +assert(-1 < 0); +assert(1 != 2); +assert(!(1 != 1)); +assert(1 == 1); +assert(1.0 == 1); +assert(0.5 < 0.75); + +// Compound assignment +var x = 10; +x += 5; +assert(x == 15); +x -= 3; +assert(x == 12); +x *= 2; +assert(x == 24); +x /= 4; +assert(x == 6); +x %= 4; +assert(x == 2); +x <<= 3; +assert(x == 16); +x >>= 2; +assert(x == 4); +x |= 3; +assert(x == 7); +x &= 5; +assert(x == 5); +x ^= 1; +assert(x == 4); + +// Increment and decrement, prefix and postfix +var i = 0; +assert(i++ == 0); +assert(i == 1); +assert(++i == 2); +assert(i == 2); +assert(i-- == 2); +assert(i == 1); +assert(--i == 0); + +// Increment of elements and members +var arr = [1, 2, 3]; +arr[1]++; +assert(arr[1] == 3); +--arr[0]; +assert(arr[0] == 0); +var s = {count: 5}; +s.count++; +assert(s.count == 6); +s["count"] += 4; +assert(s.count == 10); + +// Large and small values stay numbers +assert(1000000 + 1 == 1000001); +assert(0 == -0); +assert(0.25 * 4 == 1); diff --git a/test/duskscript/assets/pass_strings.ds b/test/duskscript/assets/pass_strings.ds new file mode 100644 index 00000000..3274e68a --- /dev/null +++ b/test/duskscript/assets/pass_strings.ds @@ -0,0 +1,131 @@ +var s = "Hello, World"; + +// Length and indexing +assert(len(s) == 12); +assert(len("") == 0); +assert(s[0] == "H"); +assert(s[11] == "d"); +assert(s[4] == "o"); + +// Concatenation, everything turns into text next to a string +assert("a" + "b" == "ab"); +assert("" + "" == ""); +assert("n=" + 5 == "n=5"); +assert(5 + "n" == "5n"); +assert("v" + 1.5 == "v1.5"); +assert("b=" + true == "b=true"); +assert("b=" + false == "b=false"); +assert("x=" + null == "x=null"); +assert("x=" + undefined == "x=undefined"); +assert("a" + 1 + 2 == "a12"); +assert(1 + 2 + "a" == "3a"); +assert("arr " + [1, 2] == "arr [1, 2]"); +assert("arr " + ["a", 1] == "arr [\"a\", 1]"); + +// Equality compares text, not identity +assert("abc" == "abc"); +assert("abc" != "abd"); +assert("abc" != "ab"); +assert("" == ""); +assert("a" + "bc" == "ab" + "c"); +assert("1" != 1); + +// Case +assert(upcase("abc DEF 123") == "ABC DEF 123"); +assert(downcase("ABC def 123") == "abc def 123"); +assert(upcase("") == ""); + +// Trim +assert(trim(" hi ") == "hi"); +assert(ltrim(" hi ") == "hi "); +assert(rtrim(" hi ") == " hi"); +assert(trim("hi") == "hi"); +assert(trim(" ") == ""); + +// Searching +assert(contains(s, "World")); +assert(!contains(s, "world")); +assert(contains(s, "")); +assert(strindex(s, "o") == 4); +assert(strindex(s, "World") == 7); +assert(strindex(s, "zzz") == -1); +assert(startswith(s, "Hello")); +assert(!startswith(s, "World")); +assert(startswith(s, "")); +assert(startswith(s, s)); +assert(!startswith("ab", "abc")); +assert(endswith(s, "World")); +assert(!endswith(s, "Hello")); +assert(endswith(s, s)); +assert(!endswith("bc", "abc")); +assert(indexof("hello", "l") == 2); +assert(lastindexof("hello", "l") == 3); +assert(indexof("hello", "z") == -1); + +// Comparing helpers +assert(stringequals("a", "a")); +assert(!stringequals("a", "A")); +assert(stringequalsinsensitive("a", "A")); +assert(stringequalsinsensitive("Hello", "hELLO")); +assert(!stringequalsinsensitive("Hello", "Hell")); + +// Slicing +assert(subarr("abcdef", 2) == "cdef"); +assert(subarr("abcdef", 1, 4) == "bcd"); +assert(subarr("abcdef", 0, 0) == ""); +assert(subarr("abcdef", 6) == ""); +assert(subarr("abcdef", 0, 6) == "abcdef"); + +// Split and join +var parts = split("a,b,c", ","); +assert(len(parts) == 3); +assert(parts[0] == "a"); +assert(parts[2] == "c"); +assert(join(parts, "-") == "a-b-c"); +assert(len(split("abc", "")) == 3); +assert(split("abc", "")[1] == "b"); +assert(len(split("abc", ",")) == 1); +assert(len(split("a,,b", ",")) == 3); +assert(split("a,,b", ",")[1] == ""); +assert(len(split(",", ",")) == 2); +assert(join([], ",") == ""); +assert(join([1, "b", true, null], "|") == "1|b|true|null"); +assert(join(["x"], ",") == "x"); +assert(join(split("a--b--c", "--"), "+") == "a+b+c"); + +// Chars +assert(chr(65) == "A"); +assert(chr(97) == "a"); +assert(len(chr(255)) == 1); + +// Escapes +assert(len("a\nb") == 3); +assert(len("a\tb") == 3); +assert(len("\\") == 1); +assert(len("\"") == 1); +assert(len('it\'s') == 4); +assert("a\\b"[1] == "\\"); + +// Iterating +var total = ""; +foreach(var c : "abc") { + total = c + total; +} +assert(total == "cba"); + +// Strings are values: the original is untouched by what was derived from it +var original = "keep"; +var changed = upcase(original); +assert(original == "keep"); +assert(changed == "KEEP"); + +// Truthiness +assert("a"); +assert(!""); + +// Long-ish string built in a loop +var built = ""; +for(var i = 0; i < 100; i++) { + built = built + "x"; +} +assert(len(built) == 100); diff --git a/test/duskscript/assets/pass_structs.ds b/test/duskscript/assets/pass_structs.ds new file mode 100644 index 00000000..4370fabf --- /dev/null +++ b/test/duskscript/assets/pass_structs.ds @@ -0,0 +1,110 @@ +// Literals, member access and bracket access agree +var s = {name: "Ann", age: 30, "quoted key": true}; +assert(s.name == "Ann"); +assert(s["name"] == "Ann"); +assert(s.age == 30); +assert(s["quoted key"] == true); +var dynamic = "age"; +assert(s[dynamic] == 30); +assert(tostr({}) == "{}"); + +// Missing keys are undefined +assert(isundefined(s.nothing)); +assert(isundefined(s["nothing"])); +assert(isundefined({}.x)); + +// Writing existing and new keys +s.age = 31; +assert(s.age == 31); +s.city = "Paris"; +assert(s.city == "Paris"); +s["country"] = "FR"; +assert(s.country == "FR"); +assert(s.name == "Ann"); + +// Every key can change to a value of any type +s.name = 5; +assert(s.name == 5); +s.name = true; +assert(s.name == true); +s.name = null; +assert(isnull(s.name)); +s.name = [1, "two", {three: 3}]; +assert(s.name[2].three == 3); +s.name = "text again"; +assert(s.name == "text again"); +s.name = undefined; +assert(isundefined(s.name)); +s.name = function() { return "called"; }; +assert(s.name() == "called"); + +// Different keys hold different types at once +var mixed = { + n: 1, + s: "two", + b: true, + z: null, + a: [1, "x", false], + o: {inner: {deep: "yes"}}, + f: function(x) { return x + 1; } +}; +assert(mixed.n == 1); +assert(mixed.s == "two"); +assert(mixed.b == true); +assert(isnull(mixed.z)); +assert(mixed.a[1] == "x"); +assert(mixed.o.inner.deep == "yes"); +assert(mixed.f(1) == 2); +assert(mixed["o"]["inner"]["deep"] == "yes"); + +// Nested writes +mixed.o.inner.deep = 42; +assert(mixed.o.inner.deep == 42); +mixed.a[0] = "now text"; +assert(mixed.a[0] == "now text"); +mixed.o.extra = [1, 2]; +push(mixed.o.extra, 3); +assert(len(mixed.o.extra) == 3); + +// Structs are shared, deepclone is not +var alias = mixed; +alias.n = 100; +assert(mixed.n == 100); +var copy = deepclone(mixed); +copy.o.inner.deep = "copied"; +assert(mixed.o.inner.deep == 42); +copy.n = -1; +assert(mixed.n == 100); + +// Structs in arrays and arrays in structs +var people = [{n: "a", tags: ["x"]}, {n: "b", tags: []}]; +push(people[1].tags, "y"); +assert(people[1].tags[0] == "y"); +people[0].n = 7; +assert(people[0].n == 7); +var names = []; +foreach(var p : people) push(names, p.n); +assert(names[1] == "b"); + +// Keys are compared by text +var key = "a" + "b"; +var t = {ab: 1}; +assert(t[key] == 1); +t[key] = 2; +assert(t.ab == 2); +t["a" + "c"] = 3; +assert(t.ac == 3); + +// The most keys a struct holds +var wide = {}; +for(var i = 0; i < 16; i++) wide["k" + i] = i; +for(var j = 0; j < 16; j++) assert(wide["k" + j] == j); +wide.k3 = "overwritten without adding"; +assert(wide.k3 == "overwritten without adding"); + +// Counters in a struct +var counts = {hits: 0}; +for(var h = 0; h < 5; h++) counts.hits++; +assert(counts.hits == 5); +counts.hits += 10; +assert(counts.hits == 15); diff --git a/test/duskscript/assets/pass_types.ds b/test/duskscript/assets/pass_types.ds new file mode 100644 index 00000000..2a8f6766 --- /dev/null +++ b/test/duskscript/assets/pass_types.ds @@ -0,0 +1,30 @@ +assert(isnull(null)); +assert(!isnull(0)); +assert(!isnull("")); +assert(!isnull(undefined)); +assert(!isnull(false)); +assert(isundefined(undefined)); +assert(!isundefined(null)); +assert(!isundefined(0)); +assert(isnullorundefined(null)); +assert(isnullorundefined(undefined)); +assert(!isnullorundefined(0)); +assert(!isnullorundefined("")); +assert(!isnullorundefined([])); +assert(isfunction(isnull)); +assert(isfunction(function() {})); +assert(!isfunction({})); +assert(!isfunction([])); +assert(!isfunctionasync(1)); +assert(!isfunctionasync(isnull)); +async function work() { return 1; } +function plain() { return 1; } +assert(isfunctionasync(work)); +assert(!isfunctionasync(plain)); + +// Things that hold nothing are undefined +assert(isundefined(plain())); +var untouched; +assert(isundefined(untouched)); +var holder = {}; +assert(isundefined(holder.a)); diff --git a/test/duskscript/test_duskscript.c b/test/duskscript/test_duskscript.c index 8b8efe6f..485be81e 100644 --- a/test/duskscript/test_duskscript.c +++ b/test/duskscript/test_duskscript.c @@ -55,6 +55,98 @@ static void test_scriptNumbersAreAllFloats(void **state) { consoleDispose(); } +// Runs a packed fixture and describes where it stopped, for a failure message +static scriptresult_t duskscript_test_run(const char_t *path, scripterror_t *error) { + scriptresult_t result; + + consoleInit(); + assert_int_equal(errorIsOk(scriptInit()), true); + result = scriptRuntimeRunFile(SCRIPT.runtime, path, true, error); + return result; +} + +static void duskscript_test_finish(void) { + assert_int_equal(errorIsOk(scriptDispose()), true); + consoleDispose(); +} + +// A pass fixture is a script of assert() calls, it has to run to the end +static void test_scriptPasses(void **state) { + const char_t *path = (const char_t *)*state; + scripterror_t error; + scriptresult_t result; + uint8_t i; + + memoryZero(&error, sizeof(error)); + result = duskscript_test_run(path, &error); + duskscript_test_finish(); + if(result != SCRIPT_OK) { + print_error("%s stopped with code %d: %s\n", path, (int)result, error.message); + for(i = 0; i < error.frameCount; i++) { + print_error(" at %s %s:%d:%d\n", error.frames[i].name, error.frames[i].file, (int)error.frames[i].line, (int)error.frames[i].column); + } + } + assert_int_equal(result, SCRIPT_OK); +} + +// A fail fixture does one thing the engine has to refuse, as a runtime error +// with a message, and not a crash, a hang, or going on as if nothing happened +static void test_scriptFailsWithRuntimeError(void **state) { + const char_t *path = (const char_t *)*state; + scripterror_t error; + scriptresult_t result; + + memoryZero(&error, sizeof(error)); + result = duskscript_test_run(path, &error); + duskscript_test_finish(); + if(result != SCRIPT_ERROR_RUNTIME) print_error("%s was expected to fail but gave code %d\n", path, (int)result); + assert_int_equal(result, SCRIPT_ERROR_RUNTIME); + assert_true(error.message[0] != '\0'); +} + +// The engine is still usable after a script failed: a run after an error +static void test_scriptRunsAfterAnError(void **state) { + scripterror_t error; + + memoryZero(&error, sizeof(error)); + consoleInit(); + assert_int_equal(errorIsOk(scriptInit()), true); + assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "fail_assert_false.dsb", true, &error), SCRIPT_ERROR_RUNTIME); + assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "fail_div_zero.dsb", true, &error), SCRIPT_ERROR_RUNTIME); + assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "pass_arrays.dsb", true, &error), SCRIPT_OK); + assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "pass_structs.dsb", true, &error), SCRIPT_OK); + duskscript_test_finish(); +} + +// The same script can run again and again, nothing of it is left behind +static void test_scriptRunsRepeatedly(void **state) { + scripterror_t error; + int32_t i; + + memoryZero(&error, sizeof(error)); + consoleInit(); + assert_int_equal(errorIsOk(scriptInit()), true); + for(i = 0; i < 20; i++) { + assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "pass_mixed.dsb", true, &error), SCRIPT_OK); + assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "pass_strings.dsb", true, &error), SCRIPT_OK); + } + duskscript_test_finish(); +} + +// A file that does not exist is an error, not a crash +static void test_scriptMissingFile(void **state) { + scripterror_t error; + + memoryZero(&error, sizeof(error)); + consoleInit(); + assert_int_equal(errorIsOk(scriptInit()), true); + assert_int_not_equal(scriptRuntimeRunFile(SCRIPT.runtime, "no_such_script.dsb", true, &error), SCRIPT_OK); + duskscript_test_finish(); +} + +#define SCRIPT_PASS(name) { "pass_" #name, test_scriptPasses, NULL, NULL, (void *)("pass_" #name ".dsb") } +#define SCRIPT_FAIL(name) { "fail_" #name, test_scriptFailsWithRuntimeError, NULL, NULL, (void *)("fail_" #name ".dsb") } + // Fixtures load through the asset system now, so ASSET needs a real zip // behind it - open the packed test.dsk directly rather than the full // assetInit()/assetInitPlatform(), which expects a real launched engine. @@ -76,6 +168,147 @@ int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_scriptRunsAndPrintsToConsole), cmocka_unit_test(test_scriptNumbersAreAllFloats), + cmocka_unit_test(test_scriptRunsAfterAnError), + cmocka_unit_test(test_scriptRunsRepeatedly), + cmocka_unit_test(test_scriptMissingFile), + SCRIPT_PASS(arrays), + SCRIPT_PASS(async), + SCRIPT_PASS(constants), + SCRIPT_PASS(control), + SCRIPT_PASS(conversion), + SCRIPT_PASS(functions), + SCRIPT_PASS(logic), + SCRIPT_PASS(math), + SCRIPT_PASS(mixed), + SCRIPT_PASS(numbers), + SCRIPT_PASS(strings), + SCRIPT_PASS(structs), + SCRIPT_PASS(types), + SCRIPT_FAIL(abs_string), + SCRIPT_FAIL(add_array), + SCRIPT_FAIL(add_bool), + SCRIPT_FAIL(add_null), + SCRIPT_FAIL(array_too_long), + SCRIPT_FAIL(array_too_long_merge), + SCRIPT_FAIL(assert_empty_string), + SCRIPT_FAIL(assert_false), + SCRIPT_FAIL(assert_no_arguments), + SCRIPT_FAIL(assert_null), + SCRIPT_FAIL(assert_zero), + SCRIPT_FAIL(async_error), + SCRIPT_FAIL(async_error_nested), + SCRIPT_FAIL(bitwise_string), + SCRIPT_FAIL(call_missing_member), + SCRIPT_FAIL(call_null), + SCRIPT_FAIL(call_number), + SCRIPT_FAIL(callback_raises), + SCRIPT_FAIL(chr_fraction), + SCRIPT_FAIL(chr_too_big), + SCRIPT_FAIL(chr_zero), + SCRIPT_FAIL(compare_mixed), + SCRIPT_FAIL(compare_null), + SCRIPT_FAIL(compare_strings), + SCRIPT_FAIL(const_array_alias_assign), + SCRIPT_FAIL(const_array_pop), + SCRIPT_FAIL(const_array_push), + SCRIPT_FAIL(const_array_sort), + SCRIPT_FAIL(const_struct_alias_assign), + SCRIPT_FAIL(const_struct_alias_new_key), + SCRIPT_FAIL(contains_number), + SCRIPT_FAIL(deepclone_cycle), + SCRIPT_FAIL(deepclone_struct_cycle), + SCRIPT_FAIL(div_zero), + SCRIPT_FAIL(error_after_output), + SCRIPT_FAIL(error_in_loop), + SCRIPT_FAIL(error_inside_function), + SCRIPT_FAIL(error_inside_nested_calls), + SCRIPT_FAIL(fill_negative), + SCRIPT_FAIL(fill_too_many), + SCRIPT_FAIL(filter_non_function), + SCRIPT_FAIL(flatten_non_array_items), + SCRIPT_FAIL(foreach_null), + SCRIPT_FAIL(foreach_number), + SCRIPT_FAIL(foreach_struct), + SCRIPT_FAIL(include_missing), + SCRIPT_FAIL(index_assign_negative), + SCRIPT_FAIL(index_assign_oob), + SCRIPT_FAIL(index_fraction), + SCRIPT_FAIL(index_negative), + SCRIPT_FAIL(index_number), + SCRIPT_FAIL(index_read_oob), + SCRIPT_FAIL(index_string_key), + SCRIPT_FAIL(indexof_non_array), + SCRIPT_FAIL(indexof_string_long_needle), + SCRIPT_FAIL(insert_negative), + SCRIPT_FAIL(insert_out_of_range), + SCRIPT_FAIL(insertarr_out_of_range), + SCRIPT_FAIL(invert_string), + SCRIPT_FAIL(join_non_array), + SCRIPT_FAIL(join_non_string_separator), + SCRIPT_FAIL(len_null), + SCRIPT_FAIL(len_number), + SCRIPT_FAIL(map_non_array), + SCRIPT_FAIL(map_non_function), + SCRIPT_FAIL(member_of_null), + SCRIPT_FAIL(member_of_number), + SCRIPT_FAIL(min_string), + SCRIPT_FAIL(missing_parameter), + SCRIPT_FAIL(missing_second_parameter), + SCRIPT_FAIL(mod_zero), + SCRIPT_FAIL(mul_string), + SCRIPT_FAIL(mutual_recursion_too_deep), + SCRIPT_FAIL(negate_string), + SCRIPT_FAIL(pop_string), + SCRIPT_FAIL(pow_fraction), + SCRIPT_FAIL(push_string), + SCRIPT_FAIL(rand_backwards), + SCRIPT_FAIL(rand_empty_range), + SCRIPT_FAIL(rand_fraction), + SCRIPT_FAIL(randf_empty_range), + SCRIPT_FAIL(recursion_too_deep), + SCRIPT_FAIL(reduce_non_function), + SCRIPT_FAIL(reverse_string), + SCRIPT_FAIL(sort_bad_comparer_result), + SCRIPT_FAIL(sort_mixed), + SCRIPT_FAIL(sort_non_array), + SCRIPT_FAIL(sort_structs), + SCRIPT_FAIL(splice_negative_count), + SCRIPT_FAIL(splice_out_of_range), + SCRIPT_FAIL(split_non_string), + SCRIPT_FAIL(sqrt_negative), + SCRIPT_FAIL(string_assign), + SCRIPT_FAIL(string_index_oob), + SCRIPT_FAIL(string_too_long), + SCRIPT_FAIL(struct_number_key), + SCRIPT_FAIL(struct_number_key_assign), + SCRIPT_FAIL(struct_too_many_keys), + SCRIPT_FAIL(sub_string), + SCRIPT_FAIL(subarr_backwards), + SCRIPT_FAIL(subarr_negative), + SCRIPT_FAIL(subarr_past_end), + SCRIPT_FAIL(subarr_string_past_end), + SCRIPT_FAIL(tobase_bad_base), + SCRIPT_FAIL(tobase_big_base), + SCRIPT_FAIL(tobase_fraction), + SCRIPT_FAIL(tobase_string), + SCRIPT_FAIL(tobool_invalid), + SCRIPT_FAIL(tobool_null), + SCRIPT_FAIL(tonum_bad_digit), + SCRIPT_FAIL(tonum_bool), + SCRIPT_FAIL(tonum_empty), + SCRIPT_FAIL(tonum_invalid), + SCRIPT_FAIL(tonum_too_big), + SCRIPT_FAIL(tonum_trailing), + SCRIPT_FAIL(too_few_arguments), + SCRIPT_FAIL(too_many_arguments), + SCRIPT_FAIL(tostr_decimals_on_string), + SCRIPT_FAIL(tostr_negative_decimals), + SCRIPT_FAIL(tostr_too_many_decimals), + SCRIPT_FAIL(trim_null), + SCRIPT_FAIL(undefined_function_call), + SCRIPT_FAIL(undefined_variable_read), + SCRIPT_FAIL(undefined_variable_write), + SCRIPT_FAIL(upcase_number), }; return cmocka_run_group_tests(tests, duskscript_test_setup, duskscript_test_teardown); }