From 31ee7989de19bddcf94c246ba00cf02a17668a87 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 3 Nov 2026 07:29:25 -0600 Subject: [PATCH] Remove rpg, ui, editor and scene subfolders; split scripts by platform Delete src/dusk/rpg, src/dusk/ui, the animation/keyframe modules, the scene subfolders, the chunk asset loader and ./editor, and strip the dependent save slot, scene type, engine and test code so the main binary and tests build again. Add scene/node.c/.h skeleton and move build scripts into scripts//. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/build.yml | 14 +- .github/workflows/test.yml | 2 +- editor/client/public/common/chunkterrain.js | 159 --- editor/client/public/common/dmf.js | 64 -- editor/client/public/index.html | 6 - editor/client/public/map/index.html | 92 -- editor/client/public/map/map.css | 65 -- editor/client/public/map/map.js | 920 ------------------ editor/client/public/map/renderer.js | 298 ------ editor/client/public/template.html | 23 - editor/server/server.py | 298 ------ editor/start.sh | 4 - .../{ => gamecube}/build-gamecube-docker.sh | 2 +- .../build-gamecube-iso-docker.sh | 2 +- scripts/{ => gamecube}/build-gamecube-iso.sh | 0 scripts/{ => gamecube}/build-gamecube.sh | 0 scripts/{ => knulli}/build-knulli-docker.sh | 2 +- scripts/{ => knulli}/build-knulli.sh | 0 scripts/{ => linux}/build-linux-docker.sh | 2 +- scripts/{ => linux}/build-linux.sh | 0 scripts/{ => linux}/test-linux-docker.sh | 2 +- scripts/{ => linux}/test-linux.sh | 0 scripts/{ => psp}/build-psp-docker.sh | 2 +- scripts/{ => psp}/build-psp.sh | 0 scripts/{ => vita}/build-vita-docker.sh | 2 +- scripts/{ => vita}/build-vita.sh | 0 scripts/{ => wii}/build-wii-docker.sh | 2 +- scripts/{ => wii}/build-wii-iso-docker.sh | 2 +- scripts/{ => wii}/build-wii-iso.sh | 0 scripts/{ => wii}/build-wii.sh | 0 src/dusk/CMakeLists.txt | 2 - src/dusk/animation/CMakeLists.txt | 2 - src/dusk/animation/animation.c | 132 --- src/dusk/animation/animation.h | 94 -- src/dusk/animation/keyframe.c | 45 - src/dusk/animation/keyframe.h | 27 - src/dusk/asset/loader/CMakeLists.txt | 1 - src/dusk/asset/loader/assetloader.c | 6 - src/dusk/asset/loader/assetloader.h | 5 - src/dusk/asset/loader/chunk/CMakeLists.txt | 10 - .../asset/loader/chunk/assetchunkloader.c | 484 --------- .../asset/loader/chunk/assetchunkloader.h | 120 --- src/dusk/display/display.c | 1 - src/dusk/engine/engine.c | 9 - src/dusk/locale/localemanager.c | 4 - src/dusk/rpg/CMakeLists.txt | 20 - src/dusk/rpg/battle/CMakeLists.txt | 15 - src/dusk/rpg/battle/battle.c | 79 -- src/dusk/rpg/battle/battle.h | 75 -- src/dusk/rpg/battle/fighter/CMakeLists.txt | 10 - src/dusk/rpg/battle/fighter/battlefighter.c | 230 ----- src/dusk/rpg/battle/fighter/battlefighter.h | 127 --- .../battle/fighter/battlefightercontroller.h | 18 - .../rpg/battle/fighter/battlefighterpos.h | 35 - .../rpg/battle/fighter/battlefighterspecies.h | 22 - .../rpg/battle/fighter/battlefighterstats.h | 22 - .../rpg/battle/fighter/battlefighterstatus.h | 20 - .../rpg/battle/fighter/battlefighterteam.h | 16 - src/dusk/rpg/battle/move/CMakeLists.txt | 10 - src/dusk/rpg/battle/move/battlemove.c | 139 --- src/dusk/rpg/battle/move/battlemove.h | 42 - src/dusk/rpg/battle/move/battlemoveaction.h | 18 - src/dusk/rpg/battle/move/battletarget.h | 16 - src/dusk/rpg/battle/party.c | 188 ---- src/dusk/rpg/battle/party.h | 126 --- src/dusk/rpg/battle/state/CMakeLists.txt | 19 - src/dusk/rpg/battle/state/battlestate.c | 66 -- src/dusk/rpg/battle/state/battlestate.h | 64 -- src/dusk/rpg/battle/state/battlestateending.c | 28 - src/dusk/rpg/battle/state/battlestateending.h | 29 - .../rpg/battle/state/battlestateexecuting.c | 116 --- .../rpg/battle/state/battlestateexecuting.h | 50 - src/dusk/rpg/battle/state/battlestatefled.c | 32 - src/dusk/rpg/battle/state/battlestatefled.h | 30 - src/dusk/rpg/battle/state/battlestatelost.c | 32 - src/dusk/rpg/battle/state/battlestatelost.h | 30 - .../rpg/battle/state/battlestateopening.c | 28 - .../rpg/battle/state/battlestateopening.h | 28 - .../rpg/battle/state/battlestatepostround.c | 20 - .../rpg/battle/state/battlestatepostround.h | 28 - .../rpg/battle/state/battlestatepreround.c | 41 - .../rpg/battle/state/battlestatepreround.h | 28 - .../rpg/battle/state/battlestateselection.c | 146 --- .../rpg/battle/state/battlestateselection.h | 120 --- src/dusk/rpg/battle/state/battlestatewon.c | 32 - src/dusk/rpg/battle/state/battlestatewon.h | 30 - src/dusk/rpg/cutscene/CMakeLists.txt | 15 - src/dusk/rpg/cutscene/cutscene.c | 178 ---- src/dusk/rpg/cutscene/cutscene.h | 154 --- src/dusk/rpg/cutscene/cutscenepause.h | 26 - src/dusk/rpg/cutscene/cutsceneref.c | 24 - src/dusk/rpg/cutscene/cutsceneref.h | 36 - src/dusk/rpg/cutscene/cutscenesystem.c | 358 ------- src/dusk/rpg/cutscene/cutscenesystem.h | 241 ----- src/dusk/rpg/cutscene/item/CMakeLists.txt | 24 - .../rpg/cutscene/item/audio/CMakeLists.txt | 17 - .../cutscene/item/audio/cutsceneaudiofade.c | 59 -- .../cutscene/item/audio/cutsceneaudiofade.h | 56 -- .../item/audio/cutsceneaudiofadewait.c | 24 - .../item/audio/cutsceneaudiofadewait.h | 36 - .../cutscene/item/audio/cutsceneaudiopause.c | 31 - .../cutscene/item/audio/cutsceneaudiopause.h | 45 - .../cutscene/item/audio/cutsceneaudioplay.c | 62 -- .../cutscene/item/audio/cutsceneaudioplay.h | 58 -- .../cutscene/item/audio/cutsceneaudioresume.c | 31 - .../cutscene/item/audio/cutsceneaudioresume.h | 45 - .../cutscene/item/audio/cutsceneaudioset.c | 54 - .../cutscene/item/audio/cutsceneaudioset.h | 57 -- .../item/audio/cutsceneaudiosetloop.c | 52 - .../item/audio/cutsceneaudiosetloop.h | 54 - .../cutscene/item/audio/cutsceneaudiosetpan.c | 41 - .../cutscene/item/audio/cutsceneaudiosetpan.h | 51 - .../cutscene/item/audio/cutsceneaudiostop.c | 31 - .../cutscene/item/audio/cutsceneaudiostop.h | 45 - .../rpg/cutscene/item/battle/CMakeLists.txt | 14 - .../item/battle/cutscenebattlefighterdamage.c | 68 -- .../item/battle/cutscenebattlefighterdamage.h | 55 -- .../item/battle/cutscenebattlefighterheal.c | 68 -- .../item/battle/cutscenebattlefighterheal.h | 55 -- .../item/battle/cutscenebattlepostmove.c | 60 -- .../item/battle/cutscenebattlepostmove.h | 37 - .../item/battle/cutscenebattlesetstate.c | 41 - .../item/battle/cutscenebattlesetstate.h | 50 - .../item/battle/cutscenebattlestart.c | 95 -- .../item/battle/cutscenebattlestart.h | 64 -- .../item/battle/cutscenebattlewaitselection.c | 29 - .../item/battle/cutscenebattlewaitselection.h | 27 - .../rpg/cutscene/item/control/CMakeLists.txt | 16 - .../item/control/cutsceneconcurrent.c | 46 - .../item/control/cutsceneconcurrent.h | 60 -- .../rpg/cutscene/item/control/cutsceneidle.c | 15 - .../rpg/cutscene/item/control/cutsceneidle.h | 26 - .../cutscene/item/control/cutscenemarker.c | 35 - .../cutscene/item/control/cutscenemarker.h | 54 - .../cutscene/item/control/cutscenequitgame.c | 23 - .../cutscene/item/control/cutscenequitgame.h | 38 - .../cutscene/item/control/cutscenerestart.c | 23 - .../cutscene/item/control/cutscenerestart.h | 38 - .../rpg/cutscene/item/control/cutscenescene.c | 39 - .../rpg/cutscene/item/control/cutscenescene.h | 53 - .../cutscene/item/control/cutscenesetpause.c | 41 - .../cutscene/item/control/cutscenesetpause.h | 45 - .../rpg/cutscene/item/control/cutscenewait.c | 34 - .../rpg/cutscene/item/control/cutscenewait.h | 47 - src/dusk/rpg/cutscene/item/cutscenecallback.c | 23 - src/dusk/rpg/cutscene/item/cutscenecallback.h | 34 - src/dusk/rpg/cutscene/item/cutsceneitem.c | 562 ----------- src/dusk/rpg/cutscene/item/cutsceneitem.h | 367 ------- src/dusk/rpg/cutscene/item/cutsceneitembase.c | 15 - src/dusk/rpg/cutscene/item/cutsceneitembase.h | 110 --- src/dusk/rpg/cutscene/item/cutsceneprint.c | 37 - src/dusk/rpg/cutscene/item/cutsceneprint.h | 50 - .../rpg/cutscene/item/entity/CMakeLists.txt | 14 - .../cutscene/item/entity/cutsceneentityadd.c | 57 -- .../cutscene/item/entity/cutsceneentityadd.h | 51 - .../item/entity/cutsceneentityremove.c | 41 - .../item/entity/cutsceneentityremove.h | 48 - .../item/entity/cutsceneentityteleport.c | 50 - .../item/entity/cutsceneentityteleport.h | 50 - .../cutscene/item/entity/cutsceneentityturn.c | 52 - .../cutscene/item/entity/cutsceneentityturn.h | 52 - .../item/entity/cutsceneentitywalkto.c | 85 -- .../item/entity/cutsceneentitywalkto.h | 63 -- .../item/entity/cutsceneentitywalktoentity.c | 65 -- .../item/entity/cutsceneentitywalktoentity.h | 56 -- .../rpg/cutscene/item/item/CMakeLists.txt | 9 - .../rpg/cutscene/item/item/cutsceneitemgive.c | 36 - .../rpg/cutscene/item/item/cutsceneitemgive.h | 50 - .../rpg/cutscene/item/json/CMakeLists.txt | 22 - .../cutscene/item/json/cutscenejsonareaid.c | 23 - .../cutscene/item/json/cutscenejsonareaid.h | 25 - .../item/json/cutscenejsonaudiochannel.c | 49 - .../item/json/cutscenejsonaudiochannel.h | 40 - .../item/json/cutscenejsonbattlestate.c | 38 - .../item/json/cutscenejsonbattlestate.h | 21 - .../cutscene/item/json/cutscenejsoneasing.c | 42 - .../cutscene/item/json/cutscenejsoneasing.h | 19 - .../cutscene/item/json/cutscenejsonemoji.c | 29 - .../cutscene/item/json/cutscenejsonemoji.h | 19 - .../item/json/cutscenejsonentitydir.c | 34 - .../item/json/cutscenejsonentitydir.h | 19 - .../item/json/cutscenejsonentityindex.c | 27 - .../item/json/cutscenejsonentityindex.h | 27 - .../item/json/cutscenejsonentitytype.c | 30 - .../item/json/cutscenejsonentitytype.h | 19 - .../rpg/cutscene/item/json/cutscenejsonenum.c | 41 - .../rpg/cutscene/item/json/cutscenejsonenum.h | 51 - .../cutscene/item/json/cutscenejsonfield.c | 70 -- .../cutscene/item/json/cutscenejsonfield.h | 88 -- .../item/json/cutscenejsonpauseflags.c | 38 - .../item/json/cutscenejsonpauseflags.h | 24 - .../item/json/cutscenejsonscenetype.c | 30 - .../item/json/cutscenejsonscenetype.h | 19 - .../item/json/cutscenejsonstoryflag.c | 18 - .../item/json/cutscenejsonstoryflag.h | 21 - .../item/json/cutscenejsonuiscreentype.c | 31 - .../item/json/cutscenejsonuiscreentype.h | 22 - .../rpg/cutscene/item/maparea/CMakeLists.txt | 11 - .../item/maparea/cutscenemapareaadd.c | 29 - .../item/maparea/cutscenemapareaadd.h | 44 - .../item/maparea/cutscenemaparearemove.c | 38 - .../item/maparea/cutscenemaparearemove.h | 49 - .../item/maparea/cutscenemapareawait.c | 72 -- .../item/maparea/cutscenemapareawait.h | 59 -- .../rpg/cutscene/item/party/CMakeLists.txt | 12 - .../item/party/cutscenepartyaddmember.c | 42 - .../item/party/cutscenepartyaddmember.h | 54 - .../item/party/cutscenepartyhealall.c | 23 - .../item/party/cutscenepartyhealall.h | 33 - .../item/party/cutscenepartyhealmember.c | 42 - .../item/party/cutscenepartyhealmember.h | 53 - .../item/party/cutscenepartyremovemember.c | 44 - .../item/party/cutscenepartyremovemember.h | 54 - .../rpg/cutscene/item/save/CMakeLists.txt | 10 - .../item/save/cutscenesavedevicecheck.c | 57 -- .../item/save/cutscenesavedevicecheck.h | 55 -- .../item/save/cutscenesaveloadallslots.c | 55 -- .../item/save/cutscenesaveloadallslots.h | 54 - .../rpg/cutscene/item/story/CMakeLists.txt | 10 - .../item/story/cutscenestorysetflag.c | 39 - .../item/story/cutscenestorysetflag.h | 51 - .../item/story/cutscenestorywaitflag.c | 32 - .../item/story/cutscenestorywaitflag.h | 40 - src/dusk/rpg/cutscene/item/ui/CMakeLists.txt | 18 - src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c | 49 - src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h | 52 - src/dusk/rpg/cutscene/item/ui/cutscenefade.c | 66 -- src/dusk/rpg/cutscene/item/ui/cutscenefade.h | 53 - .../rpg/cutscene/item/ui/cutscenekeyboard.c | 27 - .../rpg/cutscene/item/ui/cutscenekeyboard.h | 44 - src/dusk/rpg/cutscene/item/ui/cutscenemodal.c | 79 -- src/dusk/rpg/cutscene/item/ui/cutscenemodal.h | 129 --- .../item/ui/cutscenemodaloptionsmarkers.c | 112 --- .../item/ui/cutscenemodaloptionsmarkers.h | 71 -- src/dusk/rpg/cutscene/item/ui/cutsceneshake.c | 34 - src/dusk/rpg/cutscene/item/ui/cutsceneshake.h | 50 - src/dusk/rpg/cutscene/item/ui/cutscenetext.c | 37 - src/dusk/rpg/cutscene/item/ui/cutscenetext.h | 50 - .../rpg/cutscene/item/ui/cutscenetextmini.c | 58 -- .../rpg/cutscene/item/ui/cutscenetextmini.h | 53 - .../cutscene/item/ui/cutscenetextminihide.c | 35 - .../cutscene/item/ui/cutscenetextminihide.h | 48 - .../rpg/cutscene/item/ui/cutsceneuishow.c | 43 - .../rpg/cutscene/item/ui/cutsceneuishow.h | 57 -- src/dusk/rpg/entity/CMakeLists.txt | 19 - src/dusk/rpg/entity/anim/CMakeLists.txt | 13 - src/dusk/rpg/entity/anim/entityanim.c | 44 - src/dusk/rpg/entity/anim/entityanim.h | 46 - src/dusk/rpg/entity/anim/entityanimidle.c | 17 - src/dusk/rpg/entity/anim/entityanimidle.h | 18 - src/dusk/rpg/entity/anim/entityanimrun.c | 24 - src/dusk/rpg/entity/anim/entityanimrun.h | 21 - src/dusk/rpg/entity/anim/entityanimturn.c | 17 - src/dusk/rpg/entity/anim/entityanimturn.h | 21 - src/dusk/rpg/entity/anim/entityanimwalk.c | 24 - src/dusk/rpg/entity/anim/entityanimwalk.h | 22 - src/dusk/rpg/entity/entity.c | 333 ------- src/dusk/rpg/entity/entity.h | 215 ---- src/dusk/rpg/entity/entitydir.c | 51 - src/dusk/rpg/entity/entitydir.h | 54 - src/dusk/rpg/entity/entitypathstep.c | 59 -- src/dusk/rpg/entity/entitypathstep.h | 26 - src/dusk/rpg/entity/entitytype.h | 62 -- src/dusk/rpg/entity/global/CMakeLists.txt | 9 - src/dusk/rpg/entity/global/entityglobal.h | 17 - src/dusk/rpg/entity/global/entityglobaldefs.h | 44 - src/dusk/rpg/entity/global/entitygloballist.h | 33 - src/dusk/rpg/entity/interact/CMakeLists.txt | 9 - src/dusk/rpg/entity/interact/entityinteract.c | 67 -- src/dusk/rpg/entity/interact/entityinteract.h | 66 -- src/dusk/rpg/entity/item/CMakeLists.txt | 10 - src/dusk/rpg/entity/item/entityitem.c | 45 - src/dusk/rpg/entity/item/entityitem.h | 56 -- src/dusk/rpg/entity/npc/CMakeLists.txt | 13 - src/dusk/rpg/entity/npc/npc.c | 58 -- src/dusk/rpg/entity/npc/npc.h | 81 -- src/dusk/rpg/entity/npc/npcpath.c | 43 - src/dusk/rpg/entity/npc/npcpath.h | 44 - src/dusk/rpg/entity/npc/npcturn.c | 27 - src/dusk/rpg/entity/npc/npcturn.h | 36 - src/dusk/rpg/entity/npc/npcwalk.c | 52 - src/dusk/rpg/entity/npc/npcwalk.h | 54 - src/dusk/rpg/entity/player.c | 101 -- src/dusk/rpg/entity/player.h | 52 - src/dusk/rpg/item/CMakeLists.txt | 13 - src/dusk/rpg/item/backpack.c | 67 -- src/dusk/rpg/item/backpack.h | 97 -- src/dusk/rpg/item/inventory.c | 250 ----- src/dusk/rpg/item/inventory.h | 123 --- src/dusk/rpg/item/item.c | 98 -- src/dusk/rpg/item/item.h | 91 -- src/dusk/rpg/item/itemgive.c | 32 - src/dusk/rpg/item/itemgive.h | 20 - src/dusk/rpg/overworld/CMakeLists.txt | 18 - src/dusk/rpg/overworld/chunk.c | 16 - src/dusk/rpg/overworld/chunk.h | 57 -- src/dusk/rpg/overworld/global/CMakeLists.txt | 9 - src/dusk/rpg/overworld/global/mapareaglobal.h | 17 - .../rpg/overworld/global/mapareaglobaldefs.h | 17 - .../rpg/overworld/global/mapareagloballist.h | 22 - src/dusk/rpg/overworld/map.c | 579 ----------- src/dusk/rpg/overworld/map.h | 214 ---- src/dusk/rpg/overworld/maparea.c | 168 ---- src/dusk/rpg/overworld/maparea.h | 187 ---- src/dusk/rpg/overworld/tile.c | 8 - src/dusk/rpg/overworld/tile.h | 20 - src/dusk/rpg/overworld/tileshape.c | 39 - src/dusk/rpg/overworld/tileshape.h | 45 - src/dusk/rpg/overworld/worldpos.c | 121 --- src/dusk/rpg/overworld/worldpos.h | 132 --- src/dusk/rpg/rpg.c | 93 -- src/dusk/rpg/rpg.h | 34 - src/dusk/rpg/rpgcamera.c | 134 --- src/dusk/rpg/rpgcamera.h | 94 -- src/dusk/rpg/story/CMakeLists.txt | 10 - src/dusk/rpg/story/story.c | 87 -- src/dusk/rpg/story/story.h | 76 -- src/dusk/save/save.c | 3 +- src/dusk/save/save.h | 2 +- src/dusk/save/slot/saveslot.c | 33 +- src/dusk/save/slot/saveslot.h | 36 +- src/dusk/save/slot/saveslotcurrent.c | 27 - src/dusk/save/slot/saveslotver1.c | 61 -- src/dusk/save/slot/saveslotver1.h | 36 +- src/dusk/scene/CMakeLists.txt | 5 +- src/dusk/scene/battle/CMakeLists.txt | 9 - src/dusk/scene/battle/scenebattle.c | 163 ---- src/dusk/scene/battle/scenebattle.h | 110 --- src/dusk/scene/initial/CMakeLists.txt | 9 - src/dusk/scene/initial/sceneinitial.c | 39 - src/dusk/scene/initial/sceneinitial.h | 50 - src/dusk/scene/node.c | 15 + src/dusk/scene/node.h | 24 + src/dusk/scene/overworld/CMakeLists.txt | 9 - src/dusk/scene/overworld/sceneoverworld.c | 264 ----- src/dusk/scene/overworld/sceneoverworld.h | 149 --- src/dusk/scene/scene.c | 2 - src/dusk/scene/scenetype.c | 23 +- src/dusk/scene/scenetype.h | 11 +- src/dusk/ui/CMakeLists.txt | 22 - src/dusk/ui/debug/CMakeLists.txt | 12 - src/dusk/ui/debug/uiconsole.c | 41 - src/dusk/ui/debug/uiconsole.h | 33 - src/dusk/ui/debug/uicutscene.c | 73 -- src/dusk/ui/debug/uicutscene.h | 42 - src/dusk/ui/debug/uifps.c | 86 -- src/dusk/ui/debug/uifps.h | 45 - src/dusk/ui/debug/uiplayerpos.c | 64 -- src/dusk/ui/debug/uiplayerpos.h | 37 - src/dusk/ui/dialog/CMakeLists.txt | 12 - src/dusk/ui/dialog/keyboard/CMakeLists.txt | 9 - src/dusk/ui/dialog/keyboard/uikeyboard.c | 610 ------------ src/dusk/ui/dialog/keyboard/uikeyboard.h | 490 ---------- src/dusk/ui/dialog/save/CMakeLists.txt | 10 - src/dusk/ui/dialog/save/uisaveslot.c | 119 --- src/dusk/ui/dialog/save/uisaveslot.h | 132 --- src/dusk/ui/dialog/save/uiselectsave.c | 416 -------- src/dusk/ui/dialog/save/uiselectsave.h | 134 --- src/dusk/ui/dialog/uiconfirm.c | 64 -- src/dusk/ui/dialog/uiconfirm.h | 77 -- src/dusk/ui/focus/CMakeLists.txt | 9 - src/dusk/ui/focus/uifocus.c | 204 ---- src/dusk/ui/focus/uifocus.h | 133 --- src/dusk/ui/focus/uifocusitem.h | 71 -- src/dusk/ui/overlay/CMakeLists.txt | 12 - src/dusk/ui/overlay/uicrop.c | 89 -- src/dusk/ui/overlay/uicrop.h | 32 - src/dusk/ui/overlay/uifatalerror.c | 115 --- src/dusk/ui/overlay/uifatalerror.h | 80 -- src/dusk/ui/overlay/uifullbox.c | 118 --- src/dusk/ui/overlay/uifullbox.h | 126 --- src/dusk/ui/overlay/uiloading.c | 105 -- src/dusk/ui/overlay/uiloading.h | 89 -- src/dusk/ui/rpg/CMakeLists.txt | 12 - src/dusk/ui/rpg/textbox/CMakeLists.txt | 12 - src/dusk/ui/rpg/textbox/uitextbox.c | 244 ----- src/dusk/ui/rpg/textbox/uitextbox.h | 138 --- src/dusk/ui/rpg/textbox/uitextboxmain.c | 122 --- src/dusk/ui/rpg/textbox/uitextboxmain.h | 94 -- src/dusk/ui/rpg/textbox/uitextboxmini.c | 122 --- src/dusk/ui/rpg/textbox/uitextboxmini.h | 116 --- src/dusk/ui/rpg/textbox/uitextboxminilist.c | 40 - src/dusk/ui/rpg/textbox/uitextboxminilist.h | 44 - src/dusk/ui/rpg/uiemoji.c | 119 --- src/dusk/ui/rpg/uiemoji.h | 66 -- src/dusk/ui/screen/CMakeLists.txt | 9 - src/dusk/ui/screen/backpack/CMakeLists.txt | 9 - src/dusk/ui/screen/backpack/uibackpack.c | 126 --- src/dusk/ui/screen/backpack/uibackpack.h | 67 -- src/dusk/ui/screen/battle/CMakeLists.txt | 10 - src/dusk/ui/screen/battle/uibattlehud.c | 88 -- src/dusk/ui/screen/battle/uibattlehud.h | 33 - src/dusk/ui/screen/battle/uibattlemenu.c | 178 ---- src/dusk/ui/screen/battle/uibattlemenu.h | 63 -- src/dusk/ui/screen/game/CMakeLists.txt | 9 - src/dusk/ui/screen/game/uigamemenu.c | 152 --- src/dusk/ui/screen/game/uigamemenu.h | 71 -- src/dusk/ui/screen/mainmenu/CMakeLists.txt | 9 - src/dusk/ui/screen/mainmenu/uimainmenu.c | 200 ---- src/dusk/ui/screen/mainmenu/uimainmenu.h | 97 -- src/dusk/ui/transition/CMakeLists.txt | 10 - src/dusk/ui/transition/uitransition.c | 89 -- src/dusk/ui/transition/uitransition.h | 74 -- src/dusk/ui/transition/uitransitiondata.h | 23 - src/dusk/ui/transition/uitransitionfade.c | 56 -- src/dusk/ui/transition/uitransitionfade.h | 29 - src/dusk/ui/ui.c | 76 -- src/dusk/ui/ui.h | 49 - src/dusk/ui/uielement.c | 53 - src/dusk/ui/uielement.h | 68 -- src/dusk/ui/uielementlist.c | 166 ---- src/dusk/ui/uielementlist.h | 11 - src/dusk/ui/widget/CMakeLists.txt | 20 - src/dusk/ui/widget/uibutton.c | 58 -- src/dusk/ui/widget/uibutton.h | 79 -- src/dusk/ui/widget/uicheckbox.c | 59 -- src/dusk/ui/widget/uicheckbox.h | 79 -- src/dusk/ui/widget/uidropdown.c | 100 -- src/dusk/ui/widget/uidropdown.h | 113 --- src/dusk/ui/widget/uiframe.c | 121 --- src/dusk/ui/widget/uiframe.h | 64 -- src/dusk/ui/widget/uiitem.c | 77 -- src/dusk/ui/widget/uiitem.h | 89 -- src/dusk/ui/widget/uiitemlist.c | 179 ---- src/dusk/ui/widget/uiitemlist.h | 200 ---- src/dusk/ui/widget/uilabel.c | 121 --- src/dusk/ui/widget/uilabel.h | 130 --- src/dusk/ui/widget/uimenu.c | 444 --------- src/dusk/ui/widget/uimenu.h | 386 -------- src/dusk/ui/widget/uimodal.c | 264 ----- src/dusk/ui/widget/uimodal.h | 176 ---- src/dusk/ui/widget/uiscrolling.c | 34 - src/dusk/ui/widget/uiscrolling.h | 37 - src/dusk/ui/widget/uislider.c | 244 ----- src/dusk/ui/widget/uislider.h | 185 ---- src/dusk/ui/widget/uitab.c | 62 -- src/dusk/ui/widget/uitab.h | 53 - test/CMakeLists.txt | 2 - test/animation/CMakeLists.txt | 2 - test/animation/test_animation.c | 443 --------- test/animation/test_keyframe.c | 188 ---- test/item/CMakeLists.txt | 9 - test/item/test_inventory.c | 433 --------- test/rpg/CMakeLists.txt | 15 - test/rpg/battle/CMakeLists.txt | 9 - test/rpg/battle/test_battle.c | 271 ------ test/rpg/cutscene/CMakeLists.txt | 12 - test/rpg/cutscene/test_cutscene.c | 84 -- test/rpg/cutscene/test_cutscenecontrol.c | 173 ---- test/rpg/cutscene/test_cutscenemaparea.c | 175 ---- test/rpg/cutscene/test_cutscenesystem.c | 583 ----------- test/rpg/entity/CMakeLists.txt | 19 - test/rpg/entity/entitytestfixture.c | 31 - test/rpg/entity/entitytestfixture.h | 22 - test/rpg/entity/test_entity.c | 300 ------ test/rpg/entity/test_entityinteract.c | 135 --- test/rpg/entity/test_entityitem.c | 85 -- test/rpg/entity/test_npc.c | 155 --- test/rpg/overworld/CMakeLists.txt | 11 - test/rpg/overworld/test_maparea.c | 217 ----- test/rpg/test_rpg.c | 16 - test/save/savetestfixture.c | 51 - test/save/savetestfixture.h | 17 - test/save/test_saveslot.c | 40 +- 464 files changed, 70 insertions(+), 33009 deletions(-) delete mode 100644 editor/client/public/common/chunkterrain.js delete mode 100644 editor/client/public/common/dmf.js delete mode 100644 editor/client/public/index.html delete mode 100644 editor/client/public/map/index.html delete mode 100644 editor/client/public/map/map.css delete mode 100644 editor/client/public/map/map.js delete mode 100644 editor/client/public/map/renderer.js delete mode 100644 editor/client/public/template.html delete mode 100644 editor/server/server.py delete mode 100755 editor/start.sh rename scripts/{ => gamecube}/build-gamecube-docker.sh (78%) rename scripts/{ => gamecube}/build-gamecube-iso-docker.sh (76%) rename scripts/{ => gamecube}/build-gamecube-iso.sh (100%) rename scripts/{ => gamecube}/build-gamecube.sh (100%) rename scripts/{ => knulli}/build-knulli-docker.sh (80%) rename scripts/{ => knulli}/build-knulli.sh (100%) rename scripts/{ => linux}/build-linux-docker.sh (81%) rename scripts/{ => linux}/build-linux.sh (100%) rename scripts/{ => linux}/test-linux-docker.sh (75%) rename scripts/{ => linux}/test-linux.sh (100%) rename scripts/{ => psp}/build-psp-docker.sh (84%) rename scripts/{ => psp}/build-psp.sh (100%) rename scripts/{ => vita}/build-vita-docker.sh (82%) rename scripts/{ => vita}/build-vita.sh (100%) rename scripts/{ => wii}/build-wii-docker.sh (83%) rename scripts/{ => wii}/build-wii-iso-docker.sh (80%) rename scripts/{ => wii}/build-wii-iso.sh (100%) rename scripts/{ => wii}/build-wii.sh (100%) delete mode 100644 src/dusk/animation/animation.c delete mode 100644 src/dusk/animation/animation.h delete mode 100644 src/dusk/animation/keyframe.c delete mode 100644 src/dusk/animation/keyframe.h delete mode 100644 src/dusk/asset/loader/chunk/CMakeLists.txt delete mode 100644 src/dusk/asset/loader/chunk/assetchunkloader.c delete mode 100644 src/dusk/asset/loader/chunk/assetchunkloader.h delete mode 100644 src/dusk/rpg/CMakeLists.txt delete mode 100644 src/dusk/rpg/battle/CMakeLists.txt delete mode 100644 src/dusk/rpg/battle/battle.c delete mode 100644 src/dusk/rpg/battle/battle.h delete mode 100644 src/dusk/rpg/battle/fighter/CMakeLists.txt delete mode 100644 src/dusk/rpg/battle/fighter/battlefighter.c delete mode 100644 src/dusk/rpg/battle/fighter/battlefighter.h delete mode 100644 src/dusk/rpg/battle/fighter/battlefightercontroller.h delete mode 100644 src/dusk/rpg/battle/fighter/battlefighterpos.h delete mode 100644 src/dusk/rpg/battle/fighter/battlefighterspecies.h delete mode 100644 src/dusk/rpg/battle/fighter/battlefighterstats.h delete mode 100644 src/dusk/rpg/battle/fighter/battlefighterstatus.h delete mode 100644 src/dusk/rpg/battle/fighter/battlefighterteam.h delete mode 100644 src/dusk/rpg/battle/move/CMakeLists.txt delete mode 100644 src/dusk/rpg/battle/move/battlemove.c delete mode 100644 src/dusk/rpg/battle/move/battlemove.h delete mode 100644 src/dusk/rpg/battle/move/battlemoveaction.h delete mode 100644 src/dusk/rpg/battle/move/battletarget.h delete mode 100644 src/dusk/rpg/battle/party.c delete mode 100644 src/dusk/rpg/battle/party.h delete mode 100644 src/dusk/rpg/battle/state/CMakeLists.txt delete mode 100644 src/dusk/rpg/battle/state/battlestate.c delete mode 100644 src/dusk/rpg/battle/state/battlestate.h delete mode 100644 src/dusk/rpg/battle/state/battlestateending.c delete mode 100644 src/dusk/rpg/battle/state/battlestateending.h delete mode 100644 src/dusk/rpg/battle/state/battlestateexecuting.c delete mode 100644 src/dusk/rpg/battle/state/battlestateexecuting.h delete mode 100644 src/dusk/rpg/battle/state/battlestatefled.c delete mode 100644 src/dusk/rpg/battle/state/battlestatefled.h delete mode 100644 src/dusk/rpg/battle/state/battlestatelost.c delete mode 100644 src/dusk/rpg/battle/state/battlestatelost.h delete mode 100644 src/dusk/rpg/battle/state/battlestateopening.c delete mode 100644 src/dusk/rpg/battle/state/battlestateopening.h delete mode 100644 src/dusk/rpg/battle/state/battlestatepostround.c delete mode 100644 src/dusk/rpg/battle/state/battlestatepostround.h delete mode 100644 src/dusk/rpg/battle/state/battlestatepreround.c delete mode 100644 src/dusk/rpg/battle/state/battlestatepreround.h delete mode 100644 src/dusk/rpg/battle/state/battlestateselection.c delete mode 100644 src/dusk/rpg/battle/state/battlestateselection.h delete mode 100644 src/dusk/rpg/battle/state/battlestatewon.c delete mode 100644 src/dusk/rpg/battle/state/battlestatewon.h delete mode 100644 src/dusk/rpg/cutscene/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/cutscene.c delete mode 100644 src/dusk/rpg/cutscene/cutscene.h delete mode 100644 src/dusk/rpg/cutscene/cutscenepause.h delete mode 100644 src/dusk/rpg/cutscene/cutsceneref.c delete mode 100644 src/dusk/rpg/cutscene/cutsceneref.h delete mode 100644 src/dusk/rpg/cutscene/cutscenesystem.c delete mode 100644 src/dusk/rpg/cutscene/cutscenesystem.h delete mode 100755 src/dusk/rpg/cutscene/item/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/audio/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.c delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiofade.h delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.c delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiofadewait.h delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.c delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiopause.h delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.c delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudioplay.h delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.c delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudioresume.h delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.c delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudioset.h delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.c delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetloop.h delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.c delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiosetpan.h delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.c delete mode 100644 src/dusk/rpg/cutscene/item/audio/cutsceneaudiostop.h delete mode 100644 src/dusk/rpg/cutscene/item/battle/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.c delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterdamage.h delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.c delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlefighterheal.h delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.c delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlepostmove.h delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlesetstate.c delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlesetstate.h delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlestart.c delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlestart.h delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitselection.c delete mode 100644 src/dusk/rpg/cutscene/item/battle/cutscenebattlewaitselection.h delete mode 100644 src/dusk/rpg/cutscene/item/control/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.c delete mode 100644 src/dusk/rpg/cutscene/item/control/cutsceneconcurrent.h delete mode 100644 src/dusk/rpg/cutscene/item/control/cutsceneidle.c delete mode 100644 src/dusk/rpg/cutscene/item/control/cutsceneidle.h delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenemarker.c delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenemarker.h delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenequitgame.c delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenequitgame.h delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenerestart.c delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenerestart.h delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenescene.c delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenescene.h delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenesetpause.c delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenesetpause.h delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenewait.c delete mode 100644 src/dusk/rpg/cutscene/item/control/cutscenewait.h delete mode 100644 src/dusk/rpg/cutscene/item/cutscenecallback.c delete mode 100644 src/dusk/rpg/cutscene/item/cutscenecallback.h delete mode 100644 src/dusk/rpg/cutscene/item/cutsceneitem.c delete mode 100644 src/dusk/rpg/cutscene/item/cutsceneitem.h delete mode 100644 src/dusk/rpg/cutscene/item/cutsceneitembase.c delete mode 100644 src/dusk/rpg/cutscene/item/cutsceneitembase.h delete mode 100644 src/dusk/rpg/cutscene/item/cutsceneprint.c delete mode 100644 src/dusk/rpg/cutscene/item/cutsceneprint.h delete mode 100644 src/dusk/rpg/cutscene/item/entity/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.c delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentityadd.h delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.c delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentityremove.h delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.c delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentityteleport.h delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.c delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentityturn.h delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.c delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentitywalkto.h delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.c delete mode 100644 src/dusk/rpg/cutscene/item/entity/cutsceneentitywalktoentity.h delete mode 100644 src/dusk/rpg/cutscene/item/item/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/item/cutsceneitemgive.c delete mode 100644 src/dusk/rpg/cutscene/item/item/cutsceneitemgive.h delete mode 100644 src/dusk/rpg/cutscene/item/json/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonareaid.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonaudiochannel.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonbattlestate.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsoneasing.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonemoji.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonentitydir.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonentityindex.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonentitytype.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonenum.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonenum.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonfield.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonfield.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonpauseflags.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonscenetype.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonstoryflag.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonstoryflag.h delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.c delete mode 100644 src/dusk/rpg/cutscene/item/json/cutscenejsonuiscreentype.h delete mode 100644 src/dusk/rpg/cutscene/item/maparea/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.c delete mode 100644 src/dusk/rpg/cutscene/item/maparea/cutscenemapareaadd.h delete mode 100644 src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.c delete mode 100644 src/dusk/rpg/cutscene/item/maparea/cutscenemaparearemove.h delete mode 100644 src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.c delete mode 100644 src/dusk/rpg/cutscene/item/maparea/cutscenemapareawait.h delete mode 100644 src/dusk/rpg/cutscene/item/party/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/party/cutscenepartyaddmember.c delete mode 100644 src/dusk/rpg/cutscene/item/party/cutscenepartyaddmember.h delete mode 100644 src/dusk/rpg/cutscene/item/party/cutscenepartyhealall.c delete mode 100644 src/dusk/rpg/cutscene/item/party/cutscenepartyhealall.h delete mode 100644 src/dusk/rpg/cutscene/item/party/cutscenepartyhealmember.c delete mode 100644 src/dusk/rpg/cutscene/item/party/cutscenepartyhealmember.h delete mode 100644 src/dusk/rpg/cutscene/item/party/cutscenepartyremovemember.c delete mode 100644 src/dusk/rpg/cutscene/item/party/cutscenepartyremovemember.h delete mode 100644 src/dusk/rpg/cutscene/item/save/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.c delete mode 100644 src/dusk/rpg/cutscene/item/save/cutscenesavedevicecheck.h delete mode 100644 src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.c delete mode 100644 src/dusk/rpg/cutscene/item/save/cutscenesaveloadallslots.h delete mode 100644 src/dusk/rpg/cutscene/item/story/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/story/cutscenestorysetflag.c delete mode 100644 src/dusk/rpg/cutscene/item/story/cutscenestorysetflag.h delete mode 100644 src/dusk/rpg/cutscene/item/story/cutscenestorywaitflag.c delete mode 100644 src/dusk/rpg/cutscene/item/story/cutscenestorywaitflag.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/CMakeLists.txt delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutsceneemoji.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutsceneemoji.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenefade.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenefade.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenekeyboard.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenemodal.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenemodal.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenemodaloptionsmarkers.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutsceneshake.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutsceneshake.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenetext.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenetext.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenetextmini.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenetextmini.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutscenetextminihide.h delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutsceneuishow.c delete mode 100644 src/dusk/rpg/cutscene/item/ui/cutsceneuishow.h delete mode 100644 src/dusk/rpg/entity/CMakeLists.txt delete mode 100644 src/dusk/rpg/entity/anim/CMakeLists.txt delete mode 100644 src/dusk/rpg/entity/anim/entityanim.c delete mode 100644 src/dusk/rpg/entity/anim/entityanim.h delete mode 100644 src/dusk/rpg/entity/anim/entityanimidle.c delete mode 100644 src/dusk/rpg/entity/anim/entityanimidle.h delete mode 100644 src/dusk/rpg/entity/anim/entityanimrun.c delete mode 100644 src/dusk/rpg/entity/anim/entityanimrun.h delete mode 100644 src/dusk/rpg/entity/anim/entityanimturn.c delete mode 100644 src/dusk/rpg/entity/anim/entityanimturn.h delete mode 100644 src/dusk/rpg/entity/anim/entityanimwalk.c delete mode 100644 src/dusk/rpg/entity/anim/entityanimwalk.h delete mode 100644 src/dusk/rpg/entity/entity.c delete mode 100644 src/dusk/rpg/entity/entity.h delete mode 100644 src/dusk/rpg/entity/entitydir.c delete mode 100644 src/dusk/rpg/entity/entitydir.h delete mode 100644 src/dusk/rpg/entity/entitypathstep.c delete mode 100644 src/dusk/rpg/entity/entitypathstep.h delete mode 100644 src/dusk/rpg/entity/entitytype.h delete mode 100644 src/dusk/rpg/entity/global/CMakeLists.txt delete mode 100644 src/dusk/rpg/entity/global/entityglobal.h delete mode 100644 src/dusk/rpg/entity/global/entityglobaldefs.h delete mode 100644 src/dusk/rpg/entity/global/entitygloballist.h delete mode 100644 src/dusk/rpg/entity/interact/CMakeLists.txt delete mode 100644 src/dusk/rpg/entity/interact/entityinteract.c delete mode 100644 src/dusk/rpg/entity/interact/entityinteract.h delete mode 100644 src/dusk/rpg/entity/item/CMakeLists.txt delete mode 100644 src/dusk/rpg/entity/item/entityitem.c delete mode 100644 src/dusk/rpg/entity/item/entityitem.h delete mode 100644 src/dusk/rpg/entity/npc/CMakeLists.txt delete mode 100644 src/dusk/rpg/entity/npc/npc.c delete mode 100644 src/dusk/rpg/entity/npc/npc.h delete mode 100644 src/dusk/rpg/entity/npc/npcpath.c delete mode 100644 src/dusk/rpg/entity/npc/npcpath.h delete mode 100644 src/dusk/rpg/entity/npc/npcturn.c delete mode 100644 src/dusk/rpg/entity/npc/npcturn.h delete mode 100644 src/dusk/rpg/entity/npc/npcwalk.c delete mode 100644 src/dusk/rpg/entity/npc/npcwalk.h delete mode 100644 src/dusk/rpg/entity/player.c delete mode 100644 src/dusk/rpg/entity/player.h delete mode 100644 src/dusk/rpg/item/CMakeLists.txt delete mode 100644 src/dusk/rpg/item/backpack.c delete mode 100644 src/dusk/rpg/item/backpack.h delete mode 100644 src/dusk/rpg/item/inventory.c delete mode 100644 src/dusk/rpg/item/inventory.h delete mode 100644 src/dusk/rpg/item/item.c delete mode 100644 src/dusk/rpg/item/item.h delete mode 100644 src/dusk/rpg/item/itemgive.c delete mode 100644 src/dusk/rpg/item/itemgive.h delete mode 100644 src/dusk/rpg/overworld/CMakeLists.txt delete mode 100644 src/dusk/rpg/overworld/chunk.c delete mode 100644 src/dusk/rpg/overworld/chunk.h delete mode 100644 src/dusk/rpg/overworld/global/CMakeLists.txt delete mode 100644 src/dusk/rpg/overworld/global/mapareaglobal.h delete mode 100644 src/dusk/rpg/overworld/global/mapareaglobaldefs.h delete mode 100644 src/dusk/rpg/overworld/global/mapareagloballist.h delete mode 100644 src/dusk/rpg/overworld/map.c delete mode 100644 src/dusk/rpg/overworld/map.h delete mode 100644 src/dusk/rpg/overworld/maparea.c delete mode 100644 src/dusk/rpg/overworld/maparea.h delete mode 100644 src/dusk/rpg/overworld/tile.c delete mode 100644 src/dusk/rpg/overworld/tile.h delete mode 100644 src/dusk/rpg/overworld/tileshape.c delete mode 100644 src/dusk/rpg/overworld/tileshape.h delete mode 100644 src/dusk/rpg/overworld/worldpos.c delete mode 100644 src/dusk/rpg/overworld/worldpos.h delete mode 100644 src/dusk/rpg/rpg.c delete mode 100644 src/dusk/rpg/rpg.h delete mode 100644 src/dusk/rpg/rpgcamera.c delete mode 100644 src/dusk/rpg/rpgcamera.h delete mode 100644 src/dusk/rpg/story/CMakeLists.txt delete mode 100644 src/dusk/rpg/story/story.c delete mode 100644 src/dusk/rpg/story/story.h delete mode 100644 src/dusk/scene/battle/CMakeLists.txt delete mode 100644 src/dusk/scene/battle/scenebattle.c delete mode 100644 src/dusk/scene/battle/scenebattle.h delete mode 100644 src/dusk/scene/initial/CMakeLists.txt delete mode 100644 src/dusk/scene/initial/sceneinitial.c delete mode 100644 src/dusk/scene/initial/sceneinitial.h create mode 100644 src/dusk/scene/node.c create mode 100644 src/dusk/scene/node.h delete mode 100644 src/dusk/scene/overworld/CMakeLists.txt delete mode 100644 src/dusk/scene/overworld/sceneoverworld.c delete mode 100644 src/dusk/scene/overworld/sceneoverworld.h delete mode 100644 src/dusk/ui/CMakeLists.txt delete mode 100644 src/dusk/ui/debug/CMakeLists.txt delete mode 100644 src/dusk/ui/debug/uiconsole.c delete mode 100644 src/dusk/ui/debug/uiconsole.h delete mode 100644 src/dusk/ui/debug/uicutscene.c delete mode 100644 src/dusk/ui/debug/uicutscene.h delete mode 100644 src/dusk/ui/debug/uifps.c delete mode 100644 src/dusk/ui/debug/uifps.h delete mode 100644 src/dusk/ui/debug/uiplayerpos.c delete mode 100644 src/dusk/ui/debug/uiplayerpos.h delete mode 100644 src/dusk/ui/dialog/CMakeLists.txt delete mode 100644 src/dusk/ui/dialog/keyboard/CMakeLists.txt delete mode 100644 src/dusk/ui/dialog/keyboard/uikeyboard.c delete mode 100644 src/dusk/ui/dialog/keyboard/uikeyboard.h delete mode 100644 src/dusk/ui/dialog/save/CMakeLists.txt delete mode 100644 src/dusk/ui/dialog/save/uisaveslot.c delete mode 100644 src/dusk/ui/dialog/save/uisaveslot.h delete mode 100644 src/dusk/ui/dialog/save/uiselectsave.c delete mode 100644 src/dusk/ui/dialog/save/uiselectsave.h delete mode 100644 src/dusk/ui/dialog/uiconfirm.c delete mode 100644 src/dusk/ui/dialog/uiconfirm.h delete mode 100644 src/dusk/ui/focus/CMakeLists.txt delete mode 100644 src/dusk/ui/focus/uifocus.c delete mode 100644 src/dusk/ui/focus/uifocus.h delete mode 100644 src/dusk/ui/focus/uifocusitem.h delete mode 100644 src/dusk/ui/overlay/CMakeLists.txt delete mode 100644 src/dusk/ui/overlay/uicrop.c delete mode 100644 src/dusk/ui/overlay/uicrop.h delete mode 100644 src/dusk/ui/overlay/uifatalerror.c delete mode 100644 src/dusk/ui/overlay/uifatalerror.h delete mode 100644 src/dusk/ui/overlay/uifullbox.c delete mode 100644 src/dusk/ui/overlay/uifullbox.h delete mode 100644 src/dusk/ui/overlay/uiloading.c delete mode 100644 src/dusk/ui/overlay/uiloading.h delete mode 100644 src/dusk/ui/rpg/CMakeLists.txt delete mode 100644 src/dusk/ui/rpg/textbox/CMakeLists.txt delete mode 100644 src/dusk/ui/rpg/textbox/uitextbox.c delete mode 100644 src/dusk/ui/rpg/textbox/uitextbox.h delete mode 100644 src/dusk/ui/rpg/textbox/uitextboxmain.c delete mode 100644 src/dusk/ui/rpg/textbox/uitextboxmain.h delete mode 100644 src/dusk/ui/rpg/textbox/uitextboxmini.c delete mode 100644 src/dusk/ui/rpg/textbox/uitextboxmini.h delete mode 100644 src/dusk/ui/rpg/textbox/uitextboxminilist.c delete mode 100644 src/dusk/ui/rpg/textbox/uitextboxminilist.h delete mode 100644 src/dusk/ui/rpg/uiemoji.c delete mode 100644 src/dusk/ui/rpg/uiemoji.h delete mode 100644 src/dusk/ui/screen/CMakeLists.txt delete mode 100644 src/dusk/ui/screen/backpack/CMakeLists.txt delete mode 100644 src/dusk/ui/screen/backpack/uibackpack.c delete mode 100644 src/dusk/ui/screen/backpack/uibackpack.h delete mode 100644 src/dusk/ui/screen/battle/CMakeLists.txt delete mode 100644 src/dusk/ui/screen/battle/uibattlehud.c delete mode 100644 src/dusk/ui/screen/battle/uibattlehud.h delete mode 100644 src/dusk/ui/screen/battle/uibattlemenu.c delete mode 100644 src/dusk/ui/screen/battle/uibattlemenu.h delete mode 100644 src/dusk/ui/screen/game/CMakeLists.txt delete mode 100644 src/dusk/ui/screen/game/uigamemenu.c delete mode 100644 src/dusk/ui/screen/game/uigamemenu.h delete mode 100644 src/dusk/ui/screen/mainmenu/CMakeLists.txt delete mode 100644 src/dusk/ui/screen/mainmenu/uimainmenu.c delete mode 100644 src/dusk/ui/screen/mainmenu/uimainmenu.h delete mode 100644 src/dusk/ui/transition/CMakeLists.txt delete mode 100644 src/dusk/ui/transition/uitransition.c delete mode 100644 src/dusk/ui/transition/uitransition.h delete mode 100644 src/dusk/ui/transition/uitransitiondata.h delete mode 100644 src/dusk/ui/transition/uitransitionfade.c delete mode 100644 src/dusk/ui/transition/uitransitionfade.h delete mode 100644 src/dusk/ui/ui.c delete mode 100644 src/dusk/ui/ui.h delete mode 100644 src/dusk/ui/uielement.c delete mode 100644 src/dusk/ui/uielement.h delete mode 100644 src/dusk/ui/uielementlist.c delete mode 100644 src/dusk/ui/uielementlist.h delete mode 100644 src/dusk/ui/widget/CMakeLists.txt delete mode 100644 src/dusk/ui/widget/uibutton.c delete mode 100644 src/dusk/ui/widget/uibutton.h delete mode 100644 src/dusk/ui/widget/uicheckbox.c delete mode 100644 src/dusk/ui/widget/uicheckbox.h delete mode 100644 src/dusk/ui/widget/uidropdown.c delete mode 100644 src/dusk/ui/widget/uidropdown.h delete mode 100644 src/dusk/ui/widget/uiframe.c delete mode 100644 src/dusk/ui/widget/uiframe.h delete mode 100644 src/dusk/ui/widget/uiitem.c delete mode 100644 src/dusk/ui/widget/uiitem.h delete mode 100644 src/dusk/ui/widget/uiitemlist.c delete mode 100644 src/dusk/ui/widget/uiitemlist.h delete mode 100644 src/dusk/ui/widget/uilabel.c delete mode 100644 src/dusk/ui/widget/uilabel.h delete mode 100644 src/dusk/ui/widget/uimenu.c delete mode 100644 src/dusk/ui/widget/uimenu.h delete mode 100644 src/dusk/ui/widget/uimodal.c delete mode 100644 src/dusk/ui/widget/uimodal.h delete mode 100644 src/dusk/ui/widget/uiscrolling.c delete mode 100644 src/dusk/ui/widget/uiscrolling.h delete mode 100644 src/dusk/ui/widget/uislider.c delete mode 100644 src/dusk/ui/widget/uislider.h delete mode 100644 src/dusk/ui/widget/uitab.c delete mode 100644 src/dusk/ui/widget/uitab.h delete mode 100644 test/animation/test_animation.c delete mode 100644 test/animation/test_keyframe.c delete mode 100644 test/item/CMakeLists.txt delete mode 100644 test/item/test_inventory.c delete mode 100644 test/rpg/CMakeLists.txt delete mode 100644 test/rpg/battle/CMakeLists.txt delete mode 100644 test/rpg/battle/test_battle.c delete mode 100644 test/rpg/cutscene/CMakeLists.txt delete mode 100644 test/rpg/cutscene/test_cutscene.c delete mode 100644 test/rpg/cutscene/test_cutscenecontrol.c delete mode 100644 test/rpg/cutscene/test_cutscenemaparea.c delete mode 100644 test/rpg/cutscene/test_cutscenesystem.c delete mode 100644 test/rpg/entity/CMakeLists.txt delete mode 100644 test/rpg/entity/entitytestfixture.c delete mode 100644 test/rpg/entity/entitytestfixture.h delete mode 100644 test/rpg/entity/test_entity.c delete mode 100644 test/rpg/entity/test_entityinteract.c delete mode 100644 test/rpg/entity/test_entityitem.c delete mode 100644 test/rpg/entity/test_npc.c delete mode 100644 test/rpg/overworld/CMakeLists.txt delete mode 100644 test/rpg/overworld/test_maparea.c delete mode 100644 test/rpg/test_rpg.c diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e39129b4..56390d44 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: git \ libssl-dev - name: Build Linux - run: ./scripts/build-linux.sh + run: ./scripts/linux/build-linux.sh - name: Upload Linux binary uses: actions/upload-artifact@v3 with: @@ -48,7 +48,7 @@ jobs: - name: Setup pspdev uses: ./.github/actions/setup-pspdev - name: Build PSP - run: ./scripts/build-psp.sh + run: ./scripts/psp/build-psp.sh - name: Move EBOOT.PBP to Dusk subfolder run: | mkdir -p ./git-artifcats/Dusk/PSP/GAME/Dusk @@ -103,7 +103,7 @@ jobs: libgles2:arm64 \ libgl1-mesa-dev:arm64 - name: Build Knulli - run: ./scripts/build-knulli.sh + run: ./scripts/knulli/build-knulli.sh - name: Move output to Dusk subfolder run: | mkdir -p ./git-artifcats/Dusk @@ -134,7 +134,7 @@ jobs: gamecube-sdl2 ppc-liblzma ppc-libzip \ gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd - name: Build GameCube - run: ./scripts/build-gamecube.sh + run: ./scripts/gamecube/build-gamecube.sh - name: Copy output files run: | mkdir -p ./git-artifcats/Dusk @@ -166,7 +166,7 @@ jobs: gamecube-sdl2 ppc-liblzma ppc-libzip \ gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd - name: Build GameCube ISO - run: ./scripts/build-gamecube-iso.sh + run: ./scripts/gamecube/build-gamecube-iso.sh - name: Copy output files run: | mkdir -p ./git-artifcats/Dusk @@ -199,7 +199,7 @@ jobs: gamecube-sdl2 ppc-liblzma ppc-libzip \ gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd - name: Build Wii - run: ./scripts/build-wii.sh + run: ./scripts/wii/build-wii.sh - name: Copy output files run: | mkdir -p ./git-artifcats/Dusk/apps/Dusk @@ -232,7 +232,7 @@ jobs: gamecube-sdl2 ppc-liblzma ppc-libzip \ gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd - name: Build Wii ISO - run: ./scripts/build-wii-iso.sh + run: ./scripts/wii/build-wii-iso.sh - name: Copy output files run: | mkdir -p ./git-artifcats/Dusk diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f2cbf7af..8dea4e77 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,4 +32,4 @@ jobs: git \ libssl-dev - name: Run tests - run: ./scripts/test-linux.sh + run: ./scripts/linux/test-linux.sh diff --git a/editor/client/public/common/chunkterrain.js b/editor/client/public/common/chunkterrain.js deleted file mode 100644 index 4a19360e..00000000 --- a/editor/client/public/common/chunkterrain.js +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2026 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -"use strict"; - -// ChunkTerrain - JS port of the terrain-quad generation performed by -// tools/asset/chunk/__main__.py (build_terrain_verts / _tile_quad / -// _RAMP_CORNERS) so the editor's 3D preview can show the same geometry the -// Python compiler will bake into the chunk's terrain mesh, without waiting -// for a compile pass. Vertex layout matches DMF: interleaved [u, v, x, y, z]. - -const ChunkTerrain = (() => { - const CHUNK_WIDTH = 16; - const CHUNK_HEIGHT = 16; - const CHUNK_DEPTH = 4; - const TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH; - - // World-space Z distance of one Z-layer/story, in world-float space. - // Must match WORLD_LAYER_HEIGHT in src/dusk/rpg/overworld/worldpos.h. - const WORLD_LAYER_HEIGHT = Math.SQRT1_2; - - const TILE_SHAPE_NULL = 0; - const TILE_SHAPE_GROUND = 1; - const TILE_SHAPE_RAMP_NORTH = 2; - const TILE_SHAPE_RAMP_EAST = 3; - const TILE_SHAPE_RAMP_SOUTH = 4; - const TILE_SHAPE_RAMP_WEST = 5; - const TILE_SHAPE_RAMP_NORTHEAST = 6; - const TILE_SHAPE_RAMP_NORTHWEST = 7; - const TILE_SHAPE_RAMP_SOUTHEAST = 8; - const TILE_SHAPE_RAMP_SOUTHWEST = 9; - const TILE_SHAPE_RAMP_NORTHEAST_INNER = 10; - const TILE_SHAPE_RAMP_NORTHWEST_INNER = 11; - const TILE_SHAPE_RAMP_SOUTHEAST_INNER = 12; - const TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13; - - // Per-shape corner heights as [sw, se, ne, nw] offsets from tile base Z. - // NORTH=+Y, EAST=+X. Cardinal ramps: low face at 0, high face at 1. - // Diagonal outer-corner ramps: only the named corner raised, rest at 0. - // Diagonal inner-corner ramps: only the corner opposite the named one is - // lowered to 0, the rest (including the named corner) stay raised at 1. - const RAMP_CORNERS = { - [TILE_SHAPE_GROUND]: [0.0, 0.0, 0.0, 0.0], - [TILE_SHAPE_RAMP_NORTH]: [0.0, 0.0, 1.0, 1.0], - [TILE_SHAPE_RAMP_SOUTH]: [1.0, 1.0, 0.0, 0.0], - [TILE_SHAPE_RAMP_EAST]: [0.0, 1.0, 1.0, 0.0], - [TILE_SHAPE_RAMP_WEST]: [1.0, 0.0, 0.0, 1.0], - [TILE_SHAPE_RAMP_NORTHEAST]: [0.0, 0.0, 1.0, 0.0], - [TILE_SHAPE_RAMP_NORTHWEST]: [0.0, 0.0, 0.0, 1.0], - [TILE_SHAPE_RAMP_SOUTHEAST]: [0.0, 1.0, 0.0, 0.0], - [TILE_SHAPE_RAMP_SOUTHWEST]: [1.0, 0.0, 0.0, 0.0], - [TILE_SHAPE_RAMP_NORTHEAST_INNER]: [0.0, 1.0, 1.0, 1.0], - [TILE_SHAPE_RAMP_NORTHWEST_INNER]: [1.0, 0.0, 1.0, 1.0], - [TILE_SHAPE_RAMP_SOUTHEAST_INNER]: [1.0, 1.0, 1.0, 0.0], - [TILE_SHAPE_RAMP_SOUTHWEST_INNER]: [1.0, 1.0, 0.0, 1.0], - }; - - function tileIndex(x, y, z) { - return x + y * CHUNK_WIDTH + z * CHUNK_WIDTH * CHUNK_HEIGHT; - } - - // Six vertices (2 CCW triangles) for a quad with per-corner Z heights. - // Corner order: SW=(fx,fy), SE=(fx+1,fy), NE=(fx+1,fy+1), NW=(fx,fy+1). - function pushTileQuad(out, u0, u1, v0, v1, fx, fy, swZ, seZ, neZ, nwZ) { - out.push( - u0, v0, fx, fy, swZ, - u1, v0, fx + 1, fy, seZ, - u1, v1, fx + 1, fy + 1, neZ, - u0, v0, fx, fy, swZ, - u1, v1, fx + 1, fy + 1, neZ, - u0, v1, fx, fy + 1, nwZ, - ); - } - - // Generate terrain quads for every non-null tile in a flat tiles array - // (length TILE_COUNT, indexed via tileIndex). Returns a Float32Array of - // interleaved [u, v, x, y, z] vertices, matching DMF.VERTEX_FLOATS. - function buildTerrainVerts(tiles) { - const out = []; - for(let z = 0; z < CHUNK_DEPTH; z++) { - for(let y = 0; y < CHUNK_HEIGHT; y++) { - for(let x = 0; x < CHUNK_WIDTH; x++) { - const type = tiles[tileIndex(x, y, z)] || TILE_SHAPE_NULL; - const corners = RAMP_CORNERS[type]; - if(!corners) continue; - - const u0 = x / CHUNK_WIDTH; - const u1 = (x + 1) / CHUNK_WIDTH; - const v0 = y / CHUNK_HEIGHT; - const v1 = (y + 1) / CHUNK_HEIGHT; - const [sw, se, ne, nw] = corners; - - pushTileQuad( - out, u0, u1, v0, v1, x, y, - (z + sw) * WORLD_LAYER_HEIGHT, - (z + se) * WORLD_LAYER_HEIGHT, - (z + ne) * WORLD_LAYER_HEIGHT, - (z + nw) * WORLD_LAYER_HEIGHT - ); - } - } - } - return new Float32Array(out); - } - - // Four edges (SW-SE, SE-NE, NE-NW, NW-SW) of a tile's quad as line - // segments, lifted slightly above the terrain surface to avoid - // z-fighting - same corner-height convention as pushTileQuad(). - function pushTileGridLines(out, fx, fy, swZ, seZ, neZ, nwZ) { - const eps = 0.01; - const sw = [fx, fy, swZ + eps]; - const se = [fx + 1, fy, seZ + eps]; - const ne = [fx + 1, fy + 1, neZ + eps]; - const nw = [fx, fy + 1, nwZ + eps]; - const edges = [sw, se, se, ne, ne, nw, nw, sw]; - for(const [x, y, z] of edges) out.push(0, 0, x, y, z); - } - - // Generate grid-outline line segments for every non-null tile, following - // the same per-corner heights as buildTerrainVerts() so lines hug ramps - // instead of cutting through them. Returns a Float32Array of interleaved - // [u, v, x, y, z] vertices meant to be drawn with gl.LINES (pairs). - function buildGridLines(tiles) { - const out = []; - for(let z = 0; z < CHUNK_DEPTH; z++) { - for(let y = 0; y < CHUNK_HEIGHT; y++) { - for(let x = 0; x < CHUNK_WIDTH; x++) { - const type = tiles[tileIndex(x, y, z)] || TILE_SHAPE_NULL; - const corners = RAMP_CORNERS[type]; - if(!corners) continue; - - const [sw, se, ne, nw] = corners; - pushTileGridLines( - out, x, y, - (z + sw) * WORLD_LAYER_HEIGHT, - (z + se) * WORLD_LAYER_HEIGHT, - (z + ne) * WORLD_LAYER_HEIGHT, - (z + nw) * WORLD_LAYER_HEIGHT - ); - } - } - } - return new Float32Array(out); - } - - return Object.freeze({ - CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_COUNT, WORLD_LAYER_HEIGHT, - TILE_SHAPE_NULL, TILE_SHAPE_GROUND, - TILE_SHAPE_RAMP_NORTH, TILE_SHAPE_RAMP_EAST, - TILE_SHAPE_RAMP_SOUTH, TILE_SHAPE_RAMP_WEST, - TILE_SHAPE_RAMP_NORTHEAST, TILE_SHAPE_RAMP_NORTHWEST, - TILE_SHAPE_RAMP_SOUTHEAST, TILE_SHAPE_RAMP_SOUTHWEST, - TILE_SHAPE_RAMP_NORTHEAST_INNER, TILE_SHAPE_RAMP_NORTHWEST_INNER, - TILE_SHAPE_RAMP_SOUTHEAST_INNER, TILE_SHAPE_RAMP_SOUTHWEST_INNER, - RAMP_CORNERS, tileIndex, buildTerrainVerts, buildGridLines, - }); -})(); diff --git a/editor/client/public/common/dmf.js b/editor/client/public/common/dmf.js deleted file mode 100644 index 2a293cb5..00000000 --- a/editor/client/public/common/dmf.js +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2026 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -"use strict"; - -// DMF - Dusk Mesh Format -// -// Header (12 bytes): -// [0-3] "DMF\0" magic -// [4-7] uint32 version (little-endian) -// [8-11] uint32 vertCount (little-endian) -// -// Followed by vertCount vertices, each 20 bytes: -// [0-7] float32[2] uv (little-endian) -// [8-19] float32[3] pos (little-endian) - -const DMF = (() => { - const MAGIC = [0x44, 0x4d, 0x46, 0x00]; // "DMF\0" - const VERSION = 1; - const HEADER_SIZE = 12; - const VERTEX_FLOATS = 5; // uv[2] + pos[3] - const VERTEX_SIZE = VERTEX_FLOATS * 4; - - // Decode a DMF ArrayBuffer into { version, vertexCount, floats }, where - // floats is a Float32Array of tightly interleaved [u, v, x, y, z] per - // vertex - ready to hand straight to gl.bufferData(). - function decode(buffer) { - const bytes = new Uint8Array(buffer); - const view = new DataView(buffer); - - if(bytes.length < HEADER_SIZE) throw new Error("File too small to be a valid DMF"); - if( - bytes[0] !== MAGIC[0] || bytes[1] !== MAGIC[1] || - bytes[2] !== MAGIC[2] || bytes[3] !== MAGIC[3] - ) { - throw new Error("Invalid DMF magic bytes - not a DMF file"); - } - - const version = view.getUint32(4, true); - if(version !== VERSION) { - throw new Error(`Unsupported DMF version: ${version}`); - } - - const vertexCount = view.getUint32(8, true); - const expected = HEADER_SIZE + vertexCount * VERTEX_SIZE; - if(bytes.length < expected) { - throw new Error(`DMF vertex data truncated (expected ${expected} bytes, got ${bytes.length})`); - } - - const floats = new Float32Array(vertexCount * VERTEX_FLOATS); - for(let i = 0; i < floats.length; i++) { - floats[i] = view.getFloat32(HEADER_SIZE + i * 4, true); - } - - return { version, vertexCount, floats }; - } - - return Object.freeze({ - decode, - VERSION, HEADER_SIZE, VERTEX_FLOATS, VERTEX_SIZE, - }); -})(); diff --git a/editor/client/public/index.html b/editor/client/public/index.html deleted file mode 100644 index 2eb5fe25..00000000 --- a/editor/client/public/index.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
-

Dusk Map Editor

-

Browse and edit project assets.

-
-
diff --git a/editor/client/public/map/index.html b/editor/client/public/map/index.html deleted file mode 100644 index 5e491709..00000000 --- a/editor/client/public/map/index.html +++ /dev/null @@ -1,92 +0,0 @@ - - -
-
-
-
- - -
-
- - -
-
- - -
- - - - -
- - - - -
-
- - -
- - -
-
 
-
- -
-
- - - - - 0 -
-
-
-
- -
- -
Meshes
-
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    - -
    -
    - -
    -

    Preview (scroll to zoom)

    - -
    -
    - - - - - diff --git a/editor/client/public/map/map.css b/editor/client/public/map/map.css deleted file mode 100644 index 54b8843d..00000000 --- a/editor/client/public/map/map.css +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -.tile-palette { - display: flex; - flex-direction: column; - flex-wrap: wrap; - gap: 6px; - max-height: 600px; -} - -.tile-swatch { - position: relative; - width: 32px; - height: 32px; - border: 2px solid transparent; - border-radius: 4px; - cursor: pointer; - padding: 0; -} - -.tile-swatch.active { - border-color: #fff; -} - -.tile-swatch-icon { - position: absolute; - top: 0; - left: 0; - pointer-events: none; -} - -.dpad { - display: grid; - grid-template-columns: repeat(3, 2rem); - grid-template-rows: repeat(3, 2rem); - grid-template-areas: - ". n ." - "w . e" - ". s ."; - gap: 2px; -} - -.dpad-n { grid-area: n; } -.dpad-w { grid-area: w; } -.dpad-e { grid-area: e; } -.dpad-s { grid-area: s; } -.dpad button { padding: 0; } - -.tile-grid { - image-rendering: pixelated; - cursor: crosshair; - touch-action: none; - max-width: 100%; - height: auto; -} - -#previewCanvas { - width: 100%; - aspect-ratio: 1 / 1; - height: auto; -} diff --git a/editor/client/public/map/map.js b/editor/client/public/map/map.js deleted file mode 100644 index 0d883c12..00000000 --- a/editor/client/public/map/map.js +++ /dev/null @@ -1,920 +0,0 @@ -// Copyright (c) 2026 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -"use strict"; - -(() => { - const SHAPES = [ - { type: ChunkTerrain.TILE_SHAPE_NULL, name: "Erase", color: "#20232a" }, - { type: ChunkTerrain.TILE_SHAPE_GROUND, name: "Ground", color: "#4caf50" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_NORTH, name: "Ramp North", color: "#2196f3" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_EAST, name: "Ramp East", color: "#03a9f4" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_SOUTH, name: "Ramp South", color: "#00bcd4" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_WEST, name: "Ramp West", color: "#009688" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_NORTHEAST, name: "Ramp NE", color: "#ff9800" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST, name: "Ramp NW", color: "#ff5722" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST, name: "Ramp SE", color: "#9c27b0" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST, name: "Ramp SW", color: "#e91e63" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_NORTHEAST_INNER, name: "Ramp NE Inner", color: "#795548" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST_INNER, name: "Ramp NW Inner", color: "#607d8b" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST_INNER, name: "Ramp SE Inner", color: "#8bc34a" }, - { type: ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER, name: "Ramp SW Inner", color: "#ffc107" }, - ]; - const SHAPE_COLORS = Object.fromEntries(SHAPES.map((s) => [s.type, s.color])); - - // Arrow direction per ramp, in canvas/screen space: north is up (dy < 0), - // east is right (dx > 0) - matches renderGrid()'s north-is-up convention. - const SHAPE_DIRECTIONS = { - [ChunkTerrain.TILE_SHAPE_RAMP_NORTH]: { dx: 0, dy: -1 }, - [ChunkTerrain.TILE_SHAPE_RAMP_SOUTH]: { dx: 0, dy: 1 }, - [ChunkTerrain.TILE_SHAPE_RAMP_EAST]: { dx: 1, dy: 0 }, - [ChunkTerrain.TILE_SHAPE_RAMP_WEST]: { dx: -1, dy: 0 }, - [ChunkTerrain.TILE_SHAPE_RAMP_NORTHEAST]: { dx: 1, dy: -1 }, - [ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST]: { dx: -1, dy: -1 }, - [ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST]: { dx: 1, dy: 1 }, - [ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST]: { dx: -1, dy: 1 }, - [ChunkTerrain.TILE_SHAPE_RAMP_NORTHEAST_INNER]: { dx: 1, dy: -1 }, - [ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST_INNER]: { dx: -1, dy: -1 }, - [ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST_INNER]: { dx: 1, dy: 1 }, - [ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER]: { dx: -1, dy: 1 }, - }; - - const INNER_CORNER_SHAPES = new Set([ - ChunkTerrain.TILE_SHAPE_RAMP_NORTHEAST_INNER, - ChunkTerrain.TILE_SHAPE_RAMP_NORTHWEST_INNER, - ChunkTerrain.TILE_SHAPE_RAMP_SOUTHEAST_INNER, - ChunkTerrain.TILE_SHAPE_RAMP_SOUTHWEST_INNER, - ]); - - // Draws a single-headed arrow from (tailX, tailY) to (tipX, tipY). - function drawArrowSegment(ctx, tailX, tailY, tipX, tipY, size) { - ctx.strokeStyle = "#ffffff"; - ctx.fillStyle = "#ffffff"; - ctx.lineWidth = Math.max(1, size * 0.08); - ctx.lineCap = "round"; - - ctx.beginPath(); - ctx.moveTo(tailX, tailY); - ctx.lineTo(tipX, tipY); - ctx.stroke(); - - const headLen = size * 0.18; - const angle = Math.atan2(tipY - tailY, tipX - tailX); - const leftAngle = angle + Math.PI * 0.8; - const rightAngle = angle - Math.PI * 0.8; - ctx.beginPath(); - ctx.moveTo(tipX, tipY); - ctx.lineTo(tipX + Math.cos(leftAngle) * headLen, tipY + Math.sin(leftAngle) * headLen); - ctx.lineTo(tipX + Math.cos(rightAngle) * headLen, tipY + Math.sin(rightAngle) * headLen); - ctx.closePath(); - ctx.fill(); - } - - // Draws an arrow centered at (cx, cy) pointing toward the ramp's high - // side, so ramp direction is visible at a glance on both the palette - // swatches and the tile grid. Used for cardinal ramps and outer-corner - // ramps (a single raised corner). - function drawArrow(ctx, cx, cy, size, dx, dy) { - const mag = Math.hypot(dx, dy) || 1; - const ux = dx / mag, uy = dy / mag; - const len = size * 0.32; - drawArrowSegment(ctx, cx - ux * len, cy - uy * len, cx + ux * len, cy + uy * len, size); - } - - // Draws a plain right-angle bracket - two line segments meeting at 90 - // degrees at the tile's corner, one running along each of the two edges - // adjacent to it - so inner-corner ramps (three corners raised, one - // dropped) read as "the adjacent sides meeting in a 90-degree corner", - // distinct from the single diagonal arrow used for outer-corner ramps. - function drawCornerBracket(ctx, cx, cy, size, dx, dy) { - const cornerDist = size * 0.42; - const armLen = size * 0.34; - const cornerX = cx + dx * cornerDist; - const cornerY = cy + dy * cornerDist; - - ctx.strokeStyle = "#ffffff"; - ctx.lineWidth = Math.max(1, size * 0.08); - ctx.lineCap = "round"; - ctx.lineJoin = "round"; - - ctx.beginPath(); - ctx.moveTo(cornerX - dx * armLen, cornerY); - ctx.lineTo(cornerX, cornerY); - ctx.lineTo(cornerX, cornerY - dy * armLen); - ctx.stroke(); - } - - // Draws the direction icon for a ramp tile `type` in a `size`x`size` area - // centered at (cx, cy): a diagonal arrow for cardinal/outer-corner ramps, - // or a right-angle bracket for inner-corner ramps. No-op for shapes with - // no direction (ground, erase). - function drawShapeIcon(ctx, cx, cy, size, type) { - const dir = SHAPE_DIRECTIONS[type]; - if(!dir) return; - if(INNER_CORNER_SHAPES.has(type)) { - drawCornerBracket(ctx, cx, cy, size, dir.dx, dir.dy); - } else { - drawArrow(ctx, cx, cy, size, dir.dx, dir.dy); - } - } - - // Draws a small pencil icon centered in a `size`x`size` canvas. - function drawPencilIcon(ctx, size) { - ctx.save(); - ctx.translate(size / 2, size / 2); - ctx.rotate(-Math.PI / 4); - - const shaftW = size * 0.18; - const shaftH = size * 0.62; - - ctx.fillStyle = "#e0e0e0"; - ctx.fillRect(-shaftW / 2, shaftH * 0.28, shaftW, shaftH * 0.22); - - ctx.fillStyle = "#f0c419"; - ctx.fillRect(-shaftW / 2, -shaftH * 0.5, shaftW, shaftH * 0.78); - - ctx.fillStyle = "#8d6e63"; - ctx.beginPath(); - ctx.moveTo(-shaftW / 2, -shaftH * 0.5); - ctx.lineTo(shaftW / 2, -shaftH * 0.5); - ctx.lineTo(0, -shaftH * 0.5 - shaftW); - ctx.closePath(); - ctx.fill(); - - ctx.restore(); - } - - // Draws a small paint bucket icon centered in a `size`x`size` canvas. - function drawBucketIcon(ctx, size) { - ctx.save(); - ctx.translate(size / 2, size / 2); - - const w = size * 0.5, h = size * 0.34; - - ctx.strokeStyle = "#e0e0e0"; - ctx.lineWidth = Math.max(1, size * 0.06); - ctx.beginPath(); - ctx.arc(0, -h * 0.4, w * 0.4, Math.PI, 0); - ctx.stroke(); - - ctx.fillStyle = "#03a9f4"; - ctx.beginPath(); - ctx.moveTo(-w / 2, -h * 0.2); - ctx.lineTo(w / 2, -h * 0.2); - ctx.lineTo(w * 0.32, h * 0.6); - ctx.lineTo(-w * 0.32, h * 0.6); - ctx.closePath(); - ctx.fill(); - - ctx.beginPath(); - ctx.arc(-w * 0.55, h * 0.75, size * 0.06, 0, Math.PI * 2); - ctx.fill(); - - ctx.restore(); - } - - const TOOLS = [ - { id: "pencil", name: "Pencil", draw: drawPencilIcon }, - { id: "bucket", name: "Paint Bucket", draw: drawBucketIcon }, - ]; - - let coord = { x: 0, y: 0, z: 0 }; - let tiles = new Int32Array(ChunkTerrain.TILE_COUNT); - let meshes = []; - let chunkExists = false; - let currentZLevel = 0; - let selectedShape = ChunkTerrain.TILE_SHAPE_GROUND; - let zoomWorldH = 20; - let dirty = false; - let hoverTile = null; - let activeTool = "pencil"; - - let mapRenderer = null; - let terrainMesh = null; - let gridLinesMesh = null; - let terrainTexturePromise = null; - let modelIndex = null; - let neighborChunks = []; - const modelCache = new Map(); - - const UNDO_LIMIT = 100; - let undoStack = []; - - function markDirty() { - dirty = true; - } - - function snapshotState() { - return { - tiles: tiles.slice(), - meshes: meshes.map((m) => ({ file: m.file, pos: m.pos.slice() })), - }; - } - - function pushUndo() { - undoStack.push(snapshotState()); - if(undoStack.length > UNDO_LIMIT) undoStack.shift(); - } - - function undo() { - if(!undoStack.length) return; - const snap = undoStack.pop(); - tiles = snap.tiles; - meshes = snap.meshes; - dirty = true; - renderGrid(); - renderMeshList(); - renderPreview(); - updateStatus("Undid last change."); - } - - // Returns true if it's OK to proceed (no unsaved changes, or the user - // confirmed discarding them). - function confirmDiscardIfDirty() { - if(!dirty) return true; - return window.confirm("You have unsaved changes to this chunk. Discard them?"); - } - - function statusLine() { - return document.getElementById("statusLine"); - } - - function updateStatus(msg, isError) { - const el = statusLine(); - el.textContent = msg; - el.classList.toggle("text-danger", !!isError); - el.classList.toggle("text-muted", !isError); - } - - async function apiReadJson(path) { - const res = await fetch(`/api/read?path=${encodeURIComponent(path)}`); - const data = await res.json(); - return { ok: res.ok, data }; - } - - async function apiWrite(path, content) { - return fetch(`/api/write?path=${encodeURIComponent(path)}`, { - method: "POST", - body: JSON.stringify({ content }), - }); - } - - async function apiDelete(path) { - return fetch(`/api/delete?path=${encodeURIComponent(path)}`, { method: "POST" }); - } - - async function apiCompileChunk(x, y, z) { - const res = await fetch(`/api/compile-chunk?x=${x}&y=${y}&z=${z}`, { method: "POST" }); - return res.json(); - } - - async function apiLs(path) { - const res = await fetch(`/api/ls?path=${encodeURIComponent(path)}`); - return res.json(); - } - - async function apiFind(path, ext) { - const res = await fetch(`/api/find?path=${encodeURIComponent(path)}&ext=${encodeURIComponent(ext)}`); - return res.json(); - } - - function base64ToBuffer(base64) { - const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); - return bytes.buffer; - } - - async function loadImageTexture(assetPath) { - const { ok, data } = await apiReadJson(assetPath); - if(!ok || data.error) return null; - const blob = new Blob([base64ToBuffer(data.content)], { type: "image/png" }); - const bitmap = await createImageBitmap(blob); - return mapRenderer.createTextureFromImage(bitmap); - } - - async function readDmfVertices(assetPath) { - const { ok, data } = await apiReadJson(assetPath); - if(!ok || data.error) throw new Error(`Failed to read ${assetPath}`); - return DMF.decode(base64ToBuffer(data.content)).floats; - } - - async function ensureModelIndex() { - if(modelIndex) return modelIndex; - const res = await apiFind("assets/models", ".json"); - modelIndex = new Map(); - for(const file of res.files || []) { - const base = file.split("/").pop().replace(/\.json$/, ""); - modelIndex.set(base, file); - } - return modelIndex; - } - - // Scales a mesh's stored [x, y, z] offset for preview, matching the - // Z scaling mapChunkLoaded() applies at runtime (chunk.meshOffsets[m][2] - // * WORLD_LAYER_HEIGHT) - x/y stay 1:1 since only Z is height-scaled. - function scaledMeshOffset(pos) { - return [pos[0], pos[1], pos[2] * ChunkTerrain.WORLD_LAYER_HEIGHT]; - } - - // Resolves a chunk mesh entry's bare filename (e.g. "house_5_3.dmf") to - // its model JSON by basename, mirroring find_model() in - // tools/asset/chunk/__main__.py - the chunk JSON only ever stores a bare - // filename, not a path. - async function resolveModelForMeshFile(file) { - await ensureModelIndex(); - const base = file.replace(/\.[^.]+$/, ""); - const rel = modelIndex.get(base); - if(!rel) return null; - - const modelPath = `assets/models/${rel}`; - if(modelCache.has(modelPath)) return modelCache.get(modelPath); - - const promise = (async () => { - const { ok, data } = await apiReadJson(modelPath); - if(!ok || data.error) throw new Error(`Failed to read model ${modelPath}`); - const modelDef = JSON.parse(data.content); - const meshFloats = await readDmfVertices(`assets/${modelDef.mesh}`); - const mesh = mapRenderer.createMesh(meshFloats); - const texture = modelDef.texture ? await loadImageTexture(`assets/${modelDef.texture}`) : null; - const colorBytes = modelDef.color || [255, 255, 255, 255]; - const color = colorBytes.map((c) => c / 255); - return { mesh, texture, color }; - })(); - modelCache.set(modelPath, promise); - return promise; - } - - const MAP_NAME = "overworld"; - - function chunkPaths(x, y, z) { - return { - raw: `assetsraw/maps/${MAP_NAME}/chunks/chunk_${x}_${y}_${z}.json`, - dcf: `assets/maps/${MAP_NAME}/chunks/${x}_${y}_${z}.dcf`, - terrainDmf: `assets/maps/${MAP_NAME}/meshes/chunks/chunk_${x}_${y}_${z}_0.dmf`, - terrainModel: `assets/maps/${MAP_NAME}/models/chunks/chunk_${x}_${y}_${z}_0.json`, - }; - } - - // Loads the 8 same-Z neighbors around `coord` (read-only, for context in - // the 3D preview) and pre-builds their render data so renderPreview() - // doesn't have to rebuild GL buffers on every paint stroke. - async function loadNeighborChunks() { - const offsets = []; - for(let dy = -1; dy <= 1; dy++) { - for(let dx = -1; dx <= 1; dx++) { - if(dx || dy) offsets.push({ dx, dy }); - } - } - - const results = await Promise.all(offsets.map(async ({ dx, dy }) => { - const nx = coord.x + dx, ny = coord.y + dy, nz = coord.z; - const { ok, data } = await apiReadJson(chunkPaths(nx, ny, nz).raw); - if(!ok || data.error) return null; - - const json = JSON.parse(data.content); - const neighborTiles = new Int32Array(ChunkTerrain.TILE_COUNT); - for(const t of json.tiles || []) { - const [tx, ty, tz] = t.pos; - neighborTiles[ChunkTerrain.tileIndex(tx, ty, tz)] = t.type; - } - const neighborMeshes = (json.meshes || []).map((m) => ({ file: m.file, pos: m.pos || [0, 0, 0] })); - - const floats = ChunkTerrain.buildTerrainVerts(neighborTiles); - const terrain = floats.length ? { mesh: mapRenderer.createMesh(floats) } : null; - - const models = []; - for(const m of neighborMeshes) { - try { - const resolved = await resolveModelForMeshFile(m.file); - if(resolved) models.push({ ...resolved, offset: scaledMeshOffset(m.pos) }); - } catch(e) { - console.warn("Failed to load neighbor mesh preview", m.file, e); - } - } - - return { - offset: [dx * ChunkTerrain.CHUNK_WIDTH, dy * ChunkTerrain.CHUNK_HEIGHT, 0], - terrain, - models, - }; - })); - - neighborChunks = results.filter(Boolean); - } - - async function loadChunk(x, y, z) { - coord = { x, y, z }; - tiles = new Int32Array(ChunkTerrain.TILE_COUNT); - meshes = []; - chunkExists = false; - - const { ok, data } = await apiReadJson(chunkPaths(x, y, z).raw); - if(ok && !data.error) { - chunkExists = true; - const json = JSON.parse(data.content); - for(const t of json.tiles || []) { - const [tx, ty, tz] = t.pos; - tiles[ChunkTerrain.tileIndex(tx, ty, tz)] = t.type; - } - meshes = (json.meshes || []).map((m) => ({ file: m.file, pos: m.pos || [0, 0, 0] })); - } - - await loadNeighborChunks(); - dirty = false; - undoStack = []; - - renderGrid(); - renderMeshList(); - renderPreview(); - updateStatus(chunkExists - ? `Loaded chunk_${x}_${y}_${z}.json` - : `New (empty) chunk at ${x}, ${y}, ${z} - paint a tile and save to create it.`); - } - - async function saveChunk() { - const tilesArr = []; - for(let z = 0; z < ChunkTerrain.CHUNK_DEPTH; z++) { - for(let y = 0; y < ChunkTerrain.CHUNK_HEIGHT; y++) { - for(let x = 0; x < ChunkTerrain.CHUNK_WIDTH; x++) { - const type = tiles[ChunkTerrain.tileIndex(x, y, z)]; - if(type && type !== ChunkTerrain.TILE_SHAPE_NULL) { - tilesArr.push({ pos: [x, y, z], type, tile: 0 }); - } - } - } - } - - const paths = chunkPaths(coord.x, coord.y, coord.z); - - if(tilesArr.length === 0 && meshes.length === 0) { - await Promise.all(Object.values(paths).map((p) => apiDelete(p))); - chunkExists = false; - dirty = false; - updateStatus(`Chunk ${coord.x}, ${coord.y}, ${coord.z} is empty - deleted.`); - return; - } - - const json = { tiles: tilesArr, meshes: meshes.map((m) => ({ file: m.file, pos: m.pos })) }; - await apiWrite(paths.raw, JSON.stringify(json, null, 2)); - chunkExists = true; - dirty = false; - updateStatus("Saved. Compiling..."); - - const result = await apiCompileChunk(coord.x, coord.y, coord.z); - if(result.ok) { - updateStatus(`Compiled OK.\n${result.stdout}`); - } else { - updateStatus(`Compile FAILED (code ${result.returncode}):\n${result.stderr}`, true); - } - } - - function buildPalette() { - const el = document.getElementById("tilePalette"); - el.innerHTML = ""; - for(const s of SHAPES) { - const btn = document.createElement("button"); - btn.type = "button"; - btn.className = "tile-swatch" + (s.type === selectedShape ? " active" : ""); - btn.style.background = s.color; - btn.title = s.name; - btn.addEventListener("click", () => { - selectedShape = s.type; - buildPalette(); - }); - - if(SHAPE_DIRECTIONS[s.type]) { - const icon = document.createElement("canvas"); - icon.width = 32; - icon.height = 32; - icon.className = "tile-swatch-icon"; - drawShapeIcon(icon.getContext("2d"), 16, 16, 32, s.type); - btn.appendChild(icon); - } - - el.appendChild(btn); - } - } - - function buildToolPalette() { - const el = document.getElementById("toolPalette"); - el.innerHTML = ""; - for(const tool of TOOLS) { - const btn = document.createElement("button"); - btn.type = "button"; - btn.className = "tile-swatch" + (tool.id === activeTool ? " active" : ""); - btn.style.background = "#2a2d33"; - btn.title = tool.name; - btn.addEventListener("click", () => { - activeTool = tool.id; - buildToolPalette(); - }); - - const icon = document.createElement("canvas"); - icon.width = 32; - icon.height = 32; - icon.className = "tile-swatch-icon"; - tool.draw(icon.getContext("2d"), 32); - btn.appendChild(icon); - - el.appendChild(btn); - } - } - - function canvasToTile(e, canvas) { - const rect = canvas.getBoundingClientRect(); - const cell = canvas.width / ChunkTerrain.CHUNK_WIDTH; - const px = (e.clientX - rect.left) * (canvas.width / rect.width); - const py = (e.clientY - rect.top) * (canvas.height / rect.height); - const x = Math.floor(px / cell); - const yFromTop = Math.floor(py / cell); - const y = ChunkTerrain.CHUNK_HEIGHT - 1 - yFromTop; - if(x < 0 || x >= ChunkTerrain.CHUNK_WIDTH || y < 0 || y >= ChunkTerrain.CHUNK_HEIGHT) return null; - return { x, y }; - } - - function renderGrid() { - const canvas = document.getElementById("gridCanvas"); - const ctx = canvas.getContext("2d"); - const cell = canvas.width / ChunkTerrain.CHUNK_WIDTH; - - ctx.clearRect(0, 0, canvas.width, canvas.height); - for(let y = 0; y < ChunkTerrain.CHUNK_HEIGHT; y++) { - for(let x = 0; x < ChunkTerrain.CHUNK_WIDTH; x++) { - const type = tiles[ChunkTerrain.tileIndex(x, y, currentZLevel)]; - const sy = (ChunkTerrain.CHUNK_HEIGHT - 1 - y) * cell; - ctx.fillStyle = SHAPE_COLORS[type] || "#15171b"; - ctx.fillRect(x * cell, sy, cell, cell); - ctx.strokeStyle = "rgba(255, 255, 255, 0.25)"; - ctx.strokeRect(x * cell, sy, cell, cell); - - drawShapeIcon(ctx, x * cell + cell / 2, sy + cell / 2, cell, type); - } - } - - ctx.fillStyle = "#ffffff"; - for(const m of meshes) { - const [mx, my, mz] = m.pos; - if(Math.round(mz) !== currentZLevel) continue; - const sy = (ChunkTerrain.CHUNK_HEIGHT - 1 - my) * cell; - ctx.beginPath(); - ctx.arc(mx * cell + cell / 2, sy + cell / 2, cell * 0.3, 0, Math.PI * 2); - ctx.fill(); - } - - if(hoverTile) { - const sy = (ChunkTerrain.CHUNK_HEIGHT - 1 - hoverTile.y) * cell; - ctx.strokeStyle = "#ffeb3b"; - ctx.lineWidth = 2; - ctx.strokeRect(hoverTile.x * cell + 1, sy + 1, cell - 2, cell - 2); - } - } - - function hitTestMesh(e, canvas) { - const rect = canvas.getBoundingClientRect(); - const cell = canvas.width / ChunkTerrain.CHUNK_WIDTH; - const px = (e.clientX - rect.left) * (canvas.width / rect.width); - const py = (e.clientY - rect.top) * (canvas.height / rect.height); - - let closest = -1; - let closestDist = cell * 0.5; - meshes.forEach((m, i) => { - const [mx, my, mz] = m.pos; - if(Math.round(mz) !== currentZLevel) return; - const cx = mx * cell + cell / 2; - const cy = (ChunkTerrain.CHUNK_HEIGHT - 1 - my) * cell + cell / 2; - const dist = Math.hypot(px - cx, py - cy); - if(dist < closestDist) { closestDist = dist; closest = i; } - }); - return closest; - } - - function moveMeshTo(e, index) { - const canvas = document.getElementById("gridCanvas"); - const t = canvasToTile(e, canvas); - if(!t) return; - meshes[index].pos[0] = t.x; - meshes[index].pos[1] = t.y; - markDirty(); - renderGrid(); - renderMeshList(); - renderPreview(); - } - - function paintAt(e) { - const canvas = document.getElementById("gridCanvas"); - const t = canvasToTile(e, canvas); - if(!t) return; - tiles[ChunkTerrain.tileIndex(t.x, t.y, currentZLevel)] = selectedShape; - markDirty(); - renderGrid(); - renderPreview(); - } - - // 4-connected flood fill of same-type tiles at the current Z-level, - // starting from (startX, startY), replacing them with selectedShape. - function floodFill(startX, startY) { - const targetType = tiles[ChunkTerrain.tileIndex(startX, startY, currentZLevel)]; - if(targetType === selectedShape) return; - - const stack = [[startX, startY]]; - const visited = new Set(); - while(stack.length) { - const [x, y] = stack.pop(); - const key = `${x},${y}`; - if(visited.has(key)) continue; - if(x < 0 || x >= ChunkTerrain.CHUNK_WIDTH || y < 0 || y >= ChunkTerrain.CHUNK_HEIGHT) continue; - visited.add(key); - - const idx = ChunkTerrain.tileIndex(x, y, currentZLevel); - if(tiles[idx] !== targetType) continue; - tiles[idx] = selectedShape; - stack.push([x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]); - } - } - - function fillAt(e) { - const canvas = document.getElementById("gridCanvas"); - const t = canvasToTile(e, canvas); - if(!t) return; - pushUndo(); - floodFill(t.x, t.y); - markDirty(); - renderGrid(); - renderPreview(); - } - - function rebuildTerrainMesh() { - const floats = ChunkTerrain.buildTerrainVerts(tiles); - terrainMesh = floats.length ? mapRenderer.createMesh(floats) : null; - - const lineFloats = ChunkTerrain.buildGridLines(tiles); - gridLinesMesh = lineFloats.length ? mapRenderer.createMesh(lineFloats) : null; - } - - // Keeps the preview canvas's backing-store resolution matched to its - // actual displayed CSS size (times devicePixelRatio) - the canvas - // otherwise stays at its fixed width/height attributes (512x512) while - // CSS stretches it to fill the column, which upscales and blurs the - // WebGL output on wider or high-DPI screens. - function resizePreviewCanvas() { - const canvas = document.getElementById("previewCanvas"); - const dpr = window.devicePixelRatio || 1; - const displayWidth = Math.max(1, Math.round(canvas.clientWidth * dpr)); - const displayHeight = Math.max(1, Math.round(canvas.clientHeight * dpr)); - if(canvas.width !== displayWidth || canvas.height !== displayHeight) { - canvas.width = displayWidth; - canvas.height = displayHeight; - } - } - - async function renderPreview() { - if(!mapRenderer) return; - resizePreviewCanvas(); - rebuildTerrainMesh(); - - const models = []; - for(const m of meshes) { - try { - const resolved = await resolveModelForMeshFile(m.file); - if(resolved) models.push({ ...resolved, offset: scaledMeshOffset(m.pos) }); - } catch(e) { - console.warn("Failed to load mesh preview", m.file, e); - } - } - - let terrainTexture = null; - if(terrainMesh || neighborChunks.some((n) => n.terrain)) { - if(!terrainTexturePromise) terrainTexturePromise = loadImageTexture("assets/tiles.png"); - terrainTexture = await terrainTexturePromise; - } - - const neighbors = neighborChunks.map((n) => ({ - offset: n.offset, - terrain: n.terrain ? { mesh: n.terrain.mesh, texture: terrainTexture } : null, - models: n.models, - })); - - const zLevelHeight = currentZLevel * ChunkTerrain.WORLD_LAYER_HEIGHT; - mapRenderer.render({ - target: [ChunkTerrain.CHUNK_WIDTH / 2, ChunkTerrain.CHUNK_HEIGHT / 2, zLevelHeight], - worldH: zoomWorldH, - terrain: terrainMesh ? { mesh: terrainMesh, texture: terrainTexture } : null, - gridLines: gridLinesMesh ? { mesh: gridLinesMesh } : null, - models, - neighbors, - highlight: hoverTile ? { x: hoverTile.x, y: hoverTile.y, z: zLevelHeight } : null, - }); - } - - function renderMeshList() { - const ul = document.getElementById("meshList"); - ul.innerHTML = ""; - meshes.forEach((m, i) => { - const li = document.createElement("li"); - li.className = "list-group-item d-flex align-items-center gap-2 flex-wrap"; - - const label = document.createElement("span"); - label.className = "me-auto"; - label.textContent = m.file; - li.appendChild(label); - - ["X", "Y", "Z"].forEach((axis, axisIndex) => { - const input = document.createElement("input"); - input.type = "number"; - input.className = "form-control form-control-sm"; - input.style.width = "5rem"; - input.title = axis; - input.value = m.pos[axisIndex]; - input.addEventListener("change", () => { - pushUndo(); - m.pos[axisIndex] = parseFloat(input.value) || 0; - markDirty(); - renderGrid(); - renderPreview(); - }); - li.appendChild(input); - }); - - const btn = document.createElement("button"); - btn.type = "button"; - btn.className = "btn btn-sm btn-outline-danger"; - btn.textContent = "Remove"; - btn.addEventListener("click", () => { - pushUndo(); - meshes.splice(i, 1); - markDirty(); - renderMeshList(); - renderGrid(); - renderPreview(); - }); - - li.appendChild(btn); - ul.appendChild(li); - }); - } - - async function buildMeshPicker() { - await ensureModelIndex(); - const select = document.getElementById("meshPicker"); - select.innerHTML = ""; - for(const [base, rel] of [...modelIndex.entries()].sort()) { - const opt = document.createElement("option"); - opt.value = base; - opt.textContent = rel; - select.appendChild(opt); - } - } - - async function buildChunkBrowseList() { - const list = document.getElementById("chunkBrowseList"); - list.innerHTML = ""; - const res = await apiLs(`assetsraw/maps/${MAP_NAME}/chunks`); - for(const entry of res.entries || []) { - const m = entry.name.match(/^chunk_(-?\d+)_(-?\d+)_(-?\d+)\.json$/); - if(!m) continue; - const [, x, y, z] = m; - - const li = document.createElement("li"); - const a = document.createElement("a"); - a.className = "dropdown-item"; - a.href = "#"; - a.textContent = `${x}, ${y}, ${z}`; - a.addEventListener("click", (e) => { - e.preventDefault(); - goToChunk(parseInt(x, 10), parseInt(y, 10), parseInt(z, 10)); - }); - - li.appendChild(a); - list.appendChild(li); - } - } - - function setCoordInputs(x, y, z) { - document.getElementById("coordX").value = x; - document.getElementById("coordY").value = y; - document.getElementById("coordZ").value = z; - } - - function goToChunk(x, y, z) { - if(!confirmDiscardIfDirty()) return; - setCoordInputs(x, y, z); - loadChunk(x, y, z); - } - - function bindEvents() { - const gridCanvas = document.getElementById("gridCanvas"); - let painting = false; - let draggingMeshIndex = -1; - - gridCanvas.addEventListener("mousedown", (e) => { - if(activeTool === "bucket") { - fillAt(e); - return; - } - - const hit = hitTestMesh(e, gridCanvas); - pushUndo(); - if(hit !== -1) { - draggingMeshIndex = hit; - return; - } - painting = true; - paintAt(e); - }); - window.addEventListener("mouseup", () => { painting = false; draggingMeshIndex = -1; }); - gridCanvas.addEventListener("mousemove", (e) => { - hoverTile = canvasToTile(e, gridCanvas); - if(draggingMeshIndex !== -1) { moveMeshTo(e, draggingMeshIndex); return; } - if(painting) { paintAt(e); return; } - renderGrid(); - renderPreview(); - }); - gridCanvas.addEventListener("mouseleave", () => { - hoverTile = null; - renderGrid(); - renderPreview(); - }); - - function setZLevel(z) { - currentZLevel = Math.max(0, Math.min(ChunkTerrain.CHUNK_DEPTH - 1, z)); - document.getElementById("zLevel").value = currentZLevel; - document.getElementById("zLevelLabel").textContent = currentZLevel; - renderGrid(); - renderPreview(); - } - - document.getElementById("zLevel").addEventListener("input", (e) => { - setZLevel(parseInt(e.target.value, 10)); - }); - document.getElementById("zLevelDown").addEventListener("click", () => setZLevel(currentZLevel - 1)); - document.getElementById("zLevelUp").addEventListener("click", () => setZLevel(currentZLevel + 1)); - - document.getElementById("btnLoad").addEventListener("click", () => { - const x = parseInt(document.getElementById("coordX").value, 10) || 0; - const y = parseInt(document.getElementById("coordY").value, 10) || 0; - const z = parseInt(document.getElementById("coordZ").value, 10) || 0; - goToChunk(x, y, z); - }); - - // NORTH=+Y, EAST=+X (matches tile-shape convention used by the tile - // grid and tools/asset/chunk/__main__.py). - document.getElementById("navN").addEventListener("click", () => goToChunk(coord.x, coord.y + 1, coord.z)); - document.getElementById("navS").addEventListener("click", () => goToChunk(coord.x, coord.y - 1, coord.z)); - document.getElementById("navE").addEventListener("click", () => goToChunk(coord.x + 1, coord.y, coord.z)); - document.getElementById("navW").addEventListener("click", () => goToChunk(coord.x - 1, coord.y, coord.z)); - document.getElementById("navUp").addEventListener("click", () => goToChunk(coord.x, coord.y, coord.z + 1)); - document.getElementById("navDown").addEventListener("click", () => goToChunk(coord.x, coord.y, coord.z - 1)); - - function runSave() { - saveChunk().catch((e) => updateStatus(`Save failed: ${e.message}`, true)); - } - - document.getElementById("btnSave").addEventListener("click", runSave); - - document.getElementById("btnAddMesh").addEventListener("click", () => { - const base = document.getElementById("meshPicker").value; - if(!base) return; - const pos = [ - parseFloat(document.getElementById("meshX").value) || 0, - parseFloat(document.getElementById("meshY").value) || 0, - parseFloat(document.getElementById("meshZ").value) || 0, - ]; - pushUndo(); - meshes.push({ file: `${base}.dmf`, pos }); - markDirty(); - renderMeshList(); - renderGrid(); - renderPreview(); - }); - - document.getElementById("previewCanvas").addEventListener("wheel", (e) => { - e.preventDefault(); - zoomWorldH = Math.min(60, Math.max(4, zoomWorldH + Math.sign(e.deltaY))); - renderPreview(); - }, { passive: false }); - - window.addEventListener("resize", () => renderPreview()); - - window.addEventListener("keydown", (e) => { - if(!(e.ctrlKey || e.metaKey)) return; - const key = e.key.toLowerCase(); - if(key === "z") { - e.preventDefault(); - undo(); - } else if(key === "s") { - e.preventDefault(); - runSave(); - } - }); - } - - document.addEventListener("DOMContentLoaded", async () => { - buildPalette(); - buildToolPalette(); - mapRenderer = MapRenderer.create(document.getElementById("previewCanvas")); - bindEvents(); - await Promise.all([buildChunkBrowseList(), buildMeshPicker()]); - await loadChunk(coord.x, coord.y, coord.z); - }); -})(); diff --git a/editor/client/public/map/renderer.js b/editor/client/public/map/renderer.js deleted file mode 100644 index fc9c7470..00000000 --- a/editor/client/public/map/renderer.js +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (c) 2026 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -"use strict"; - -// MapRenderer - a minimal, dependency-free WebGL renderer for the chunk -// preview pane. The camera formula is ported 1:1 from -// src/dusk/scene/overworld/sceneoverworld.c (lines 62-81) so the preview -// approximates the game's own fixed-angle 3rd-person camera: the eye sits -// worldH units "behind" the target along Y and a proportional distance -// "above" along Z, always looking at the target, up = (0, 1, 0). -// -// The material model mirrors SHADER_UNLIT: a texture sampler multiplied by -// a flat RGBA tint, with a 1x1 white fallback texture standing in for -// untextured models. - -const MapRenderer = (() => { - const VERT_SRC = ` - attribute vec2 aUV; - attribute vec3 aPos; - uniform mat4 uModel; - uniform mat4 uView; - uniform mat4 uProjection; - varying vec2 vUV; - void main() { - vUV = aUV; - gl_Position = uProjection * uView * uModel * vec4(aPos, 1.0); - } - `; - - const FRAG_SRC = ` - precision mediump float; - varying vec2 vUV; - uniform sampler2D uTexture; - uniform vec4 uColor; - void main() { - gl_FragColor = texture2D(uTexture, vUV) * uColor; - } - `; - - function mat4Identity(out) { - out.fill(0); - out[0] = out[5] = out[10] = out[15] = 1; - return out; - } - - function mat4Translation(out, v) { - mat4Identity(out); - out[12] = v[0]; - out[13] = v[1]; - out[14] = v[2]; - return out; - } - - function mat4Perspective(out, fovy, aspect, near, far) { - const f = 1.0 / Math.tan(fovy / 2); - out.fill(0); - out[0] = f / aspect; - out[5] = f; - out[10] = (far + near) / (near - far); - out[11] = -1; - out[14] = (2 * far * near) / (near - far); - return out; - } - - function vec3Sub(a, b) { - return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; - } - - function vec3Cross(a, b) { - return [ - a[1] * b[2] - a[2] * b[1], - a[2] * b[0] - a[0] * b[2], - a[0] * b[1] - a[1] * b[0], - ]; - } - - function vec3Normalize(a) { - const len = Math.hypot(a[0], a[1], a[2]) || 1; - return [a[0] / len, a[1] / len, a[2] / len]; - } - - function mat4LookAt(out, eye, center, up) { - const zAxis = vec3Normalize(vec3Sub(eye, center)); - const xAxis = vec3Normalize(vec3Cross(up, zAxis)); - const yAxis = vec3Cross(zAxis, xAxis); - - out[0] = xAxis[0]; out[1] = yAxis[0]; out[2] = zAxis[0]; out[3] = 0; - out[4] = xAxis[1]; out[5] = yAxis[1]; out[6] = zAxis[1]; out[7] = 0; - out[8] = xAxis[2]; out[9] = yAxis[2]; out[10] = zAxis[2]; out[11] = 0; - out[12] = -(xAxis[0] * eye[0] + xAxis[1] * eye[1] + xAxis[2] * eye[2]); - out[13] = -(yAxis[0] * eye[0] + yAxis[1] * eye[1] + yAxis[2] * eye[2]); - out[14] = -(zAxis[0] * eye[0] + zAxis[1] * eye[1] + zAxis[2] * eye[2]); - out[15] = 1; - return out; - } - - // Ported from sceneoverworld.c:62-81. Returns { eye, view }. - function computeCamera(target, worldH) { - const fov = Math.PI / 4; // glm_rad(45.0f) - const zDist = (worldH * 0.5) / Math.tan(fov * 0.5); - const offset = -worldH; - - const eye = [target[0], target[1] + offset, target[2] + zDist]; - const view = mat4LookAt(new Float32Array(16), eye, target, [0, 1, 0]); - return { eye, view, fov }; - } - - function compileShader(gl, type, src) { - const shader = gl.createShader(type); - gl.shaderSource(shader, src); - gl.compileShader(shader); - if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - const info = gl.getShaderInfoLog(shader); - gl.deleteShader(shader); - throw new Error(`Shader compile failed: ${info}`); - } - return shader; - } - - function create(canvas) { - const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); - if(!gl) throw new Error("WebGL is not available in this browser"); - - const program = gl.createProgram(); - gl.attachShader(program, compileShader(gl, gl.VERTEX_SHADER, VERT_SRC)); - gl.attachShader(program, compileShader(gl, gl.FRAGMENT_SHADER, FRAG_SRC)); - gl.linkProgram(program); - if(!gl.getProgramParameter(program, gl.LINK_STATUS)) { - throw new Error(`Program link failed: ${gl.getProgramInfoLog(program)}`); - } - - const attribs = { - aUV: gl.getAttribLocation(program, "aUV"), - aPos: gl.getAttribLocation(program, "aPos"), - }; - const uniforms = { - uModel: gl.getUniformLocation(program, "uModel"), - uView: gl.getUniformLocation(program, "uView"), - uProjection: gl.getUniformLocation(program, "uProjection"), - uTexture: gl.getUniformLocation(program, "uTexture"), - uColor: gl.getUniformLocation(program, "uColor"), - }; - - const whiteTexture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, whiteTexture); - gl.texImage2D( - gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, - new Uint8Array([255, 255, 255, 255]), - ); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - - gl.enable(gl.DEPTH_TEST); - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - // Terrain/mesh winding isn't guaranteed consistent relative to this - // preview's fixed camera angle, so backface culling stays off - this - // is an authoring preview, not a performance-sensitive renderer, and - // an invisible mesh is a worse failure mode than a visible backface. - - // Uploads interleaved [u, v, x, y, z] float data (DMF/terrain layout) - // into a GL buffer. Returns { buffer, vertexCount }. - function createMesh(floats) { - const buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, floats, gl.STATIC_DRAW); - return { buffer, vertexCount: floats.length / 5 }; - } - - function createTextureFromImage(image) { - const texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - return texture; - } - - function drawMesh(mesh, model, texture, color, depthWrite, mode) { - gl.depthMask(depthWrite !== false); - gl.bindBuffer(gl.ARRAY_BUFFER, mesh.buffer); - gl.enableVertexAttribArray(attribs.aUV); - gl.vertexAttribPointer(attribs.aUV, 2, gl.FLOAT, false, 20, 0); - gl.enableVertexAttribArray(attribs.aPos); - gl.vertexAttribPointer(attribs.aPos, 3, gl.FLOAT, false, 20, 8); - - gl.uniformMatrix4fv(uniforms.uModel, false, model); - gl.uniform4f(uniforms.uColor, color[0], color[1], color[2], color[3]); - - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture || whiteTexture); - gl.uniform1i(uniforms.uTexture, 0); - - gl.drawArrays(mode || gl.TRIANGLES, 0, mesh.vertexCount); - } - - // Builds a 4-vertex outline of the tile at (x, y, z), lifted slightly - // to avoid z-fighting with the terrain surface beneath it. - function buildHighlightMesh(x, y, z) { - const eps = 0.02; - return createMesh(new Float32Array([ - 0, 0, x, y, z + eps, - 0, 0, x + 1, y, z + eps, - 0, 0, x + 1, y + 1, z + eps, - 0, 0, x, y + 1, z + eps, - ])); - } - - // Neighboring chunks are drawn dimmer and semi-transparent, so they - // read as context rather than part of the chunk being edited. - const NEIGHBOR_ALPHA = 0.35; - const NEIGHBOR_DARKEN = 0.5; - - // scene = { - // target: [x, y, z], worldH: number, - // terrain: { mesh, texture } | null, - // gridLines: { mesh } | null, - // models: [{ mesh, texture, color: [r,g,b,a in 0..1], offset: [x,y,z] }], - // neighbors: [{ - // offset: [x,y,z], - // terrain: { mesh, texture } | null, - // models: [{ mesh, texture, color, offset }], - // }], - // highlight: { x, y, z } | null, - // } - function render(scene) { - const width = canvas.width, height = canvas.height; - gl.viewport(0, 0, width, height); - gl.clearColor(0.16, 0.18, 0.22, 1); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - gl.useProgram(program); - gl.depthMask(true); - - const { view, fov } = computeCamera(scene.target, scene.worldH); - const projection = mat4Perspective(new Float32Array(16), fov, width / height, 0.1, 1000); - gl.uniformMatrix4fv(uniforms.uProjection, false, projection); - gl.uniformMatrix4fv(uniforms.uView, false, view); - - const identity = mat4Identity(new Float32Array(16)); - - if(scene.terrain) { - drawMesh(scene.terrain.mesh, identity, scene.terrain.texture, [1, 1, 1, 1], true); - } - - if(scene.gridLines) { - drawMesh(scene.gridLines.mesh, identity, null, [0, 0, 0, 0.35], false, gl.LINES); - } - - for(const model of scene.models) { - const modelMatrix = mat4Translation(new Float32Array(16), model.offset); - drawMesh(model.mesh, modelMatrix, model.texture, model.color, true); - } - - for(const neighbor of scene.neighbors || []) { - if(neighbor.terrain) { - const modelMatrix = mat4Translation(new Float32Array(16), neighbor.offset); - drawMesh( - neighbor.terrain.mesh, modelMatrix, neighbor.terrain.texture, - [NEIGHBOR_DARKEN, NEIGHBOR_DARKEN, NEIGHBOR_DARKEN, NEIGHBOR_ALPHA], false, - ); - } - - for(const model of neighbor.models) { - const worldOffset = [ - neighbor.offset[0] + model.offset[0], - neighbor.offset[1] + model.offset[1], - neighbor.offset[2] + model.offset[2], - ]; - const modelMatrix = mat4Translation(new Float32Array(16), worldOffset); - const color = [ - model.color[0] * NEIGHBOR_DARKEN, - model.color[1] * NEIGHBOR_DARKEN, - model.color[2] * NEIGHBOR_DARKEN, - model.color[3] * NEIGHBOR_ALPHA, - ]; - drawMesh(model.mesh, modelMatrix, model.texture, color, false); - } - } - - if(scene.highlight) { - const { x, y, z } = scene.highlight; - const highlightMesh = buildHighlightMesh(x, y, z); - drawMesh(highlightMesh, identity, null, [1, 0.92, 0.23, 1], false, gl.LINE_LOOP); - } - } - - return { gl, createMesh, createTextureFromImage, render }; - } - - return Object.freeze({ create, computeCamera }); -})(); diff --git a/editor/client/public/template.html b/editor/client/public/template.html deleted file mode 100644 index 5ea9aba2..00000000 --- a/editor/client/public/template.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - Dusk Map Editor - - - - -
    - - - diff --git a/editor/server/server.py b/editor/server/server.py deleted file mode 100644 index 8985ce34..00000000 --- a/editor/server/server.py +++ /dev/null @@ -1,298 +0,0 @@ -""" -Map Editor HTTP Server - -Serves editor/client/public/ as static files with smart routing: - / -> index.html - /page -> page.html or page/index.html - /index -> index.html - -API endpoints (all paths relative to project root): - GET /api/ls?path= directory listing - GET /api/read?path= file contents - GET /api/find?path=&ext=<.json> recursive file listing by extension - POST /api/write?path= write file (JSON body: {"content":"..."}) - POST /api/mkdir?path= create directory - POST /api/delete?path= delete a file - POST /api/compile-chunk?x=&y=&z= recompile a chunk JSON to .dcf -""" - -import os -import re -import sys -import json -import mimetypes -import subprocess -from http.server import HTTPServer, BaseHTTPRequestHandler -from urllib.parse import urlparse, parse_qs, unquote -from pathlib import Path - -CHUNK_COORD_RE = re.compile(r"^-?\d+$") - -PORT = 3000 -SCRIPT_DIR = Path(__file__).parent.resolve() -PUBLIC_DIR = SCRIPT_DIR.parent / "client" / "public" -PROJECT_ROOT = SCRIPT_DIR.parent.parent.resolve() - - -class Handler(BaseHTTPRequestHandler): - def do_GET(self): - parsed = urlparse(self.path) - path = unquote(parsed.path) - if path.startswith("/api/"): - params = parse_qs(parsed.query) - if path == "/api/ls": - self.api_ls(params) - elif path == "/api/read": - self.api_read(params) - elif path == "/api/find": - self.api_find(params) - else: - self.send_error(404) - else: - self.serve_static(path) - - def do_POST(self): - parsed = urlparse(self.path) - params = parse_qs(parsed.query) - if parsed.path == "/api/write": - self.api_write(params) - elif parsed.path == "/api/mkdir": - self.api_mkdir(params) - elif parsed.path == "/api/delete": - self.api_delete(params) - elif parsed.path == "/api/compile-chunk": - self.api_compile_chunk(params) - else: - self.send_error(404) - - # ------------------------------------------------------------------ - # Helpers - # ------------------------------------------------------------------ - - def resolve_project_path(self, rel): - rel = rel.lstrip("/") - target = (PROJECT_ROOT / rel).resolve() - if not str(target).startswith(str(PROJECT_ROOT)): - return None - return target - - def send_json(self, data, code=200): - body = json.dumps(data, indent=2).encode("utf-8") - self.send_response(code) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", len(body)) - self.end_headers() - self.wfile.write(body) - - def read_body(self): - length = int(self.headers.get("Content-Length", 0)) - return self.rfile.read(length) - - # ------------------------------------------------------------------ - # API handlers - # ------------------------------------------------------------------ - - def api_ls(self, params): - rel = params.get("path", [""])[0] - target = self.resolve_project_path(rel) - if target is None: - self.send_json({"error": "invalid path"}, 400) - return - if not target.exists(): - self.send_json({"error": "path not found"}, 404) - return - if not target.is_dir(): - self.send_json({"error": "not a directory"}, 400) - return - - entries = [] - for entry in sorted(target.iterdir(), key=lambda e: (e.is_file(), e.name)): - entries.append({ - "name": entry.name, - "type": "dir" if entry.is_dir() else "file", - "size": entry.stat().st_size if entry.is_file() else None, - }) - self.send_json({ - "path": str(target.relative_to(PROJECT_ROOT)), - "entries": entries, - }) - - def api_read(self, params): - rel = params.get("path", [""])[0] - target = self.resolve_project_path(rel) - if target is None: - self.send_json({"error": "invalid path"}, 400) - return - if not target.exists(): - self.send_json({"error": "file not found"}, 404) - return - if not target.is_file(): - self.send_json({"error": "not a file"}, 400) - return - - try: - content = target.read_text(encoding="utf-8") - self.send_json({ - "path": str(target.relative_to(PROJECT_ROOT)), - "content": content, - }) - except UnicodeDecodeError: - import base64 - content = base64.b64encode(target.read_bytes()).decode("ascii") - self.send_json({ - "path": str(target.relative_to(PROJECT_ROOT)), - "content": content, - "encoding": "base64", - }) - - def api_write(self, params): - rel = params.get("path", [""])[0] - target = self.resolve_project_path(rel) - if target is None: - self.send_json({"error": "invalid path"}, 400) - return - - body = self.read_body() - try: - data = json.loads(body) - content = data.get("content", "") - except (json.JSONDecodeError, UnicodeDecodeError): - content = body.decode("utf-8") - - target.parent.mkdir(parents=True, exist_ok=True) - target.write_text(content, encoding="utf-8") - self.send_json({"ok": True, "path": str(target.relative_to(PROJECT_ROOT))}) - - def api_mkdir(self, params): - rel = params.get("path", [""])[0] - target = self.resolve_project_path(rel) - if target is None: - self.send_json({"error": "invalid path"}, 400) - return - target.mkdir(parents=True, exist_ok=True) - self.send_json({"ok": True, "path": str(target.relative_to(PROJECT_ROOT))}) - - def api_delete(self, params): - rel = params.get("path", [""])[0] - target = self.resolve_project_path(rel) - if target is None: - self.send_json({"error": "invalid path"}, 400) - return - if target.is_file(): - target.unlink() - self.send_json({"ok": True, "path": str(target.relative_to(PROJECT_ROOT))}) - - def api_find(self, params): - rel = params.get("path", [""])[0] - ext = params.get("ext", [""])[0] - target = self.resolve_project_path(rel) - if target is None: - self.send_json({"error": "invalid path"}, 400) - return - if not target.is_dir(): - self.send_json({"error": "not a directory"}, 400) - return - - pattern = f"*{ext}" if ext else "*" - paths = sorted( - str(p.relative_to(target)).replace(os.sep, "/") - for p in target.rglob(pattern) - if p.is_file() - ) - self.send_json({"path": str(target.relative_to(PROJECT_ROOT)), "files": paths}) - - def api_compile_chunk(self, params): - x = params.get("x", [""])[0] - y = params.get("y", [""])[0] - z = params.get("z", [""])[0] - if not (CHUNK_COORD_RE.match(x) and CHUNK_COORD_RE.match(y) and CHUNK_COORD_RE.match(z)): - self.send_json({"error": "invalid chunk coordinates"}, 400) - return - - json_path = ( - PROJECT_ROOT / "assetsraw" / "maps" / "overworld" / "chunks" - / f"chunk_{x}_{y}_{z}.json" - ) - if not json_path.is_file(): - self.send_json({"error": "chunk json not found"}, 404) - return - - result = subprocess.run( - [sys.executable, "-m", "tools.asset.chunk", str(json_path)], - cwd=str(PROJECT_ROOT), - capture_output=True, - text=True, - ) - self.send_json({ - "ok": result.returncode == 0, - "returncode": result.returncode, - "stdout": result.stdout, - "stderr": result.stderr, - }) - - # ------------------------------------------------------------------ - # Static file serving - # ------------------------------------------------------------------ - - def serve_static(self, path): - clean = path.lstrip("/") - candidates = [] - - if clean == "" or clean == "index" or clean == "index.html": - candidates = ["index.html"] - else: - candidates.append(clean) - if "." not in Path(clean).name: - candidates.append(clean + ".html") - candidates.append(clean + "/index.html") - - for candidate in candidates: - file_path = PUBLIC_DIR / candidate - if file_path.is_file(): - self.serve_file(file_path) - return - - self.send_error(404) - - def serve_file(self, file_path): - mime, _ = mimetypes.guess_type(str(file_path)) - mime = mime or "application/octet-stream" - - template = PUBLIC_DIR / "template.html" - if ( - mime == "text/html" - and file_path.name != "template.html" - and template.is_file() - ): - fragment = file_path.read_text(encoding="utf-8") - shell = template.read_text(encoding="utf-8") - marker = shell.find('id="editor"') - if marker == -1: - content = shell.encode("utf-8") - else: - tag_end = shell.index(">", marker) + 1 - close_idx = shell.index("", tag_end) - content = (shell[:tag_end] + fragment + shell[close_idx:]).encode("utf-8") - else: - content = file_path.read_bytes() - - self.send_response(200) - self.send_header("Content-Type", mime) - self.send_header("Content-Length", len(content)) - self.end_headers() - self.wfile.write(content) - - def log_message(self, fmt, *args): - print(f" {self.address_string()} {fmt % args}") - - -if __name__ == "__main__": - print(f"Map Editor") - print(f" http://localhost:{PORT}") - print(f" public : {PUBLIC_DIR}") - print(f" project: {PROJECT_ROOT}") - server = HTTPServer(("", PORT), Handler) - try: - server.serve_forever() - except KeyboardInterrupt: - print("\nStopped.") diff --git a/editor/start.sh b/editor/start.sh deleted file mode 100755 index e1871d04..00000000 --- a/editor/start.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -e -cd "$(dirname "$0")" -python3 server/server.py diff --git a/scripts/build-gamecube-docker.sh b/scripts/gamecube/build-gamecube-docker.sh similarity index 78% rename from scripts/build-gamecube-docker.sh rename to scripts/gamecube/build-gamecube-docker.sh index 69d2777b..1af6d466 100755 --- a/scripts/build-gamecube-docker.sh +++ b/scripts/gamecube/build-gamecube-docker.sh @@ -1,3 +1,3 @@ #!/bin/bash docker build -t dusk-dolphin -f docker/dolphin/Dockerfile . -docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-gamecube.sh" \ No newline at end of file +docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/gamecube/build-gamecube.sh" \ No newline at end of file diff --git a/scripts/build-gamecube-iso-docker.sh b/scripts/gamecube/build-gamecube-iso-docker.sh similarity index 76% rename from scripts/build-gamecube-iso-docker.sh rename to scripts/gamecube/build-gamecube-iso-docker.sh index 24975feb..4dcea118 100755 --- a/scripts/build-gamecube-iso-docker.sh +++ b/scripts/gamecube/build-gamecube-iso-docker.sh @@ -1,3 +1,3 @@ #!/bin/bash docker build -t dusk-dolphin -f docker/dolphin/Dockerfile . -docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-gamecube-iso.sh" +docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/gamecube/build-gamecube-iso.sh" diff --git a/scripts/build-gamecube-iso.sh b/scripts/gamecube/build-gamecube-iso.sh similarity index 100% rename from scripts/build-gamecube-iso.sh rename to scripts/gamecube/build-gamecube-iso.sh diff --git a/scripts/build-gamecube.sh b/scripts/gamecube/build-gamecube.sh similarity index 100% rename from scripts/build-gamecube.sh rename to scripts/gamecube/build-gamecube.sh diff --git a/scripts/build-knulli-docker.sh b/scripts/knulli/build-knulli-docker.sh similarity index 80% rename from scripts/build-knulli-docker.sh rename to scripts/knulli/build-knulli-docker.sh index 26f8bfc6..4deaab17 100755 --- a/scripts/build-knulli-docker.sh +++ b/scripts/knulli/build-knulli-docker.sh @@ -1,3 +1,3 @@ #!/bin/bash docker build -t dusk-knulli -f docker/knulli/Dockerfile . -docker run --rm -v "$(pwd):/workdir" dusk-knulli /bin/bash -c "./scripts/build-knulli.sh" \ No newline at end of file +docker run --rm -v "$(pwd):/workdir" dusk-knulli /bin/bash -c "./scripts/knulli/build-knulli.sh" \ No newline at end of file diff --git a/scripts/build-knulli.sh b/scripts/knulli/build-knulli.sh similarity index 100% rename from scripts/build-knulli.sh rename to scripts/knulli/build-knulli.sh diff --git a/scripts/build-linux-docker.sh b/scripts/linux/build-linux-docker.sh similarity index 81% rename from scripts/build-linux-docker.sh rename to scripts/linux/build-linux-docker.sh index b46fddd9..8ec3e6cc 100755 --- a/scripts/build-linux-docker.sh +++ b/scripts/linux/build-linux-docker.sh @@ -1,3 +1,3 @@ #!/bin/bash docker build -t dusk-linux -f docker/linux/Dockerfile . -docker run --rm -v "$(pwd):/workdir" dusk-linux /bin/bash -c "./scripts/build-linux.sh" \ No newline at end of file +docker run --rm -v "$(pwd):/workdir" dusk-linux /bin/bash -c "./scripts/linux/build-linux.sh" \ No newline at end of file diff --git a/scripts/build-linux.sh b/scripts/linux/build-linux.sh similarity index 100% rename from scripts/build-linux.sh rename to scripts/linux/build-linux.sh diff --git a/scripts/test-linux-docker.sh b/scripts/linux/test-linux-docker.sh similarity index 75% rename from scripts/test-linux-docker.sh rename to scripts/linux/test-linux-docker.sh index d5c70904..57adf7ce 100755 --- a/scripts/test-linux-docker.sh +++ b/scripts/linux/test-linux-docker.sh @@ -4,4 +4,4 @@ docker run \ --rm \ -v "${GITHUB_WORKSPACE}:/workdir" \ dusk-linux \ - /bin/bash -c "./scripts/test-linux.sh" \ No newline at end of file + /bin/bash -c "./scripts/linux/test-linux.sh" \ No newline at end of file diff --git a/scripts/test-linux.sh b/scripts/linux/test-linux.sh similarity index 100% rename from scripts/test-linux.sh rename to scripts/linux/test-linux.sh diff --git a/scripts/build-psp-docker.sh b/scripts/psp/build-psp-docker.sh similarity index 84% rename from scripts/build-psp-docker.sh rename to scripts/psp/build-psp-docker.sh index 348fdce2..13a2d3d0 100755 --- a/scripts/build-psp-docker.sh +++ b/scripts/psp/build-psp-docker.sh @@ -1,3 +1,3 @@ #!/bin/bash docker build -t dusk-psp -f docker/psp/Dockerfile . -docker run --rm -v "$(pwd):/workdir" dusk-psp /bin/bash -c "./scripts/build-psp.sh" \ No newline at end of file +docker run --rm -v "$(pwd):/workdir" dusk-psp /bin/bash -c "./scripts/psp/build-psp.sh" \ No newline at end of file diff --git a/scripts/build-psp.sh b/scripts/psp/build-psp.sh similarity index 100% rename from scripts/build-psp.sh rename to scripts/psp/build-psp.sh diff --git a/scripts/build-vita-docker.sh b/scripts/vita/build-vita-docker.sh similarity index 82% rename from scripts/build-vita-docker.sh rename to scripts/vita/build-vita-docker.sh index a0308cb4..733359df 100755 --- a/scripts/build-vita-docker.sh +++ b/scripts/vita/build-vita-docker.sh @@ -1,3 +1,3 @@ #!/bin/bash docker build -t dusk-vita -f docker/vita/Dockerfile . -docker run --rm -v "$(pwd):/workdir" dusk-vita /bin/bash -c "./scripts/build-vita.sh" +docker run --rm -v "$(pwd):/workdir" dusk-vita /bin/bash -c "./scripts/vita/build-vita.sh" diff --git a/scripts/build-vita.sh b/scripts/vita/build-vita.sh similarity index 100% rename from scripts/build-vita.sh rename to scripts/vita/build-vita.sh diff --git a/scripts/build-wii-docker.sh b/scripts/wii/build-wii-docker.sh similarity index 83% rename from scripts/build-wii-docker.sh rename to scripts/wii/build-wii-docker.sh index 087f551a..6bfbdc88 100755 --- a/scripts/build-wii-docker.sh +++ b/scripts/wii/build-wii-docker.sh @@ -1,3 +1,3 @@ #!/bin/bash docker build -t dusk-dolphin -f docker/dolphin/Dockerfile . -docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-wii.sh" \ No newline at end of file +docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/wii/build-wii.sh" \ No newline at end of file diff --git a/scripts/build-wii-iso-docker.sh b/scripts/wii/build-wii-iso-docker.sh similarity index 80% rename from scripts/build-wii-iso-docker.sh rename to scripts/wii/build-wii-iso-docker.sh index cd05520a..cde6ba70 100755 --- a/scripts/build-wii-iso-docker.sh +++ b/scripts/wii/build-wii-iso-docker.sh @@ -1,3 +1,3 @@ #!/bin/bash docker build -t dusk-dolphin -f docker/dolphin/Dockerfile . -docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-wii-iso.sh" +docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/wii/build-wii-iso.sh" diff --git a/scripts/build-wii-iso.sh b/scripts/wii/build-wii-iso.sh similarity index 100% rename from scripts/build-wii-iso.sh rename to scripts/wii/build-wii-iso.sh diff --git a/scripts/build-wii.sh b/scripts/wii/build-wii.sh similarity index 100% rename from scripts/build-wii.sh rename to scripts/wii/build-wii.sh diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index c0347f89..b8c57116 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -81,11 +81,9 @@ add_subdirectory(error) add_subdirectory(input) add_subdirectory(locale) add_subdirectory(perf) -add_subdirectory(rpg) add_subdirectory(scene) add_subdirectory(system) add_subdirectory(time) -add_subdirectory(ui) add_subdirectory(save) add_subdirectory(util) add_subdirectory(thread) \ No newline at end of file diff --git a/src/dusk/animation/CMakeLists.txt b/src/dusk/animation/CMakeLists.txt index a03d91ad..db67ed5e 100644 --- a/src/dusk/animation/CMakeLists.txt +++ b/src/dusk/animation/CMakeLists.txt @@ -6,6 +6,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC easing.c - animation.c - keyframe.c ) diff --git a/src/dusk/animation/animation.c b/src/dusk/animation/animation.c deleted file mode 100644 index 65fa2b20..00000000 --- a/src/dusk/animation/animation.c +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2026 Dominic Masters -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -#include "animation.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/math.h" - -void animationInit( - animation_t *anim, - keyframe_t *keyframes, - uint16_t *keyframeCounts, - const uint16_t layerCount -) { - assertNotNull(anim, "Animation pointer cannot be null."); - assertNotNull(keyframes, "Keyframes pointer cannot be null."); - assertNotNull(keyframeCounts, "Keyframe counts pointer cannot be null."); - assertTrue(layerCount > 0, "Layer count must be greater than zero."); - - memoryZero(anim, sizeof(animation_t)); - anim->keyframes = keyframes; - anim->keyframeCounts = keyframeCounts; - anim->layerCount = layerCount; - - // Determine duration - float_t duration = 0.0f; - for(uint16_t layer = 0; layer < layerCount; layer++) { - uint16_t keyframeCount = keyframeCounts[layer]; - assertTrue(keyframeCount > 0, "Keyframe count invalid."); - keyframe_t *layerKeyframes = keyframes + layer * keyframeCount; - - #ifdef DUSK_ASSERTIONS - // Check that the keyframes are sorted by time. - for(uint16_t i = 1; i < keyframeCount; i++) { - assertTrue( - layerKeyframes[i].time >= layerKeyframes[i - 1].time, - "Keyframes must be sorted by time." - ); - } - #endif - - keyframe_t *lastKeyframe = layerKeyframes + keyframeCount - 1; - duration = mathMax(duration, lastKeyframe->time); - } - assertTrue(duration > 0, "Animation duration must be greater than 0."); - anim->duration = duration; -} - -float_t animationGetLayerValue(const animation_t *anim, const uint16_t layer) { - assertNotNull(anim, "Animation pointer cannot be null."); - assertTrue(layer < anim->layerCount, "Layer index out of bounds."); - - uint16_t keyframeCount = anim->keyframeCounts[layer]; - keyframe_t *layerKeyframes = anim->keyframes + layer * keyframeCount; - return keyframeGetValue(layerKeyframes, keyframeCount, anim->time); -} - -void animationUpdate( - animation_t *anim, - const float_t deltaTime -) { - assertNotNull(anim, "Animation pointer cannot be null."); - assertTrue(deltaTime >= 0, "Delta time must be non-negative."); - - bool_t justCompleted = false; - - if(!(anim->flags & ANIMATION_FLAG_INTERNAL_COMPLETED)) { - bool_t loop = (anim->flags & ANIMATION_FLAG_LOOP) != 0; - bool_t pingpong = (anim->flags & ANIMATION_FLAG_PINGPONG) != 0; - assertFalse( - loop && pingpong, - "Cannot set both ANIMATION_FLAG_LOOP and ANIMATION_FLAG_PINGPONG." - ); - - bool_t backward; - if(pingpong) { - backward = (anim->flags & ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD) != 0; - } else { - backward = (anim->flags & ANIMATION_FLAG_REVERSE) != 0; - } - - // Resolve boundary crossings one at a time, so a single large deltaTime - // can correctly loop/pingpong across multiple boundaries in one call. - float_t remaining = deltaTime; - while(remaining > 0.0f) { - float_t toBoundary = ( - backward ? anim->time : (anim->duration - anim->time) - ); - if(remaining < toBoundary) { - anim->time += backward ? -remaining : remaining; - break; - } - - remaining -= toBoundary; - anim->time = backward ? 0.0f : anim->duration; - - bool_t stopHere; - if(backward) { - stopHere = (anim->flags & ANIMATION_FLAG_STOP_BEGINNING) != 0; - } else { - stopHere = (anim->flags & ANIMATION_FLAG_STOP_END) != 0; - } - - if(stopHere) { - justCompleted = true; - break; - } else if(pingpong) { - backward = !backward; - if(backward) anim->flags |= ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD; - else anim->flags &= ~ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD; - } else if(loop) { - anim->time = backward ? anim->duration : 0.0f; - if(anim->onLoop) anim->onLoop(anim->user); - } else { - justCompleted = true; - break; - } - } - - if(justCompleted) anim->flags |= ANIMATION_FLAG_INTERNAL_COMPLETED; - } - - // Call onUpdate for each layer. - for(uint16_t layer = 0; layer < anim->layerCount; layer++) { - float_t value = animationGetLayerValue(anim, layer); - if(anim->onUpdate) anim->onUpdate(layer, value, anim->user); - } - - if(justCompleted && anim->onComplete) anim->onComplete(anim->user); -} \ No newline at end of file diff --git a/src/dusk/animation/animation.h b/src/dusk/animation/animation.h deleted file mode 100644 index f8cea8b8..00000000 --- a/src/dusk/animation/animation.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 "keyframe.h" - -#define ANIMATION_FLAG_LOOP (1 << 0) -#define ANIMATION_FLAG_REVERSE (1 << 1) -#define ANIMATION_FLAG_PINGPONG (1 << 2) -#define ANIMATION_FLAG_STOP_BEGINNING (1 << 3) -#define ANIMATION_FLAG_STOP_END (1 << 4) - -// Internal - tracks which direction a pingponging animation is currently -// travelling. Do not set this manually, it is managed by animationUpdate(). -#define ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD (1 << 7) - -// Internal - set once the animation has stopped advancing (see -// animationUpdate()). Do not set this manually. There is currently no way to -// restart a completed animation short of clearing this bit and resetting -// anim->time by hand. -#define ANIMATION_FLAG_INTERNAL_COMPLETED (1 << 6) - -typedef struct { - keyframe_t *keyframes; - uint16_t *keyframeCounts; - uint16_t layerCount; - float_t time; - float_t duration; - uint8_t flags; - - void *user; - void (*onUpdate)(const uint16_t layer, const float_t value, void *user); - void (*onComplete)(void *user); - void (*onLoop)(void *user); -} animation_t; - -/** - * Initializes an animation with the given keyframes and layer count. - * - * @param anim Pointer to the animation to initialize. - * @param keyframes Pointer to the array of keyframes for each layer. - * @param keyframeCount Number of keyframes in each layer. - * @param layerCount Number of layers in the animation. - */ -void animationInit( - animation_t *anim, - keyframe_t *keyframes, - uint16_t *keyframeCounts, - const uint16_t layerCount -); - -/** - * Sets the current time of the animation, clamping it to the valid range. - * This will call the onUpdate callback but none of the other callbacks. - * - * @param anim Pointer to the animation to set the time for. - * @param time The new time to set for the animation. - */ -void animationSetTime(animation_t *anim, const float_t time); - -/** - * Gets the current value of a specific layer in the animation based on the - * current animation time. - */ -float_t animationGetLayerValue(const animation_t *anim, const uint16_t layer); - -/** - * Updates the animation state based on the elapsed time. Advances anim->time - * by deltaTime (or against it, if ANIMATION_FLAG_REVERSE is set), then - * resolves whatever happens when it reaches the 0 or duration boundary: - * - * - ANIMATION_FLAG_PINGPONG: reflects off the boundary and continues playing - * in the opposite direction, forever, unless stopped (see below). - * - ANIMATION_FLAG_LOOP: wraps back around to the other boundary and keeps - * playing in the same direction, forever, unless stopped (see below). - * - ANIMATION_FLAG_STOP_BEGINNING / ANIMATION_FLAG_STOP_END: when the - * animation reaches that specific boundary, it clamps there and stops - * (firing onComplete) instead of looping/pingponging past it. - * - If none of the above apply at a boundary, the animation clamps there and - * stops, firing onComplete. - * - * onUpdate is called for every layer on every call. onLoop is called each - * time a loop wraps around. onComplete is called at most once, the moment - * the animation stops advancing. - * - * @param anim Pointer to the animation to update. - * @param deltaTime Time elapsed since the last update (in seconds). - */ -void animationUpdate( - animation_t *anim, - const float_t deltaTime -); \ No newline at end of file diff --git a/src/dusk/animation/keyframe.c b/src/dusk/animation/keyframe.c deleted file mode 100644 index a50338ef..00000000 --- a/src/dusk/animation/keyframe.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 "keyframe.h" -#include "assert/assert.h" -#include "util/math.h" - -float_t keyframeGetValue( - const keyframe_t *keyframes, - const uint32_t keyframeCount, - const float_t time -) { - assertNotNull(keyframes, "Keyframes pointer cannot be null."); - assertTrue(keyframeCount > 0, "Keyframe count must be more than 0."); - assertTrue(time >= 0, "Time must be non-negative."); - #ifdef DUSK_ASSERTIONS - // Checks that the keyframes are sorted by time. - for(uint32_t i = 1; i < keyframeCount; i++) { - assertTrue( - keyframes[i].time >= keyframes[i - 1].time, - "Keyframes must be sorted by time." - ); - } - #endif - - keyframe_t *last = (keyframe_t *)(keyframes + keyframeCount - 1); - if(time >= last->time) return last->value; - - // Since time < last->time (checked above), current is guaranteed to stop - // at or before reaching last, so no separate end-of-array check is needed. - keyframe_t *current = (keyframe_t *)keyframes; - keyframe_t *start = current; - while(current->time <= time) { - start = current; - current++; - } - keyframe_t *end = current; - - float_t t = (time - start->time) / (end->time - start->time); - return mathLerp(start->value, end->value, easingApply(start->easing, t)); -} \ No newline at end of file diff --git a/src/dusk/animation/keyframe.h b/src/dusk/animation/keyframe.h deleted file mode 100644 index 96a0a4f6..00000000 --- a/src/dusk/animation/keyframe.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 "easing.h" - -typedef struct { - float_t time; - float_t value; - easingtype_t easing; -} keyframe_t; - -/** - * Gets the value of a keyframe at a given time. - * - * @param keyframes The keyframes to get the value from. - * @param keyframeCount The number of keyframes in the array. - * @param time The time at which to get the value, in seconds. - * @return The value of the keyframe at the given time. - */ -float_t keyframeGetValue( - const keyframe_t *keyframes, - const uint32_t keyframeCount, - const float_t time -); \ No newline at end of file diff --git a/src/dusk/asset/loader/CMakeLists.txt b/src/dusk/asset/loader/CMakeLists.txt index 21932838..5ef3dccf 100644 --- a/src/dusk/asset/loader/CMakeLists.txt +++ b/src/dusk/asset/loader/CMakeLists.txt @@ -15,7 +15,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} add_subdirectory(display) add_subdirectory(locale) add_subdirectory(json) -add_subdirectory(chunk) add_subdirectory(dmf) add_subdirectory(wav) add_subdirectory(mp3) \ No newline at end of file diff --git a/src/dusk/asset/loader/assetloader.c b/src/dusk/asset/loader/assetloader.c index ad808032..c66a5f28 100644 --- a/src/dusk/asset/loader/assetloader.c +++ b/src/dusk/asset/loader/assetloader.c @@ -46,12 +46,6 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = { .dispose = assetJsonDispose }, - [ASSET_LOADER_TYPE_CHUNK] = { - .loadSync = assetChunkLoaderSync, - .loadAsync = assetChunkLoaderAsync, - .dispose = assetChunkDispose - }, - [ASSET_LOADER_TYPE_WAV] = { .loadSync = assetWavLoaderSync, .loadAsync = assetWavLoaderAsync, diff --git a/src/dusk/asset/loader/assetloader.h b/src/dusk/asset/loader/assetloader.h index c18c108f..ae339546 100644 --- a/src/dusk/asset/loader/assetloader.h +++ b/src/dusk/asset/loader/assetloader.h @@ -12,7 +12,6 @@ #include "asset/loader/display/assettilesetloader.h" #include "asset/loader/locale/assetlocaleloader.h" #include "asset/loader/json/assetjsonloader.h" -#include "asset/loader/chunk/assetchunkloader.h" #include "asset/loader/wav/assetwavloader.h" #include "asset/loader/mp3/assetmp3loader.h" @@ -25,7 +24,6 @@ typedef enum { ASSET_LOADER_TYPE_TILESET, ASSET_LOADER_TYPE_LOCALE, ASSET_LOADER_TYPE_JSON, - ASSET_LOADER_TYPE_CHUNK, ASSET_LOADER_TYPE_WAV, ASSET_LOADER_TYPE_MP3, @@ -39,7 +37,6 @@ typedef union { assettilesetloaderloading_t tileset; assetlocaleloaderloading_t locale; assetjsonloaderloading_t json; - assetchunkloaderloading_t chunk; assetwavloaderloading_t wav; assetmp3loaderloading_t mp3; } assetloaderloading_t; @@ -51,7 +48,6 @@ typedef union { assettilesetoutput_t tileset; assetlocaleoutput_t locale; assetjsonoutput_t json; - assetchunkoutput_t chunk; assetwavoutput_t wav; assetmp3output_t mp3; } assetloaderoutput_t; @@ -61,7 +57,6 @@ typedef union { assettilesetloaderinput_t tileset; assetlocaleloaderinput_t locale; assetjsonloaderinput_t json; - assetchunkloaderinput_t chunk; assetwavloaderinput_t wav; assetmp3loaderinput_t mp3; } assetloaderinput_t; diff --git a/src/dusk/asset/loader/chunk/CMakeLists.txt b/src/dusk/asset/loader/chunk/CMakeLists.txt deleted file mode 100644 index 64e16290..00000000 --- a/src/dusk/asset/loader/chunk/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 - assetchunkloader.c -) diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.c b/src/dusk/asset/loader/chunk/assetchunkloader.c deleted file mode 100644 index a2325027..00000000 --- a/src/dusk/asset/loader/chunk/assetchunkloader.c +++ /dev/null @@ -1,484 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "assetchunkloader.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "util/string.h" -#include "asset/asset.h" -#include "asset/loader/assetloading.h" -#include "asset/loader/assetentry.h" -#include "asset/loader/assetloader.h" -#include "asset/loader/json/assetjsonloader.h" -#include "display/color.h" -#include "display/texture/texture.h" -#include "rpg/overworld/worldpos.h" -#include "yyjson.h" - -#define ASSET_CHUNK_TERRAIN_TEXTURE "tiles.png" - -// Per-shape corner Z offsets (sw, se, ne, nw) as offsets from a tile's own -// base Z, ported 1:1 from tools/asset/chunk/__main__.py's _RAMP_CORNERS. -// TILE_SHAPE_NULL's row is never read - assetChunkTilesHaveTerrain/ -// assetChunkBuildTerrain skip those tiles outright. -static const float_t CHUNK_RAMP_CORNERS[TILE_SHAPE_COUNT][4] = { - [TILE_SHAPE_GROUND] = {0.0f, 0.0f, 0.0f, 0.0f}, - [TILE_SHAPE_RAMP_NORTH] = {0.0f, 0.0f, 1.0f, 1.0f}, - [TILE_SHAPE_RAMP_SOUTH] = {1.0f, 1.0f, 0.0f, 0.0f}, - [TILE_SHAPE_RAMP_EAST] = {0.0f, 1.0f, 1.0f, 0.0f}, - [TILE_SHAPE_RAMP_WEST] = {1.0f, 0.0f, 0.0f, 1.0f}, - [TILE_SHAPE_RAMP_NORTHEAST] = {0.0f, 0.0f, 1.0f, 0.0f}, - [TILE_SHAPE_RAMP_NORTHWEST] = {0.0f, 0.0f, 0.0f, 1.0f}, - [TILE_SHAPE_RAMP_SOUTHEAST] = {0.0f, 1.0f, 0.0f, 0.0f}, - [TILE_SHAPE_RAMP_SOUTHWEST] = {1.0f, 0.0f, 0.0f, 0.0f}, - [TILE_SHAPE_RAMP_NORTHEAST_INNER] = {0.0f, 1.0f, 1.0f, 1.0f}, - [TILE_SHAPE_RAMP_NORTHWEST_INNER] = {1.0f, 0.0f, 1.0f, 1.0f}, - [TILE_SHAPE_RAMP_SOUTHEAST_INNER] = {1.0f, 1.0f, 1.0f, 0.0f}, - [TILE_SHAPE_RAMP_SOUTHWEST_INNER] = {1.0f, 1.0f, 0.0f, 1.0f}, -}; - -errorret_t assetChunkLoaderAsync(assetloading_t *loading) { - assertNotNull(loading, "Loading cannot be NULL"); - assertNotMainThread("Should be called from an async thread."); - errorOk(); -} - -static bool_t assetChunkTilesHaveTerrain(const tile_t *tiles) { - for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) { - if(tiles[i].shape != TILE_SHAPE_NULL) return true; - } - return false; -} - -// Reads the "tiles" array - a dense, fixed-size CHUNK_TILE_COUNT grid, -// one slot per (x, y) column at index x + y * CHUNK_WIDTH (position is -// implicit in the index, not stored). Each slot is either `null` (empty -// column, already zeroed = TILE_SHAPE_NULL) or a 2-element [type, z] -// array. -static void assetChunkParseTiles(yyjson_val *root, tile_t *tiles) { - memoryZero(tiles, CHUNK_TILE_COUNT * sizeof(tile_t)); - - yyjson_val *tilesArr = yyjson_obj_get(root, "tiles"); - if(!yyjson_is_arr(tilesArr)) return; - - size_t idx, len; - yyjson_val *tileVal; - yyjson_arr_foreach(tilesArr, idx, len, tileVal) { - if(idx >= CHUNK_TILE_COUNT) break; - if(!yyjson_is_arr(tileVal) || yyjson_arr_size(tileVal) < 2) continue; - - int32_t type = (int32_t)yyjson_get_int(yyjson_arr_get(tileVal, 0)); - int32_t z = (int32_t)yyjson_get_int(yyjson_arr_get(tileVal, 1)); - tiles[idx] = (tile_t){ .shape = (tileshape_t)type, .z = (uint8_t)z }; - } -} - -// Resolves "meshes" (external building references, by bare filename) into -// out->modelNames/meshOffsets starting at *ioIndex, advancing it as -// entries are written. Filenames are resolved as -// "models/buildings/.json" - a hardcoded stand-in for -// tools/asset/chunk/__main__.py's find_model() recursive search, valid -// as long as every building model lives flat under that one directory -// (true today - see the raw-json-maps plan notes for the tradeoff). -static void assetChunkParseMeshes( - yyjson_val *root, assetchunkoutput_t *out, uint8_t *ioIndex -) { - yyjson_val *meshesArr = yyjson_obj_get(root, "meshes"); - if(!yyjson_is_arr(meshesArr)) return; - - size_t idx, len; - yyjson_val *meshVal; - yyjson_arr_foreach(meshesArr, idx, len, meshVal) { - if(*ioIndex >= CHUNK_MESH_COUNT_MAX) break; - - yyjson_val *fileVal = yyjson_obj_get(meshVal, "file"); - if(!fileVal || !yyjson_is_str(fileVal)) continue; - - char_t baseName[CHUNK_MESH_NAME_MAX]; - stringCopy(baseName, yyjson_get_str(fileVal), sizeof(baseName)); - char_t *dot = stringFindLastChar(baseName, '.'); - if(dot != NULL) *dot = '\0'; - - uint8_t m = *ioIndex; - stringFormat( - out->modelNames[m], CHUNK_MESH_NAME_MAX, - "models/buildings/%s.json", baseName - ); - - vec3 offset = {0.0f, 0.0f, 0.0f}; - yyjson_val *posArr = yyjson_obj_get(meshVal, "pos"); - if(yyjson_is_arr(posArr) && yyjson_arr_size(posArr) >= 3) { - offset[0] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 0)); - offset[1] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 1)); - offset[2] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 2)); - } - memoryCopy(out->meshOffsets[m], offset, sizeof(vec3)); - - (*ioIndex)++; - } -} - -static void assetChunkParseEntities(yyjson_val *root, assetchunkoutput_t *out) { - out->entitySpawnCount = 0; - - yyjson_val *arr = yyjson_obj_get(root, "entities"); - if(!yyjson_is_arr(arr)) return; - - size_t idx, len; - yyjson_val *val; - yyjson_arr_foreach(arr, idx, len, val) { - if(out->entitySpawnCount >= CHUNK_ENTITY_SPAWN_COUNT_MAX) break; - - const char_t *type = yyjson_get_str(yyjson_obj_get(val, "type")); - yyjson_val *posArr = yyjson_obj_get(val, "pos"); - if(type == NULL || !yyjson_is_arr(posArr) || yyjson_arr_size(posArr) < 3) { - continue; - } - worldpos_t pos = { - .x = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 0)), - .y = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 1)), - .z = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 2)), - }; - - chunkentityspawn_t *spawn = &out->entitySpawns[out->entitySpawnCount]; - if(stringEquals(type, "global")) { - spawn->kind = CHUNK_ENTITY_SPAWN_KIND_GLOBAL; - spawn->globalId = - (uint16_t)yyjson_get_int(yyjson_obj_get(val, "globalId")); - spawn->itemId = 0; - spawn->itemQuantity = 0; - } else if(stringEquals(type, "item")) { - spawn->kind = CHUNK_ENTITY_SPAWN_KIND_ITEM; - spawn->globalId = 0; - spawn->itemId = (uint16_t)yyjson_get_int(yyjson_obj_get(val, "itemId")); - spawn->itemQuantity = - (uint8_t)yyjson_get_int(yyjson_obj_get(val, "quantity")); - } else { - continue; - } - spawn->position = pos; - out->entitySpawnCount++; - } -} - -static void assetChunkParseAreas(yyjson_val *root, assetchunkoutput_t *out) { - out->areaSpawnCount = 0; - - yyjson_val *arr = yyjson_obj_get(root, "areas"); - if(!yyjson_is_arr(arr)) return; - - size_t idx, len; - yyjson_val *val; - yyjson_arr_foreach(arr, idx, len, val) { - if(out->areaSpawnCount >= CHUNK_AREA_COUNT_MAX) break; - - yyjson_val *minArr = yyjson_obj_get(val, "min"); - yyjson_val *maxArr = yyjson_obj_get(val, "max"); - if( - !yyjson_is_arr(minArr) || yyjson_arr_size(minArr) < 3 || - !yyjson_is_arr(maxArr) || yyjson_arr_size(maxArr) < 3 - ) continue; - - chunkareaspawn_t *area = &out->areaSpawns[out->areaSpawnCount]; - area->min = (worldpos_t){ - .x = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 0)), - .y = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 1)), - .z = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 2)), - }; - area->max = (worldpos_t){ - .x = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 0)), - .y = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 1)), - .z = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 2)), - }; - area->callbackId = - (uint16_t)yyjson_get_int(yyjson_obj_get(val, "callbackId")); - area->notify = (uint8_t)yyjson_get_int(yyjson_obj_get(val, "notify")); - area->trigger = (uint8_t)yyjson_get_int(yyjson_obj_get(val, "trigger")); - out->areaSpawnCount++; - } -} - -// Six vertices (2 CCW triangles) for one tile's quad, matching -// tools/asset/chunk/__main__.py's _tile_quad. Corner order: SW=(x,y), -// SE=(x+1,y), NE=(x+1,y+1), NW=(x,y+1). NORTH=+Y, EAST=+X. -static void assetChunkAppendTileQuad( - meshvertex_t *verts, uint32_t *vertCount, - const uint32_t x, const uint32_t y, - const float_t sw, const float_t se, const float_t ne, const float_t nw -) { - const float_t u0 = (float_t)x / (float_t)CHUNK_WIDTH; - const float_t u1 = (float_t)(x + 1) / (float_t)CHUNK_WIDTH; - const float_t v0 = (float_t)y / (float_t)CHUNK_HEIGHT; - const float_t v1 = (float_t)(y + 1) / (float_t)CHUNK_HEIGHT; - const float_t fx = (float_t)x; - const float_t fy = (float_t)y; - - meshvertex_t *v = verts + *vertCount; - v[0] = (meshvertex_t){ .uv = {u0, v0}, .pos = {fx, fy, sw} }; - v[1] = (meshvertex_t){ .uv = {u1, v0}, .pos = {fx + 1, fy, se} }; - v[2] = (meshvertex_t){ .uv = {u1, v1}, .pos = {fx + 1, fy + 1, ne} }; - v[3] = (meshvertex_t){ .uv = {u0, v0}, .pos = {fx, fy, sw} }; - v[4] = (meshvertex_t){ .uv = {u1, v1}, .pos = {fx + 1, fy + 1, ne} }; - v[5] = (meshvertex_t){ .uv = {u0, v1}, .pos = {fx, fy + 1, nw} }; - *vertCount += 6; -} - -// Builds a mesh_t straight from the tile grid - one quad per non-null -// tile column, shaped by CHUNK_RAMP_CORNERS - the runtime equivalent of -// tools/asset/chunk/__main__.py's build_terrain_verts. Only called once -// assetChunkTilesHaveTerrain has confirmed at least one real tile exists. -// Mirrors assetMeshLoaderSync's meshInit/meshFlush/free-after-upload -// sequence (assetmeshloader.c) since this produces the same -// assetmeshoutput_t shape, just from generated verts instead of a file. -static errorret_t assetChunkBuildTerrain( - const tile_t *tiles, assetmeshoutput_t *out -) { - meshvertex_t *verts = memoryAllocate( - CHUNK_TILE_COUNT * 6 * sizeof(meshvertex_t) - ); - uint32_t vertCount = 0; - - for(uint32_t y = 0; y < CHUNK_HEIGHT; y++) { - for(uint32_t x = 0; x < CHUNK_WIDTH; x++) { - const tile_t *tile = &tiles[x + y * CHUNK_WIDTH]; - if(tile->shape == TILE_SHAPE_NULL) continue; - - const float_t *corners = CHUNK_RAMP_CORNERS[tile->shape]; - const float_t fz = (float_t)tile->z; - assetChunkAppendTileQuad( - verts, &vertCount, x, y, - (fz + corners[0]) * WORLD_LAYER_HEIGHT, - (fz + corners[1]) * WORLD_LAYER_HEIGHT, - (fz + corners[2]) * WORLD_LAYER_HEIGHT, - (fz + corners[3]) * WORLD_LAYER_HEIGHT - ); - } - } - - assertTrue(vertCount > 0, "Terrain build called with no terrain tiles."); - - out->vertices = verts; - errorret_t ret = meshInit( - &out->mesh, MESH_PRIMITIVE_TYPE_TRIANGLES, (int32_t)vertCount, verts - ); - if(errorIsNotOk(ret)) { - memoryFree(out->vertices); - out->vertices = NULL; - errorChain(ret); - } - out->meshInitialized = true; - - ret = meshFlush(&out->mesh, 0, (int32_t)vertCount); - if(errorIsNotOk(ret)) { - meshDispose(&out->mesh); - out->meshInitialized = false; - memoryFree(out->vertices); - out->vertices = NULL; - errorChain(ret); - } - - #if defined(DUSK_OPENGL) && !defined(DUSK_OPENGL_LEGACY) - memoryFree(out->vertices); - out->vertices = NULL; - #endif - - errorOk(); -} - -errorret_t assetChunkLoaderSync(assetloading_t *loading) { - assertNotNull(loading, "Loading cannot be NULL"); - assertTrue(loading->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type."); - assertIsMainThread("Must be called from the main thread."); - - assetchunkoutput_t *out = &loading->entry->data.chunk; - - switch(loading->loading.chunk.state) { - case ASSET_CHUNK_LOADING_STATE_INITIAL: - loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_PARSE; - loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; - errorOk(); - - case ASSET_CHUNK_LOADING_STATE_PARSE: - break; - - case ASSET_CHUNK_LOADING_STATE_LOAD_MODELS: - while(loading->loading.chunk.modelIndex < out->meshCount) { - uint8_t m = loading->loading.chunk.modelIndex; - if(out->modelEntries[m] == NULL) { - out->modelEntries[m] = assetLock( - out->modelNames[m], ASSET_LOADER_TYPE_MODEL, NULL - ); - assertNotNull(out->modelEntries[m], "Failed to lock model."); - loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; - errorOk(); - } - if(out->modelEntries[m]->state == ASSET_ENTRY_STATE_ERROR) { - assetLoaderErrorThrow( - loading, "Model failed to load: %s", out->modelNames[m] - ); - } - if(out->modelEntries[m]->state != ASSET_ENTRY_STATE_LOADED) { - loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; - errorOk(); - } - loading->loading.chunk.modelIndex++; - } - loading->entry->state = ASSET_ENTRY_STATE_LOADED; - errorOk(); - - default: - errorOk(); - } - - // --- PARSE: lock+require the chunk's own JSON as a sub-asset, exactly - // like assetmodelloader.c's ASSET_MODEL_LOADING_STATE_LOCK_ASSETS does - // for its model descriptor, then read everything out of it before - // unlocking (the yyjson_doc/vals only stay valid while jsonEntry does). - char_t jsonKey[ASSET_FILE_NAME_MAX]; - stringFormat(jsonKey, sizeof(jsonKey), "json:%s", loading->entry->name); - assetloaderinput_t jsonInput; - memoryZero(&jsonInput, sizeof(jsonInput)); - stringCopy( - jsonInput.json.path, loading->entry->name, ASSET_FILE_NAME_MAX - ); - assetentry_t *jsonEntry = - assetLock(jsonKey, ASSET_LOADER_TYPE_JSON, &jsonInput); - errorret_t jsonRet = assetRequireLoaded(jsonEntry); - if(errorIsNotOk(jsonRet)) { - assetUnlockEntry(jsonEntry); - assetLoaderErrorThrow( - loading, "Failed to load chunk JSON: %s", loading->entry->name - ); - } - - yyjson_val *root = yyjson_doc_get_root(jsonEntry->data.json); - - tile_t tiles[CHUNK_TILE_COUNT]; - assetChunkParseTiles(root, tiles); - out->hasTerrain = assetChunkTilesHaveTerrain(tiles); - - uint8_t meshIndex = out->hasTerrain ? 1 : 0; - assetChunkParseMeshes(root, out, &meshIndex); - out->meshCount = meshIndex; - - assetChunkParseEntities(root, out); - assetChunkParseAreas(root, out); - - assetUnlockEntry(jsonEntry); - - out->tiles = memoryAllocate(CHUNK_TILE_COUNT * sizeof(tile_t)); - memoryCopy(out->tiles, tiles, CHUNK_TILE_COUNT * sizeof(tile_t)); - - // Terrain: build the mesh, then wrap it in synthetic (non-cache, - // non-refcounted) mesh/model entries so modelEntries[0] behaves exactly - // like a real file-backed model entry for every downstream consumer - - // see the header's doc comment on hasTerrain/terrainMeshEntry. - if(out->hasTerrain) { - assetmeshoutput_t terrainMesh; - memoryZero(&terrainMesh, sizeof(terrainMesh)); - errorret_t terrainRet = assetChunkBuildTerrain(out->tiles, &terrainMesh); - if(errorIsNotOk(terrainRet)) { - memoryFree(out->tiles); - out->tiles = NULL; - assetLoaderErrorThrow( - loading, "Failed to build chunk terrain mesh: %s", - loading->entry->name - ); - } - - assetloaderinput_t texInput = { .texture = TEXTURE_FORMAT_RGBA }; - assetentry_t *texEntry = assetLock( - ASSET_CHUNK_TERRAIN_TEXTURE, ASSET_LOADER_TYPE_TEXTURE, &texInput - ); - errorret_t texRet = assetRequireLoaded(texEntry); - if(errorIsNotOk(texRet)) { - assetUnlockEntry(texEntry); - meshDispose(&terrainMesh.mesh); - memoryFree(out->tiles); - out->tiles = NULL; - assetLoaderErrorThrow( - loading, "Failed to load chunk terrain texture: %s", - loading->entry->name - ); - } - out->terrainTexEntry = texEntry; - - out->terrainMeshEntry = memoryAllocate(sizeof(assetentry_t)); - memoryZero(out->terrainMeshEntry, sizeof(assetentry_t)); - out->terrainMeshEntry->type = ASSET_LOADER_TYPE_MESH; - out->terrainMeshEntry->state = ASSET_ENTRY_STATE_LOADED; - out->terrainMeshEntry->data.mesh = terrainMesh; - - out->terrainModelEntry = memoryAllocate(sizeof(assetentry_t)); - memoryZero(out->terrainModelEntry, sizeof(assetentry_t)); - out->terrainModelEntry->type = ASSET_LOADER_TYPE_MODEL; - out->terrainModelEntry->state = ASSET_ENTRY_STATE_LOADED; - out->terrainModelEntry->data.model.meshEntry = out->terrainMeshEntry; - out->terrainModelEntry->data.model.texEntry = out->terrainTexEntry; - out->terrainModelEntry->data.model.color = COLOR_WHITE; - - out->modelEntries[0] = out->terrainModelEntry; - } - - if(out->meshCount == 0) { - loading->entry->state = ASSET_ENTRY_STATE_LOADED; - errorOk(); - } - - loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_LOAD_MODELS; - loading->loading.chunk.modelIndex = out->hasTerrain ? 1 : 0; - loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; - errorOk(); -} - -errorret_t assetChunkDispose(assetentry_t *entry) { - assertNotNull(entry, "Entry cannot be NULL"); - assertTrue(entry->type == ASSET_LOADER_TYPE_CHUNK, "Invalid type."); - assertIsMainThread("Must be called from the main thread."); - - assetchunkoutput_t *out = &entry->data.chunk; - - if(out->tiles != NULL) { - memoryFree(out->tiles); - out->tiles = NULL; - } - - for(uint8_t m = 0; m < out->meshCount; m++) { - if(out->modelEntries[m] == NULL) continue; - if(out->hasTerrain && m == 0) continue; // synthetic - freed below. - assetUnlockEntry(out->modelEntries[m]); - out->modelEntries[m] = NULL; - } - - if(out->hasTerrain) { - if(out->terrainMeshEntry != NULL) { - assetmeshoutput_t *meshOut = &out->terrainMeshEntry->data.mesh; - if(meshOut->meshInitialized) { - meshDispose(&meshOut->mesh); - meshOut->meshInitialized = false; - } - if(meshOut->vertices != NULL) { - memoryFree(meshOut->vertices); - meshOut->vertices = NULL; - } - memoryFree(out->terrainMeshEntry); - out->terrainMeshEntry = NULL; - } - if(out->terrainModelEntry != NULL) { - memoryFree(out->terrainModelEntry); - out->terrainModelEntry = NULL; - } - if(out->terrainTexEntry != NULL) { - assetUnlockEntry(out->terrainTexEntry); - out->terrainTexEntry = NULL; - } - out->modelEntries[0] = NULL; - out->hasTerrain = false; - } - - errorOk(); -} diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.h b/src/dusk/asset/loader/chunk/assetchunkloader.h deleted file mode 100644 index 38e76032..00000000 --- a/src/dusk/asset/loader/chunk/assetchunkloader.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 "asset/assetfile.h" -#include "rpg/overworld/chunk.h" - -typedef struct assetloading_s assetloading_t; -typedef struct assetentry_s assetentry_t; - -typedef struct { - void *nothing; -} assetchunkloaderinput_t; - -typedef enum { - ASSET_CHUNK_LOADING_STATE_INITIAL, - ASSET_CHUNK_LOADING_STATE_PARSE, - ASSET_CHUNK_LOADING_STATE_LOAD_MODELS, - ASSET_CHUNK_LOADING_STATE_DONE -} assetchunkloadingstate_t; - -typedef struct { - assetchunkloadingstate_t state; - - // Index into assetchunkoutput_t.modelNames/modelEntries the external - // (non-terrain) model polling loop has reached - see - // ASSET_CHUNK_LOADING_STATE_LOAD_MODELS. - uint8_t modelIndex; -} assetchunkloaderloading_t; - -typedef enum { - CHUNK_ENTITY_SPAWN_KIND_GLOBAL, - CHUNK_ENTITY_SPAWN_KIND_ITEM -} chunkentityspawnkind_t; - -typedef struct { - chunkentityspawnkind_t kind; - uint16_t globalId; // Valid when kind == CHUNK_ENTITY_SPAWN_KIND_GLOBAL. - uint16_t itemId; // Valid when kind == CHUNK_ENTITY_SPAWN_KIND_ITEM. - uint8_t itemQuantity; // Valid when kind == CHUNK_ENTITY_SPAWN_KIND_ITEM. - worldpos_t position; -} chunkentityspawn_t; - -typedef struct { - worldpos_t min; - worldpos_t max; - uint16_t callbackId; // Index into MAP_AREA_CALLBACK_LIST. - uint8_t notify; - uint8_t trigger; -} chunkareaspawn_t; - -typedef struct { - tile_t *tiles; - uint8_t meshCount; - char_t modelNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; - vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; - assetentry_t *modelEntries[CHUNK_MESH_COUNT_MAX]; - - // Terrain mesh generated at load time from the tile grid (no baked - // .dmf/.json - see assetChunkBuildTerrain in assetchunkloader.c). NULL - // (with hasTerrain false) for a chunk with no non-null tiles, in which - // case modelEntries[0] (if meshCount > 0) is the first external mesh - // instead - matches the old baked pipeline's "only write a terrain - // model if it has vertices" behavior. - // - // terrainMeshEntry/terrainModelEntry are heap-allocated, plain - // assetentry_t values that are never passed to assetLock/assetGetEntry/ - // assetUnlockEntry (only a real, name-keyed, ref-counted entry may be) - // - they exist purely so modelEntries[0] can point at something with - // the same ->state/->data.model/->data.mesh.mesh shape every other - // model/mesh entry has, so chunk.c/map.c/sceneoverworld.c don't need to - // know terrain isn't file-backed. Freed directly (not assetUnlockEntry) - // in assetChunkDispose. terrainTexEntry is a real, ref-counted texture - // entry (the shared "tiles.png") and must be assetUnlockEntry'd there. - bool_t hasTerrain; - assetentry_t *terrainMeshEntry; - assetentry_t *terrainModelEntry; - assetentry_t *terrainTexEntry; - - uint8_t entitySpawnCount; - chunkentityspawn_t entitySpawns[CHUNK_ENTITY_SPAWN_COUNT_MAX]; - - uint8_t areaSpawnCount; - chunkareaspawn_t areaSpawns[CHUNK_AREA_COUNT_MAX]; -} assetchunkoutput_t; - -/** - * Async loader stub for chunk assets. Chunk loading is performed entirely - * on the main thread (it delegates to the JSON asset loader and to - * meshInit/meshFlush, both main-thread-only); this callback is never - * reached. - * - * @param loading Loading information for the asset being loaded. - * @return Error code indicating success or failure of the load operation. - */ -errorret_t assetChunkLoaderAsync(assetloading_t *loading); - -/** - * Synchronous loader for chunk assets. Locks the chunk's JSON descriptor - * as a sub-asset, parses tiles/external mesh references/entity spawns/ - * area spawns, generates the terrain mesh directly from the tile grid, - * and locks/waits for every referenced model (and the terrain's shared - * tileset texture) to finish loading. - * - * @param loading Loading information for the asset being loaded. - * @return Error code indicating success or failure of the load operation. - */ -errorret_t assetChunkLoaderSync(assetloading_t *loading); - -/** - * Disposer for chunk assets. - * - * @param entry Asset entry containing the chunk data to dispose. - * @return Error code indicating success or failure of the dispose operation. - */ -errorret_t assetChunkDispose(assetentry_t *entry); diff --git a/src/dusk/display/display.c b/src/dusk/display/display.c index 98596d8e..7e7e07ca 100644 --- a/src/dusk/display/display.c +++ b/src/dusk/display/display.c @@ -16,7 +16,6 @@ #include "display/mesh/capsule.h" #include "display/mesh/triprism.h" #include "display/screen/screen.h" -#include "ui/ui.h" #include "display/text/text.h" #include "assert/assert.h" #include "util/memory.h" diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index f1420c4a..674eb357 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -11,11 +11,9 @@ #include "time/time.h" #include "input/input.h" #include "locale/localemanager.h" -#include "rpg/rpg.h" #include "display/display.h" #include "scene/scene.h" #include "asset/asset.h" -#include "ui/ui.h" #include "assert/assert.h" #ifdef DUSK_NETWORK #include "network/network.h" @@ -47,8 +45,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(localeManagerInit()); errorChain(displayInit()); errorChain(audioInit()); - errorChain(rpgInit()); - errorChain(uiInit()); #ifdef DUSK_NETWORK errorChain(networkInit()); #endif @@ -62,7 +58,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { consolePrint("Assertions real"); #endif - sceneSet(SCENE_TYPE_INITIAL); errorOk(); } @@ -77,10 +72,8 @@ errorret_t engineUpdate(void) { inputUpdate(); consoleUpdate(); errorChain(audioUpdateEarly()); - errorChain(rpgUpdate()); errorChain(sceneUpdate()); errorChain(assetUpdate()); - errorChain(uiUpdate()); // Render errorChain(displayUpdate()); @@ -98,9 +91,7 @@ errorret_t engineDispose(void) { #ifdef DUSK_NETWORK errorChain(networkDispose()); #endif - errorChain(rpgDispose()); localeManagerDispose(); - errorChain(uiDispose()); errorChain(audioDispose()); consoleDispose(); errorChain(displayDispose()); diff --git a/src/dusk/locale/localemanager.c b/src/dusk/locale/localemanager.c index 878a7e53..b71b1990 100644 --- a/src/dusk/locale/localemanager.c +++ b/src/dusk/locale/localemanager.c @@ -9,7 +9,6 @@ #include "util/memory.h" #include "util/string.h" #include "assert/assert.h" -#include "ui/ui.h" #include "system/system.h" #include "console/console.h" @@ -39,9 +38,6 @@ errorret_t localeManagerSetLocale(const localeinfo_t *locale) { assetEntryLock(LOCALE.entry); errorChain(assetRequireLoaded(LOCALE.entry)); - // TODO : Trigger UI update. - errorChain(uiUpdateTranslations()); - errorOk(); } 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/CMakeLists.txt b/src/dusk/rpg/battle/fighter/CMakeLists.txt deleted file mode 100644 index bc721bd2..00000000 --- a/src/dusk/rpg/battle/fighter/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 - battlefighter.c -) 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/CMakeLists.txt b/src/dusk/rpg/battle/move/CMakeLists.txt deleted file mode 100644 index cdbe40d2..00000000 --- a/src/dusk/rpg/battle/move/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 - battlemove.c -) 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/CMakeLists.txt b/src/dusk/rpg/cutscene/item/item/CMakeLists.txt deleted file mode 100644 index df5ebc21..00000000 --- a/src/dusk/rpg/cutscene/item/item/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 - cutsceneitemgive.c -) 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/CMakeLists.txt b/src/dusk/rpg/entity/item/CMakeLists.txt deleted file mode 100644 index 6834d19b..00000000 --- a/src/dusk/rpg/entity/item/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 - entityitem.c -) 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/CMakeLists.txt b/src/dusk/rpg/entity/npc/CMakeLists.txt deleted file mode 100644 index 922a4e1d..00000000 --- a/src/dusk/rpg/entity/npc/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 - -# Sources -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - npc.c - npcturn.c - npcwalk.c - npcpath.c -) 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/CMakeLists.txt b/src/dusk/rpg/overworld/global/CMakeLists.txt deleted file mode 100644 index 27caaac2..00000000 --- a/src/dusk/rpg/overworld/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 -) 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/save/save.c b/src/dusk/save/save.c index 9c8ece44..1c5ab819 100644 --- a/src/dusk/save/save.c +++ b/src/dusk/save/save.c @@ -8,7 +8,6 @@ #include "save.h" #include "util/memory.h" #include "assert/assert.h" -#include "rpg/cutscene/cutscenesystem.h" #include "console/console.h" save_t SAVE; @@ -167,7 +166,7 @@ void saveOnDeviceAvailabilityChecked(savedevice_t *device, void *user) { } bool_t saveCanSave() { - return CUTSCENE_SYSTEM.scene == NULL; + return true; } errorret_t saveSaveSettings() { diff --git a/src/dusk/save/save.h b/src/dusk/save/save.h index 2d13e701..92078fbc 100644 --- a/src/dusk/save/save.h +++ b/src/dusk/save/save.h @@ -69,7 +69,7 @@ void saveOnDeviceAvailabilityChecked(savedevice_t *device, void *user); /** * Whether the game is in a state where saving is currently allowed. - * Currently just checks that no cutscene is running. + * Currently always allowed. * * @return True if saving is currently allowed, false otherwise. */ diff --git a/src/dusk/save/slot/saveslot.c b/src/dusk/save/slot/saveslot.c index cb966442..e989fd6c 100644 --- a/src/dusk/save/slot/saveslot.c +++ b/src/dusk/save/slot/saveslot.c @@ -19,19 +19,6 @@ errorret_t saveSlotInit(saveslot_t *slot) { memoryZero(slot, sizeof(saveslot_t)); slot->version = 1; - // Set the default story flags. - for(storyflag_t flag = 0; flag < STORY_FLAG_COUNT; flag++) { - slot->storyFlags[flag] = STORY_FLAG_MAP[flag].initial; - } - - // Set the default party. - errorChain(partyInit(&slot->party)); - - // Set the default current cutscene. - stringCopy( - slot->currentCutscene, SAVE_SLOT_CUTSCENE_DEFAULT, - sizeof(slot->currentCutscene) - 1 - ); errorOk(); } @@ -39,23 +26,9 @@ errorret_t saveSlotInit(saveslot_t *slot) { errorret_t saveSlotNewGame(void) { errorChain(saveSlotInit(&SAVE.slot)); - stringCopy( - SAVE.slot.cachedData.mapName, SAVE_SLOT_MAP_NAME_DEFAULT, - sizeof(SAVE.slot.cachedData.mapName) - ); - errorOk(); } -void saveSlotSetCurrentCutscene(const char_t *file) { - assertNotNull(file, "File cannot be null"); - - stringCopy( - SAVE.slot.currentCutscene, file, sizeof(SAVE.slot.currentCutscene) - 1 - ); - SAVE.slotDirty = true; -} - bool_t saveSlotInUse(saveslotcache_t *slot) { assertNotNull(slot, "Slot cannot be null"); return slot->name[0] != '\0'; @@ -80,11 +53,7 @@ errorret_t saveSlotReadJSON(saveslot_t *slot, yyjson_val *object) { assertNotNull(object, "Object cannot be null"); // saveSlotInit() now takes slot directly, so this can reset through it - // rather than duplicating the memSet/version stub - saveSlotCurrent - // ReadJSON() fully overwrites every field it sets anyway (including - // party.members/order), so seeding a fresh party.jsonc roster here only - // to replace it is wasted work, but keeps this in one reset path rather - // than two subtly different ones. + // rather than duplicating the reset stub here. errorChain(saveSlotInit(slot)); errorChain(saveSlotCurrentReadJSON(slot, object)); diff --git a/src/dusk/save/slot/saveslot.h b/src/dusk/save/slot/saveslot.h index 6a934f08..9e80df22 100644 --- a/src/dusk/save/slot/saveslot.h +++ b/src/dusk/save/slot/saveslot.h @@ -7,9 +7,6 @@ #pragma once #include "save/savedevice.h" -#include "rpg/overworld/map.h" -#include "rpg/battle/party.h" -#include "rpg/story/story.h" #include "time/timeepoch.h" #include "asset/assetfile.h" #include "yyjson.h" @@ -19,15 +16,11 @@ #define SAVE_SLOT_COUNT 3 #endif -#define SAVE_SLOT_MAP_NAME_DEFAULT "overworld" -#define SAVE_SLOT_CUTSCENE_NAME_MAX ASSET_FILE_NAME_MAX -#define SAVE_SLOT_CUTSCENE_DEFAULT "cutscenes/new_game.jsonc" typedef struct { char_t name[SAVE_SLOT_NAME_LENGTH + 1];// 8 characters + null terminator dusktimeepoch_t time; int32_t playerLevel; - char_t mapName[MAP_NAME_MAX]; bool_t corrupt; } saveslotcache_t; @@ -35,47 +28,26 @@ typedef struct saveslot_s { uint8_t version; uint8_t dataType; saveslotcache_t cachedData; - party_t party; - storyflagval_t storyFlags[STORY_FLAG_COUNT_MAX]; - // Asset path of the cutscene currently in progress for this slot (e.g. - // resumed on load) - defaults to SAVE_SLOT_CUTSCENE_DEFAULT on a fresh - // slot (see saveSlotInit). - char_t currentCutscene[SAVE_SLOT_CUTSCENE_NAME_MAX]; } saveslot_t; /** * Inits slot with the default state, this is functionally "new game" but * will not set the player name, as that is what we use to determine if a - * save slot is "in use" or not. Seeds the party roster from party.jsonc - * (see partyInit). + * save slot is "in use" or not. * * @param slot The save slot to initialize. - * @return Error if the starting party roster (party.jsonc) fails to - * load/parse. + * @return Error state if any. */ errorret_t saveSlotInit(saveslot_t *slot); /** - * Sets up the current save slot (SAVE.slot) for a brand new game: calls - * saveSlotInit (which seeds the starting party roster) and sets the - * default starting map. Use this (not saveSlotInit directly) whenever a - * new save is actually being created for a player. Callers creating a - * new save at a specific index should set SAVE.slotCurrent first. + * Sets up the current save slot (SAVE.slot) for a brand new game. Callers + * creating a new save at a specific index should set SAVE.slotCurrent first. * * @return Error if saveSlotInit fails. */ errorret_t saveSlotNewGame(void); -/** - * Sets the current save slot's (SAVE.slot) in-progress cutscene and - * dirties the save (SAVE.slotDirty), same as storySetFlagValue - so the - * next save write persists it. Use this (not writing SAVE.slot. - * currentCutscene directly) anywhere gameplay needs to change it. - * - * @param file Asset path of the cutscene now in progress, or "" for none. - */ -void saveSlotSetCurrentCutscene(const char_t *file); - /** * Checks if the save slot is in use, this is determined by checking if the * player name is set or not. diff --git a/src/dusk/save/slot/saveslotcurrent.c b/src/dusk/save/slot/saveslotcurrent.c index 76c199e4..5e433ba2 100644 --- a/src/dusk/save/slot/saveslotcurrent.c +++ b/src/dusk/save/slot/saveslotcurrent.c @@ -28,18 +28,6 @@ errorret_t saveSlotCurrentWriteJSON( stringCopy(ver1.name, slot->cachedData.name, sizeof(ver1.name)); ver1.time = slot->cachedData.time; ver1.playerLevel = slot->cachedData.playerLevel; - stringCopy(ver1.mapName, slot->cachedData.mapName, sizeof(ver1.mapName)); - - memoryCopy( - ver1.partyMembers, slot->party.members, sizeof(ver1.partyMembers) - ); - memoryCopy(ver1.partyOrder, slot->party.order, sizeof(ver1.partyOrder)); - - memoryCopy(ver1.storyFlags, slot->storyFlags, sizeof(ver1.storyFlags)); - - stringCopy( - ver1.currentCutscene, slot->currentCutscene, sizeof(ver1.currentCutscene) - ); errorChain(saveSlotVer1WriteJSON(&ver1, doc, object)); errorOk(); @@ -59,21 +47,6 @@ errorret_t saveSlotCurrentReadJSON(saveslot_t *slot, yyjson_val *object) { stringCopy(slot->cachedData.name, ver1.name, sizeof(slot->cachedData.name)); slot->cachedData.time = ver1.time; slot->cachedData.playerLevel = ver1.playerLevel; - stringCopy( - slot->cachedData.mapName, ver1.mapName, sizeof(slot->cachedData.mapName) - ); - - memoryCopy( - slot->party.members, ver1.partyMembers, sizeof(slot->party.members) - ); - memoryCopy(slot->party.order, ver1.partyOrder, sizeof(slot->party.order)); - - memoryCopy(slot->storyFlags, ver1.storyFlags, sizeof(slot->storyFlags)); - - stringCopy( - slot->currentCutscene, ver1.currentCutscene, - sizeof(slot->currentCutscene) - ); errorOk(); } diff --git a/src/dusk/save/slot/saveslotver1.c b/src/dusk/save/slot/saveslotver1.c index 247166b6..6e9d5ef8 100644 --- a/src/dusk/save/slot/saveslotver1.c +++ b/src/dusk/save/slot/saveslotver1.c @@ -22,26 +22,6 @@ errorret_t saveSlotVer1WriteJSON( writeString("name", slot->name); writeTime("time", slot->time); writeInt32("playerLevel", slot->playerLevel); - writeString("mapName", slot->mapName); - - yyjson_mut_val *partyMembersArr = yyjson_mut_arr(doc); - for(uint8_t i = 0; i < PARTY_MEMBER_COUNT_MAX; i++) { - yyjson_mut_val *memberObj = - battleFighterSerialize(&slot->partyMembers[i], doc); - yyjson_mut_arr_add_val(partyMembersArr, memberObj); - } - writeValue("partyMembers", partyMembersArr); - - writeUInt8Array("partyOrder", slot->partyOrder, PARTY_ACTIVE_SIZE_MAX); - - yyjson_mut_val *storyFlagsObj = yyjson_mut_obj_add_obj(doc, object, "storyFlags"); - for(storyflag_t flag = 0; flag < STORY_FLAG_COUNT; flag++) { - yyjson_mut_obj_add_uint( - doc, storyFlagsObj, storyGetNameByFlag(flag), slot->storyFlags[flag] - ); - } - - writeString("currentCutscene", slot->currentCutscene); errorOk(); } @@ -59,47 +39,6 @@ errorret_t saveSlotVer1ReadJSON(saveslotver1_t *slot, yyjson_val *object) { readTime("time", slot->time); readInt32("playerLevel", slot->playerLevel, 1); - requireStrMin("mapName", 1); - readString( - "mapName", slot->mapName, "", SAVE_SLOT_VER1_MAP_NAME_MAX - 1 - ); - - requireArrayOfSize( - "partyMembers", partyMembersArr, PARTY_MEMBER_COUNT_MAX - ); - - size_t memberIdx, memberLen; - yyjson_val *memberObj; - yyjson_arr_foreach( - partyMembersArr, memberIdx, memberLen, memberObj - ) { - battlefighter_t *member = &slot->partyMembers[memberIdx]; - member->id = (uint8_t)memberIdx; - errorChain(battleFighterParse(member, memberObj)); - } - - readUInt8Array( - "partyOrder", slot->partyOrder, PARTY_ACTIVE_SIZE_MAX, - PARTY_ORDER_EMPTY - ); - - yyjson_val *storyFlagsObj = yyjson_obj_get(object, "storyFlags"); - if(storyFlagsObj != NULL) { - yyjson_obj_iter iter = yyjson_obj_iter_with(storyFlagsObj); - yyjson_val *flagKey; - while((flagKey = yyjson_obj_iter_next(&iter))) { - storyflag_t flag = storyGetFlagByName(yyjson_get_str(flagKey)); - if(flag >= STORY_FLAG_COUNT) continue;// Flag no longer exists, ignore - - slot->storyFlags[flag] = - (storyflagval_t)yyjson_get_uint(yyjson_obj_iter_get_val(flagKey)); - } - } - - readString( - "currentCutscene", slot->currentCutscene, SAVE_SLOT_VER1_CUTSCENE_DEFAULT, - SAVE_SLOT_VER1_CUTSCENE_NAME_MAX - 1 - ); errorOk(); } diff --git a/src/dusk/save/slot/saveslotver1.h b/src/dusk/save/slot/saveslotver1.h index 77040c74..6ccbb672 100644 --- a/src/dusk/save/slot/saveslotver1.h +++ b/src/dusk/save/slot/saveslotver1.h @@ -9,37 +9,13 @@ #include "error/error.h" #include "time/timeepoch.h" #include "save/slot/saveslotver.h" -#include "rpg/battle/party.h" -#include "rpg/story/story.h" #include "yyjson.h" // This schema's own frozen copies of save/slot/saveslot.h's field sizes - -// never reference SAVE_SLOT_NAME_LENGTH/MAP_NAME_MAX (or any other live +// never reference SAVE_SLOT_NAME_LENGTH (or any other live // constant) here. Once shipped, this file's on-disk shape must never change -// underneath old save files, even if the live engine constants do. The -// party arrays below are the exception - saveSlotVer1ReadJSON() checks their -// JSON array lengths explicitly, so PARTY_MEMBER_COUNT_MAX/ -// PARTY_ACTIVE_SIZE_MAX (rpg/battle/party.h) can be referenced directly. -// storyFlags is a further exception, for a different reason - it's written -// keyed by flag name (not position), so its on-disk shape is decoupled from -// STORY_FLAG_COUNT_MAX entirely; a save simply loses whatever flags no -// longer resolve by name (see storyGetFlagByName) if that cap ever shrinks. -// -// partyMembers is a THIRD exception, deliberately broken pre-release: its -// per-member shape now comes from battleFighterSerialize/battleFighterParse -// (rpg/battle/fighter/battlefighter.h) - the shared fighter JSON format -// used everywhere else (party.jsonc, BATTLE_START enemies) - instead of its -// own hand-rolled field set. This is a real on-disk break (different key -// names, no more "statAttack" etc.) with no migration - an old v1 save -// simply reads back as corrupt. Acceptable only because nothing has -// shipped yet; don't repeat this once real saves exist without a version -// bump instead. +// underneath old save files, even if the live engine constants do. #define SAVE_SLOT_VER1_NAME_LENGTH 8 -#define SAVE_SLOT_VER1_MAP_NAME_MAX 10 -#define SAVE_SLOT_VER1_CUTSCENE_NAME_MAX 128 -// Own frozen copy of save/slot/saveslot.h's SAVE_SLOT_CUTSCENE_DEFAULT, for -// the same reason as the sizes above - never reference the live constant. -#define SAVE_SLOT_VER1_CUTSCENE_DEFAULT "cutscenes/new_game.jsonc" // Independent from saveslotcache_t (save/slot/saveslot.h) by design - this // is what version 1 actually put on disk, frozen forever. saveSlotCurrent @@ -50,14 +26,6 @@ typedef struct saveslotver1_s { char_t name[SAVE_SLOT_VER1_NAME_LENGTH + 1]; dusktimeepoch_t time; int32_t playerLevel; - char_t mapName[SAVE_SLOT_VER1_MAP_NAME_MAX]; - - battlefighter_t partyMembers[PARTY_MEMBER_COUNT_MAX]; - uint8_t partyOrder[PARTY_ACTIVE_SIZE_MAX]; - - storyflagval_t storyFlags[STORY_FLAG_COUNT_MAX]; - - char_t currentCutscene[SAVE_SLOT_VER1_CUTSCENE_NAME_MAX]; } saveslotver1_t; /** diff --git a/src/dusk/scene/CMakeLists.txt b/src/dusk/scene/CMakeLists.txt index 8273e118..4fef675a 100644 --- a/src/dusk/scene/CMakeLists.txt +++ b/src/dusk/scene/CMakeLists.txt @@ -5,11 +5,8 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC + node.c 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/node.c b/src/dusk/scene/node.c new file mode 100644 index 00000000..90d24131 --- /dev/null +++ b/src/dusk/scene/node.c @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "node.h" +#include "assert/assert.h" +#include "util/memory.h" + +void nodeInit(node_t *node) { + assertNotNull(node, "Node cannot be NULL"); + memoryZero(node, sizeof(node_t)); +} diff --git a/src/dusk/scene/node.h b/src/dusk/scene/node.h new file mode 100644 index 00000000..979eb371 --- /dev/null +++ b/src/dusk/scene/node.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 "error/error.h" + +typedef struct node_s node_t; + +typedef struct node_s { + node_t *parent; + node_t *firstChild; + node_t *nextSibling; +} node_t; + +/** + * Initializes a node to an empty, detached state. + * + * @param node The node to initialize. + */ +void nodeInit(node_t *node); 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 index 72c4e9d9..1873644e 100644 --- a/src/dusk/scene/scene.c +++ b/src/dusk/scene/scene.c @@ -10,7 +10,6 @@ #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" @@ -98,7 +97,6 @@ errorret_t sceneRender(void) { errorChain(displaySetState((displaystate_t){ .flags = DISPLAY_STATE_FLAG_BLEND })); - errorChain(uiRender()); errorOk(); } diff --git a/src/dusk/scene/scenetype.c b/src/dusk/scene/scenetype.c index 3dad6627..8cd954fc 100644 --- a/src/dusk/scene/scenetype.c +++ b/src/dusk/scene/scenetype.c @@ -8,27 +8,6 @@ #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 - }, + [SCENE_TYPE_NULL] = { 0 } }; diff --git a/src/dusk/scene/scenetype.h b/src/dusk/scene/scenetype.h index 677959ee..5b3fc1d3 100644 --- a/src/dusk/scene/scenetype.h +++ b/src/dusk/scene/scenetype.h @@ -7,14 +7,9 @@ #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; + uint8_t unused; } scenedata_t; typedef errorret_t (*scenecallback_t)(scenedata_t *); @@ -29,10 +24,6 @@ typedef struct { typedef enum { SCENE_TYPE_NULL, - SCENE_TYPE_INITIAL, - SCENE_TYPE_OVERWORLD, - SCENE_TYPE_BATTLE, - SCENE_TYPE_COUNT } scenetype_t; 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/uielementlist.h b/src/dusk/ui/uielementlist.h deleted file mode 100644 index 11bbca33..00000000 --- a/src/dusk/ui/uielementlist.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 "ui/uielement.h" - -extern uielement_t UI_ELEMENTS[]; 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/CMakeLists.txt b/test/CMakeLists.txt index c09ae827..d1f2d736 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,8 +10,6 @@ add_subdirectory(console) add_subdirectory(error) add_subdirectory(thread) add_subdirectory(display) -add_subdirectory(rpg) -add_subdirectory(item) add_subdirectory(save) add_subdirectory(time) add_subdirectory(util) \ No newline at end of file diff --git a/test/animation/CMakeLists.txt b/test/animation/CMakeLists.txt index aabb6fdf..9f02402c 100644 --- a/test/animation/CMakeLists.txt +++ b/test/animation/CMakeLists.txt @@ -7,5 +7,3 @@ include(dusktest) # Tests dusktest(test_easing.c) -dusktest(test_keyframe.c) -dusktest(test_animation.c) diff --git a/test/animation/test_animation.c b/test/animation/test_animation.c deleted file mode 100644 index 39eb6b12..00000000 --- a/test/animation/test_animation.c +++ /dev/null @@ -1,443 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "animation/animation.h" -#include "util/memory.h" - -typedef struct { - uint16_t updateCount; - uint16_t loopCount; - uint16_t completeCount; - float_t lastValues[8]; -} animationtestcallbacks_t; - -static void testOnUpdate( - const uint16_t layer, const float_t value, void *user -) { - animationtestcallbacks_t *cb = (animationtestcallbacks_t *)user; - cb->updateCount++; - cb->lastValues[layer] = value; -} - -static void testOnLoop(void *user) { - ((animationtestcallbacks_t *)user)->loopCount++; -} - -static void testOnComplete(void *user) { - ((animationtestcallbacks_t *)user)->completeCount++; -} - -static void animationTestInit( - animation_t *anim, - animationtestcallbacks_t *cb, - keyframe_t *keyframes, - uint16_t *keyframeCounts, - const uint16_t layerCount -) { - memoryZero(cb, sizeof(animationtestcallbacks_t)); - animationInit(anim, keyframes, keyframeCounts, layerCount); - anim->user = cb; - anim->onUpdate = testOnUpdate; - anim->onLoop = testOnLoop; - anim->onComplete = testOnComplete; -} - -static void test_animationInitSingleLayerDuration(void **state) { - keyframe_t keyframes[3] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 3 }; - animation_t anim; - - animationInit(&anim, keyframes, keyframeCounts, 1); - - assert_float_equal(anim.duration, 2.0f, 0.0001f); - assert_int_equal(anim.layerCount, 1); - assert_float_equal(anim.time, 0.0f, 0.0001f); - assert_int_equal(anim.flags, 0); -} - -static void test_animationInitMultiLayerDurationIsMax(void **state) { - // Both layers must share the same keyframe count - animationInit/ - // animationGetLayerValue flatten the array as layer * keyframeCounts[layer], - // which only produces the correct offset when every layer's count matches. - keyframe_t keyframes[4] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - { .time = 0.0f, .value = 100.0f, .easing = EASING_LINEAR }, - { .time = 3.0f, .value = 300.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[2] = { 2, 2 }; - animation_t anim; - - animationInit(&anim, keyframes, keyframeCounts, 2); - - // Layer 1's last keyframe (time=3) is later than layer 0's (time=1). - assert_float_equal(anim.duration, 3.0f, 0.0001f); -} - -static void test_animationInitNullAsserts(void **state) { - keyframe_t keyframes[1] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 1 }; - animation_t anim; - - expect_assert_failure(animationInit(NULL, keyframes, keyframeCounts, 1)); - expect_assert_failure(animationInit(&anim, NULL, keyframeCounts, 1)); - expect_assert_failure(animationInit(&anim, keyframes, NULL, 1)); -} - -static void test_animationInitZeroLayerCountAsserts(void **state) { - keyframe_t keyframes[1] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 1 }; - animation_t anim; - - expect_assert_failure(animationInit(&anim, keyframes, keyframeCounts, 0)); -} - -static void test_animationInitUnsortedKeyframesAsserts(void **state) { - keyframe_t keyframes[2] = { - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - - expect_assert_failure(animationInit(&anim, keyframes, keyframeCounts, 1)); -} - -static void test_animationGetLayerValueSamplesEachLayerIndependently( - void **state -) { - keyframe_t keyframes[4] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - { .time = 0.0f, .value = 100.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 0.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[2] = { 2, 2 }; - animation_t anim; - - animationInit(&anim, keyframes, keyframeCounts, 2); - anim.time = 1.0f; - - assert_float_equal(animationGetLayerValue(&anim, 0), 10.0f, 0.0001f); - assert_float_equal(animationGetLayerValue(&anim, 1), 50.0f, 0.0001f); -} - -static void test_animationGetLayerValueOutOfBoundsAsserts(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - - animationInit(&anim, keyframes, keyframeCounts, 1); - expect_assert_failure(animationGetLayerValue(&anim, 1)); -} - -static void test_animationUpdateAdvancesTimeAndCallsOnUpdate(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - animationUpdate(&anim, 0.5f); - - assert_float_equal(anim.time, 0.5f, 0.0001f); - assert_int_equal(cb.updateCount, 1); - assert_float_equal(cb.lastValues[0], 5.0f, 0.0001f); - assert_int_equal(cb.completeCount, 0); -} - -static void test_animationUpdatePlainPlayStopsAtEndAndFiresOnceOnly( - void **state -) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - - // Overshoot the duration in a single update. - animationUpdate(&anim, 5.0f); - assert_float_equal(anim.time, 2.0f, 0.0001f); - assert_float_equal(cb.lastValues[0], 20.0f, 0.0001f); - assert_int_equal(cb.completeCount, 1); - - // Further updates must not clamp past the end or refire onComplete. - animationUpdate(&anim, 1.0f); - assert_float_equal(anim.time, 2.0f, 0.0001f); - assert_int_equal(cb.completeCount, 1); -} - -static void test_animationUpdateExactLandingOnEndFiresOnComplete(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - - // deltaTime exactly matches the remaining distance to the boundary. - animationUpdate(&anim, 2.0f); - assert_float_equal(anim.time, 2.0f, 0.0001f); - assert_int_equal(cb.completeCount, 1); -} - -static void test_animationUpdateLoopWrapsAndFiresOnLoop(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - anim.flags = ANIMATION_FLAG_LOOP; - - // 2.5 durations worth of time: wraps twice, lands at 1.0 into the third. - animationUpdate(&anim, 5.0f); - - assert_float_equal(anim.time, 1.0f, 0.0001f); - assert_int_equal(cb.loopCount, 2); - assert_int_equal(cb.completeCount, 0); -} - -static void test_animationUpdateLoopStopEndCompletesInsteadOfWrapping( - void **state -) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - anim.flags = ANIMATION_FLAG_LOOP | ANIMATION_FLAG_STOP_END; - - animationUpdate(&anim, 5.0f); - - assert_float_equal(anim.time, 2.0f, 0.0001f); - assert_int_equal(cb.loopCount, 0); - assert_int_equal(cb.completeCount, 1); -} - -static void test_animationUpdateReversePlaysBackward(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - anim.flags = ANIMATION_FLAG_REVERSE; - anim.time = anim.duration; - - animationUpdate(&anim, 0.5f); - - assert_float_equal(anim.time, 1.5f, 0.0001f); - assert_float_equal(cb.lastValues[0], 15.0f, 0.0001f); -} - -static void test_animationUpdateReverseStopsAtBeginning(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - anim.flags = ANIMATION_FLAG_REVERSE; - anim.time = anim.duration; - - animationUpdate(&anim, 10.0f); - - assert_float_equal(anim.time, 0.0f, 0.0001f); - assert_float_equal(cb.lastValues[0], 0.0f, 0.0001f); - assert_int_equal(cb.completeCount, 1); -} - -static void test_animationUpdatePingpongBouncesForeverWithoutStopFlags( - void **state -) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - anim.flags = ANIMATION_FLAG_PINGPONG; - - // 0 -> 2 (2s consumed), reflect, backward 1s more -> time=1, going backward. - animationUpdate(&anim, 3.0f); - assert_float_equal(anim.time, 1.0f, 0.0001f); - assert_true((anim.flags & ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD) != 0); - assert_int_equal(cb.completeCount, 0); - - // Continue backward to 0 (1s), reflect, forward 1s more -> time=1, forward. - animationUpdate(&anim, 2.0f); - assert_float_equal(anim.time, 1.0f, 0.0001f); - assert_true((anim.flags & ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD) == 0); - assert_int_equal(cb.completeCount, 0); -} - -static void test_animationUpdatePingpongStopEndCompletesAtEnd(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - anim.flags = ANIMATION_FLAG_PINGPONG | ANIMATION_FLAG_STOP_END; - - animationUpdate(&anim, 10.0f); - - assert_float_equal(anim.time, 2.0f, 0.0001f); - assert_int_equal(cb.completeCount, 1); -} - -static void test_animationUpdatePingpongStopBeginningCompletesAtStart( - void **state -) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 1); - anim.flags = ANIMATION_FLAG_PINGPONG | ANIMATION_FLAG_STOP_BEGINNING; - anim.time = anim.duration; - anim.flags |= ANIMATION_FLAG_INTERNAL_PINGPONG_BACKWARD; - - animationUpdate(&anim, 10.0f); - - assert_float_equal(anim.time, 0.0f, 0.0001f); - assert_int_equal(cb.completeCount, 1); -} - -static void test_animationUpdateMultiLayerCallsOnUpdatePerLayer(void **state) { - keyframe_t keyframes[4] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - { .time = 0.0f, .value = 100.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 0.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[2] = { 2, 2 }; - animation_t anim; - animationtestcallbacks_t cb; - - animationTestInit(&anim, &cb, keyframes, keyframeCounts, 2); - animationUpdate(&anim, 1.0f); - - assert_int_equal(cb.updateCount, 2); - assert_float_equal(cb.lastValues[0], 10.0f, 0.0001f); - assert_float_equal(cb.lastValues[1], 50.0f, 0.0001f); -} - -static void test_animationUpdateNullAsserts(void **state) { - expect_assert_failure(animationUpdate(NULL, 1.0f)); -} - -static void test_animationUpdateNegativeDeltaTimeAsserts(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - - animationInit(&anim, keyframes, keyframeCounts, 1); - expect_assert_failure(animationUpdate(&anim, -1.0f)); -} - -static void test_animationInitZeroDurationAsserts(void **state) { - keyframe_t keyframes[1] = { - { .time = 0.0f, .value = 5.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 1 }; - animation_t anim; - - expect_assert_failure(animationInit(&anim, keyframes, keyframeCounts, 1)); -} - -static void test_animationUpdateLoopAndPingpongTogetherAsserts(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - }; - uint16_t keyframeCounts[1] = { 2 }; - animation_t anim; - - animationInit(&anim, keyframes, keyframeCounts, 1); - anim.flags = ANIMATION_FLAG_LOOP | ANIMATION_FLAG_PINGPONG; - expect_assert_failure(animationUpdate(&anim, 0.1f)); -} - -int main(int argc, char **argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_animationInitSingleLayerDuration), - cmocka_unit_test(test_animationInitMultiLayerDurationIsMax), - cmocka_unit_test(test_animationInitNullAsserts), - cmocka_unit_test(test_animationInitZeroLayerCountAsserts), - cmocka_unit_test(test_animationInitUnsortedKeyframesAsserts), - cmocka_unit_test(test_animationGetLayerValueSamplesEachLayerIndependently), - cmocka_unit_test(test_animationGetLayerValueOutOfBoundsAsserts), - cmocka_unit_test(test_animationUpdateAdvancesTimeAndCallsOnUpdate), - cmocka_unit_test(test_animationUpdatePlainPlayStopsAtEndAndFiresOnceOnly), - cmocka_unit_test(test_animationUpdateExactLandingOnEndFiresOnComplete), - cmocka_unit_test(test_animationUpdateLoopWrapsAndFiresOnLoop), - cmocka_unit_test(test_animationUpdateLoopStopEndCompletesInsteadOfWrapping), - cmocka_unit_test(test_animationUpdateReversePlaysBackward), - cmocka_unit_test(test_animationUpdateReverseStopsAtBeginning), - cmocka_unit_test( - test_animationUpdatePingpongBouncesForeverWithoutStopFlags - ), - cmocka_unit_test(test_animationUpdatePingpongStopEndCompletesAtEnd), - cmocka_unit_test(test_animationUpdatePingpongStopBeginningCompletesAtStart), - cmocka_unit_test(test_animationUpdateMultiLayerCallsOnUpdatePerLayer), - cmocka_unit_test(test_animationUpdateNullAsserts), - cmocka_unit_test(test_animationUpdateNegativeDeltaTimeAsserts), - cmocka_unit_test(test_animationInitZeroDurationAsserts), - cmocka_unit_test(test_animationUpdateLoopAndPingpongTogetherAsserts), - }; - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/animation/test_keyframe.c b/test/animation/test_keyframe.c deleted file mode 100644 index 5d6bf60d..00000000 --- a/test/animation/test_keyframe.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 "dusktest.h" -#include "animation/keyframe.h" - -static void test_keyframeGetValueSingleSegmentLinear(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - }; - - assert_float_equal(keyframeGetValue(keyframes, 2, 0.0f), 0.0f, 0.0001f); - assert_float_equal(keyframeGetValue(keyframes, 2, 0.25f), 2.5f, 0.0001f); - assert_float_equal(keyframeGetValue(keyframes, 2, 0.5f), 5.0f, 0.0001f); - assert_float_equal(keyframeGetValue(keyframes, 2, 0.75f), 7.5f, 0.0001f); -} - -static void test_keyframeGetValueMultiSegment(void **state) { - keyframe_t keyframes[3] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - { .time = 2.0f, .value = 20.0f, .easing = EASING_LINEAR }, - }; - - // Within the first segment. - assert_float_equal(keyframeGetValue(keyframes, 3, 0.5f), 5.0f, 0.0001f); - - // Exactly on the interior keyframe, resolved as the end of segment one. - assert_float_equal(keyframeGetValue(keyframes, 3, 1.0f), 10.0f, 0.0001f); - - // Within the second segment. - assert_float_equal(keyframeGetValue(keyframes, 3, 1.5f), 15.0f, 0.0001f); -} - -static void test_keyframeGetValueDescendingValues(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 100.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 0.0f, .easing = EASING_LINEAR }, - }; - - assert_float_equal(keyframeGetValue(keyframes, 2, 0.0f), 100.0f, 0.0001f); - assert_float_equal(keyframeGetValue(keyframes, 2, 0.5f), 50.0f, 0.0001f); -} - -static void test_keyframeGetValueAppliesEasing(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_IN_QUAD }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - }; - - // EASING_IN_QUAD is t * t, so halfway through time should be a quarter of - // the way through the value range, not half. - assert_float_equal(keyframeGetValue(keyframes, 2, 0.5f), 2.5f, 0.0001f); -} - -static void test_keyframeGetValueNonZeroStartTime(void **state) { - keyframe_t keyframes[2] = { - { .time = 5.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 10.0f, .value = 100.0f, .easing = EASING_LINEAR }, - }; - - assert_float_equal(keyframeGetValue(keyframes, 2, 5.0f), 0.0f, 0.0001f); - assert_float_equal(keyframeGetValue(keyframes, 2, 7.5f), 50.0f, 0.0001f); -} - -// NOTE: keyframeGetValue does not clamp to the first keyframe's value when -// queried before the first keyframe's time - the start and end pointers -// collapse onto the same keyframe, so the interpolation divides by a zero -// time delta. This test documents that current behavior rather than -// asserting it is desirable; flag to the maintainer if this should instead -// clamp like the last-keyframe case does. -static void test_keyframeGetValueBeforeFirstKeyframeIsNaN(void **state) { - keyframe_t keyframes[2] = { - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - { .time = 3.0f, .value = 30.0f, .easing = EASING_LINEAR }, - }; - - assert_true(isnan(keyframeGetValue(keyframes, 2, 0.0f))); -} - -static void test_keyframeGetValueAtOrAfterLastKeyframeClampsToLastValue( - void **state -) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - }; - - assert_float_equal(keyframeGetValue(keyframes, 2, 1.0f), 10.0f, 0.0001f); - assert_float_equal(keyframeGetValue(keyframes, 2, 2.0f), 10.0f, 0.0001f); -} - -static void test_keyframeGetValueSingleKeyframeAtOrAfterClampsToValue( - void **state -) { - keyframe_t keyframes[1] = { - { .time = 5.0f, .value = 42.0f, .easing = EASING_LINEAR }, - }; - - assert_float_equal(keyframeGetValue(keyframes, 1, 5.0f), 42.0f, 0.0001f); - assert_float_equal(keyframeGetValue(keyframes, 1, 10.0f), 42.0f, 0.0001f); - - // Before the single keyframe's time is still the documented NaN case. - assert_true(isnan(keyframeGetValue(keyframes, 1, 0.0f))); -} - -static void test_keyframeGetValueNullKeyframesAsserts(void **state) { - expect_assert_failure(keyframeGetValue(NULL, 1, 0.0f)); -} - -static void test_keyframeGetValueZeroCountAsserts(void **state) { - keyframe_t keyframes[1] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - }; - - expect_assert_failure(keyframeGetValue(keyframes, 0, 0.0f)); -} - -static void test_keyframeGetValueNegativeTimeAsserts(void **state) { - keyframe_t keyframes[2] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - }; - - expect_assert_failure(keyframeGetValue(keyframes, 2, -1.0f)); -} - -static void test_keyframeGetValueUnsortedKeyframesAsserts(void **state) { - keyframe_t keyframes[2] = { - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - }; - - expect_assert_failure(keyframeGetValue(keyframes, 2, 0.0f)); -} - -static void test_keyframeGetValueLaterKeyframeOutOfOrderAsserts(void **state) { - // The first pair is sorted, but the third keyframe is out of order - the - // check must walk the whole array, not just the first pair. - keyframe_t keyframes[3] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - { .time = 0.5f, .value = 5.0f, .easing = EASING_LINEAR }, - }; - - expect_assert_failure(keyframeGetValue(keyframes, 3, 0.0f)); -} - -static void test_keyframeGetValueEqualConsecutiveTimesDoesNotAssert( - void **state -) { - // Equal (non-decreasing) times are allowed - only strictly decreasing - // times should trigger the sorted check. - keyframe_t keyframes[3] = { - { .time = 0.0f, .value = 0.0f, .easing = EASING_LINEAR }, - { .time = 0.0f, .value = 5.0f, .easing = EASING_LINEAR }, - { .time = 1.0f, .value = 10.0f, .easing = EASING_LINEAR }, - }; - - assert_float_equal(keyframeGetValue(keyframes, 3, 0.0f), 5.0f, 0.0001f); -} - -int main(int argc, char **argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_keyframeGetValueSingleSegmentLinear), - cmocka_unit_test(test_keyframeGetValueMultiSegment), - cmocka_unit_test(test_keyframeGetValueDescendingValues), - cmocka_unit_test(test_keyframeGetValueAppliesEasing), - cmocka_unit_test(test_keyframeGetValueNonZeroStartTime), - cmocka_unit_test(test_keyframeGetValueBeforeFirstKeyframeIsNaN), - cmocka_unit_test( - test_keyframeGetValueAtOrAfterLastKeyframeClampsToLastValue - ), - cmocka_unit_test(test_keyframeGetValueSingleKeyframeAtOrAfterClampsToValue), - cmocka_unit_test(test_keyframeGetValueNullKeyframesAsserts), - cmocka_unit_test(test_keyframeGetValueZeroCountAsserts), - cmocka_unit_test(test_keyframeGetValueNegativeTimeAsserts), - cmocka_unit_test(test_keyframeGetValueUnsortedKeyframesAsserts), - cmocka_unit_test(test_keyframeGetValueLaterKeyframeOutOfOrderAsserts), - cmocka_unit_test(test_keyframeGetValueEqualConsecutiveTimesDoesNotAssert), - }; - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/item/CMakeLists.txt b/test/item/CMakeLists.txt deleted file mode 100644 index ee723335..00000000 --- a/test/item/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 - -include(dusktest) - -# Tests -dusktest(test_inventory.c) \ No newline at end of file diff --git a/test/item/test_inventory.c b/test/item/test_inventory.c deleted file mode 100644 index 75d13302..00000000 --- a/test/item/test_inventory.c +++ /dev/null @@ -1,433 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "rpg/item/inventory.h" -#include "util/memory.h" - -static itemid_t ITEM_ID_POTION; -static itemid_t ITEM_ID_POTATO; -static itemid_t ITEM_ID_APPLE; - -// Registers three test items (POTION as MEDICINE, POTATO/APPLE as FOOD) -// directly into the global ITEM registry -- these tests exercise the -// inventory/backpack layer only, not the JSON-driven itemInit() pipeline. -static int_t itemFixtureSetup(void **state) { - memoryZero(&ITEM, sizeof(ITEM)); - ITEM.typeCount = ITEM_TYPE_NULL + 1; - ITEM.defCount = ITEM_ID_NULL + 1; - - const itemtype_t medicine = itemTypeGetOrCreate("MEDICINE"); - const itemtype_t food = itemTypeGetOrCreate("FOOD"); - - ITEM_ID_POTION = ITEM.defCount++; - ITEM.defs[ITEM_ID_POTION].type = medicine; - - ITEM_ID_POTATO = ITEM.defCount++; - ITEM.defs[ITEM_ID_POTATO].type = food; - - ITEM_ID_APPLE = ITEM.defCount++; - ITEM.defs[ITEM_ID_APPLE].type = food; - - return 0; -} - -static void test_inventoryInit(void **state) { - - - inventorystack_t storage[5]; - inventory_t inventory; - - inventoryInit(&inventory, storage, 5); - - // Should setup struct - assert_non_null(inventory.storage); - assert_int_equal(inventory.storageSize, 5); - - // Should zero the item ids. - for(size_t i = 0; i < inventory.storageSize; i++) { - assert_int_equal(inventory.storage[i].item, ITEM_ID_NULL); - assert_int_equal(inventory.storage[i].quantity, 0); - } - - // Should fail when given NULL inventory pointer - expect_assert_failure(inventoryInit(NULL, storage, 5)); - - // Should fail when given NULL storage pointer - expect_assert_failure(inventoryInit(&inventory, NULL, 5)); - - // Should fail when given zero storage size - expect_assert_failure(inventoryInit(&inventory, storage, 0)); -} - -static void test_inventoryItemExists(void **state) { - - - inventorystack_t storage[5]; - inventory_t inventory; - inventoryInit(&inventory, storage, 5); - - // Initially should not exist - assert_false(inventoryItemExists(&inventory, ITEM_ID_POTION)); - - // Add item - inventorySet(&inventory, ITEM_ID_POTION, 3); - assert_true(inventoryItemExists(&inventory, ITEM_ID_POTION)); - - // Remove item - inventorySet(&inventory, ITEM_ID_POTION, 0); - assert_false(inventoryItemExists(&inventory, ITEM_ID_POTION)); - - // Should fail when given NULL inventory pointer - expect_assert_failure(inventoryItemExists(NULL, ITEM_ID_POTION)); - - // Should fail when given NULL storage pointer - inventory.storage = NULL; - expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_POTION)); - inventory.storage = storage; - - // Should fail when given zero storage size - inventory.storageSize = 0; - expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_POTION)); - inventory.storageSize = 5; - - // Should fail when given ITEM_ID_NULL - expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_NULL)); - - // Should fail if item has zero quantity somehow - inventorySet(&inventory, ITEM_ID_POTION, 3); - inventory.storage[0].quantity = 0; - expect_assert_failure(inventoryItemExists(&inventory, ITEM_ID_POTION)); -} - -static void test_inventorySet(void **state) { - - - inventorystack_t storage[5]; - inventory_t inventory; - inventoryInit(&inventory, storage, 5); - - // Set item quantity - inventorySet(&inventory, ITEM_ID_POTION, 4); - assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION); - assert_int_equal(inventory.storage[0].quantity, 4); - - // Update item quantity - inventorySet(&inventory, ITEM_ID_POTION, 2); - assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION); - assert_int_equal(inventory.storage[0].quantity, 2); - - // Remove item by setting quantity to 0 - inventorySet(&inventory, ITEM_ID_POTION, 0); - assert_int_equal(inventory.storage[0].item, ITEM_ID_NULL); - - // Setting multiple items should not cause issues - inventorySet(&inventory, ITEM_ID_POTION, 1); - inventorySet(&inventory, ITEM_ID_POTATO, 5); - assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION); - assert_int_equal(inventory.storage[0].quantity, 1); - assert_int_equal(inventory.storage[1].item, ITEM_ID_POTATO); - assert_int_equal(inventory.storage[1].quantity, 5); - assert_int_equal(inventory.storage[2].item, ITEM_ID_NULL); - - // Setting early item to 0 should shift others down - inventorySet(&inventory, ITEM_ID_POTION, 0);// Item 0 removed - assert_int_equal(inventory.storage[0].item, ITEM_ID_POTATO); - assert_int_equal(inventory.storage[0].quantity, 5); - assert_int_equal(inventory.storage[1].item, ITEM_ID_NULL); - - // Setting non-existing item should add it - inventorySet(&inventory, ITEM_ID_POTION, 3); - assert_int_equal(inventory.storage[1].item, ITEM_ID_POTION); - assert_int_equal(inventory.storage[1].quantity, 3); - - // Should fail when given NULL inventory pointer - expect_assert_failure(inventorySet(NULL, ITEM_ID_POTION, 3)); - - // Should fail when given NULL storage pointer - inventory.storage = NULL; - expect_assert_failure(inventorySet(&inventory, ITEM_ID_POTION, 3)); - inventory.storage = storage; - - // Should fail when given zero storage size - inventory.storageSize = 0; - expect_assert_failure(inventorySet(&inventory, ITEM_ID_POTION, 3)); - inventory.storageSize = 5; - - // Should fail when given ITEM_ID_NULL - expect_assert_failure(inventorySet(&inventory, ITEM_ID_NULL, 3)); -} - -static void test_inventoryAdd(void **state) { - - - inventorystack_t storage[5]; - inventory_t inventory; - inventoryInit(&inventory, storage, 5); - - // Add item quantity - inventoryAdd(&inventory, ITEM_ID_POTION, 4); - assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION); - assert_int_equal(inventory.storage[0].quantity, 4); - - // Add more to existing item - inventoryAdd(&inventory, ITEM_ID_POTION, 3); - assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION); - assert_int_equal(inventory.storage[0].quantity, 7); - - // Adding item that would overflow should assert - expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_POTION, 250)); - - // Should fail when given NULL inventory pointer - expect_assert_failure(inventoryAdd(NULL, ITEM_ID_POTION, 3)); - - // Should fail when given NULL storage pointer - inventory.storage = NULL; - expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_POTION, 3)); - inventory.storage = storage; - - // Should fail when given zero storage size - inventory.storageSize = 0; - expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_POTION, 3)); - inventory.storageSize = 5; - - // Should fail when given ITEM_ID_NULL - expect_assert_failure(inventoryAdd(&inventory, ITEM_ID_NULL, 3)); -} - -static void test_inventoryRemove(void **state) { - - - inventorystack_t storage[5]; - inventory_t inventory; - inventoryInit(&inventory, storage, 5); - - // Add item and then remove it - inventorySet(&inventory, ITEM_ID_POTION, 5); - inventoryRemove(&inventory, ITEM_ID_POTION); - assert_int_equal(inventory.storage[0].item, ITEM_ID_NULL); - - // Removing non-existing item should do nothing - inventoryRemove(&inventory, ITEM_ID_POTION); - assert_int_equal(inventory.storage[0].item, ITEM_ID_NULL); - - // Should fail when given NULL inventory pointer - expect_assert_failure(inventoryRemove(NULL, ITEM_ID_POTION)); - - // Should fail when given NULL storage pointer - inventory.storage = NULL; - expect_assert_failure(inventoryRemove(&inventory, ITEM_ID_POTION)); - inventory.storage = storage; - - // Should fail when given zero storage size - inventory.storageSize = 0; - expect_assert_failure(inventoryRemove(&inventory, ITEM_ID_POTION)); - inventory.storageSize = 5; - - // Should fail when given ITEM_ID_NULL - expect_assert_failure(inventoryRemove(&inventory, ITEM_ID_NULL)); -} - -static void test_inventoryGetCount(void **state) { - - - inventorystack_t storage[5]; - inventory_t inventory; - inventoryInit(&inventory, storage, 5); - - // Initially should be zero - assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 0); - - // Add item and check count - inventorySet(&inventory, ITEM_ID_POTION, 4); - assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 4); - - // Update item quantity and check count - inventorySet(&inventory, ITEM_ID_POTION, 2); - assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 2); - - // Remove item and check count - inventorySet(&inventory, ITEM_ID_POTION, 0); - assert_int_equal(inventoryGetCount(&inventory, ITEM_ID_POTION), 0); - - // Should fail when given NULL inventory pointer - expect_assert_failure(inventoryGetCount(NULL, ITEM_ID_POTION)); - - // Should fail when given NULL storage pointer - inventory.storage = NULL; - expect_assert_failure(inventoryGetCount(&inventory, ITEM_ID_POTION)); - inventory.storage = storage; - - // Should fail when given zero storage size - inventory.storageSize = 0; - expect_assert_failure(inventoryGetCount(&inventory, ITEM_ID_POTION)); - inventory.storageSize = 5; - - // Should fail when given ITEM_ID_NULL - expect_assert_failure(inventoryGetCount(&inventory, ITEM_ID_NULL)); -} - -static void test_inventoryIsFull(void **state) { - - - inventorystack_t storage[2]; - inventory_t inventory; - inventoryInit(&inventory, storage, 2); - - // Initially should not be full - assert_false(inventoryIsFull(&inventory)); - - // Fill inventory - inventorySet(&inventory, ITEM_ID_POTION, 1); - inventorySet(&inventory, ITEM_ID_POTATO, 1); - assert_true(inventoryIsFull(&inventory)); - - // Remove one item - inventoryRemove(&inventory, ITEM_ID_POTION); - assert_false(inventoryIsFull(&inventory)); - - // Add one item back - inventorySet(&inventory, ITEM_ID_POTION, 1); - assert_true(inventoryIsFull(&inventory)); - - // Should fail when given NULL inventory pointer - expect_assert_failure(inventoryIsFull(NULL)); - - // Should fail when given NULL storage pointer - inventory.storage = NULL; - expect_assert_failure(inventoryIsFull(&inventory)); - inventory.storage = storage; - - // Should fail when given zero storage size - inventory.storageSize = 0; - expect_assert_failure(inventoryIsFull(&inventory)); - inventory.storageSize = 2; -} - -static void test_inventoryItemFull(void **state) { - - - inventorystack_t storage[2]; - inventory_t inventory; - inventoryInit(&inventory, storage, 2); - - // Initially should not be full - assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION)); - - // Add some potions, but not too many. - inventorySet(&inventory, ITEM_ID_POTION, 100); - assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION)); - - // Fill with potions - inventorySet(&inventory, ITEM_ID_POTION, ITEM_STACK_QUANTITY_MAX); - assert_true(inventoryItemFull(&inventory, ITEM_ID_POTION)); - - // Add potatoes, should not affect potions - inventorySet(&inventory, ITEM_ID_POTATO, 50); - assert_true(inventoryItemFull(&inventory, ITEM_ID_POTION)); - assert_false(inventoryItemFull(&inventory, ITEM_ID_POTATO)); - - // Remove some potions - inventorySet(&inventory, ITEM_ID_POTION, 200); - assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION)); - - // Fill with potatoes - inventorySet(&inventory, ITEM_ID_POTATO, ITEM_STACK_QUANTITY_MAX); - assert_false(inventoryItemFull(&inventory, ITEM_ID_POTION)); - assert_true(inventoryItemFull(&inventory, ITEM_ID_POTATO)); - - // Should fail when given NULL inventory pointer - expect_assert_failure(inventoryItemFull(NULL, ITEM_ID_POTION)); - - // Should fail when given NULL storage pointer - inventory.storage = NULL; - expect_assert_failure(inventoryItemFull(&inventory, ITEM_ID_POTION)); - inventory.storage = storage; - - // Should fail when given zero storage size - inventory.storageSize = 0; - expect_assert_failure(inventoryItemFull(&inventory, ITEM_ID_POTION)); - inventory.storageSize = 2; -} - -static void test_inventorySort(void **state) { - - - inventorystack_t storage[5]; - inventory_t inventory; - inventoryInit(&inventory, storage, 5); - - // Add items out of order - inventorySet(&inventory, ITEM_ID_POTATO, 3); - inventorySet(&inventory, ITEM_ID_APPLE, 5); - inventorySet(&inventory, ITEM_ID_POTION, 2); - - // Sort by ID - inventorySort(&inventory, INVENTORY_SORT_BY_ID, false); - - // Check order - assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION); - assert_int_equal(inventory.storage[1].item, ITEM_ID_POTATO); - assert_int_equal(inventory.storage[2].item, ITEM_ID_APPLE); - - // Sort by ID reverse - inventorySort(&inventory, INVENTORY_SORT_BY_ID, true); - assert_int_equal(inventory.storage[0].item, ITEM_ID_APPLE); - assert_int_equal(inventory.storage[1].item, ITEM_ID_POTATO); - assert_int_equal(inventory.storage[2].item, ITEM_ID_POTION); - - // Sort by type ascending - POTION (MEDICINE) sorts before POTATO/APPLE - // (both FOOD). The two FOOD items compare equal, so their relative order - // isn't guaranteed (qsort is not stable) - only the type grouping is. - inventorySort(&inventory, INVENTORY_SORT_BY_TYPE, false); - assert_int_equal(inventory.storage[0].item, ITEM_ID_POTION); - assert_true( - (inventory.storage[1].item == ITEM_ID_POTATO && - inventory.storage[2].item == ITEM_ID_APPLE) || - (inventory.storage[1].item == ITEM_ID_APPLE && - inventory.storage[2].item == ITEM_ID_POTATO) - ); - - // Sort by type reverse - FOOD items first (order unspecified), POTION last. - inventorySort(&inventory, INVENTORY_SORT_BY_TYPE, true); - assert_int_equal(inventory.storage[2].item, ITEM_ID_POTION); - assert_true( - (inventory.storage[0].item == ITEM_ID_POTATO && - inventory.storage[1].item == ITEM_ID_APPLE) || - (inventory.storage[0].item == ITEM_ID_APPLE && - inventory.storage[1].item == ITEM_ID_POTATO) - ); - - // Should fail when given NULL inventory pointer - expect_assert_failure(inventorySort(NULL, INVENTORY_SORT_BY_ID, false)); - - // Should fail when given NULL storage pointer - inventory.storage = NULL; - expect_assert_failure(inventorySort(&inventory, INVENTORY_SORT_BY_ID, false)); - inventory.storage = storage; - - // Should fail when given zero storage size - inventory.storageSize = 0; - expect_assert_failure(inventorySort(&inventory, INVENTORY_SORT_BY_ID, false)); - inventory.storageSize = 5; -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_inventoryInit), - cmocka_unit_test(test_inventoryItemExists), - cmocka_unit_test(test_inventorySet), - cmocka_unit_test(test_inventoryAdd), - cmocka_unit_test(test_inventoryRemove), - cmocka_unit_test(test_inventoryGetCount), - cmocka_unit_test(test_inventoryIsFull), - cmocka_unit_test(test_inventoryItemFull), - cmocka_unit_test(test_inventorySort), - }; - - return cmocka_run_group_tests(tests, itemFixtureSetup, NULL); -} \ No newline at end of file diff --git a/test/rpg/CMakeLists.txt b/test/rpg/CMakeLists.txt deleted file mode 100644 index bd2d8fb2..00000000 --- a/test/rpg/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 - -include(dusktest) - -# Tests -dusktest(test_rpg.c) - -# Subdirs -add_subdirectory(overworld) -add_subdirectory(battle) -add_subdirectory(entity) -add_subdirectory(cutscene) \ No newline at end of file diff --git a/test/rpg/battle/CMakeLists.txt b/test/rpg/battle/CMakeLists.txt deleted file mode 100644 index b9d92cfd..00000000 --- a/test/rpg/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 - -include(dusktest) - -# Tests -dusktest(test_battle.c) diff --git a/test/rpg/battle/test_battle.c b/test/rpg/battle/test_battle.c deleted file mode 100644 index c2112ba1..00000000 --- a/test/rpg/battle/test_battle.c +++ /dev/null @@ -1,271 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "rpg/battle/battle.h" - -// TEMPORARY: hardcoded pending a real moves.jsonc-backed registry lookup. -#define BATTLE_MOVE_ID_ATTACK ((battlemoveid_t)1) - -static battlefighter_t *addFighter( - const battlefighterteam_t team, - const battlefightercontroller_t controller, - const uint16_t attack, - const uint16_t defense, - const uint16_t speed, - const uint16_t healthMax -) { - const battlefighterstats_t stats = { - .attack = attack, .defense = defense, .speed = speed - }; - return battleAddFighter(team, controller, stats, healthMax, 0); -} - -static void test_battleStartEntersOpeningThenPreRound(void **state) { - - - battleInit(); - addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 10, 0, 10, 20 - ); - addFighter( - BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 10, 0, 5, 20 - ); - - battleStart(BATTLE_ENCOUNTER_REGULAR, true); - assert_int_equal(BATTLE.state, BATTLE_STATE_OPENING); - - // OPENING only transitions to PRE_ROUND; it doesn't run PRE_ROUND's logic - // in the same call. - battleUpdate(); - assert_int_equal(BATTLE.state, BATTLE_STATE_PRE_ROUND); - assert_int_equal(BATTLE.executionCount, 0); - - // PRE_ROUND builds the execution order and moves on to selection. - battleUpdate(); - assert_int_equal(BATTLE.executionCount, 2); - assert_true( - BATTLE.state == BATTLE_STATE_PLAYER_SELECTION || - BATTLE.state == BATTLE_STATE_AI_SELECTION - ); -} - -static void test_battlePlayerSelectionAdvancesAndSkipsDecidedFighters( - void **state -) { - - - battleInit(); - battlefighter_t *ally1 = addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 5, 0, 100, 20 - ); - battlefighter_t *ally2 = addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 5, 0, 10, 20 - ); - battlefighter_t *enemy = addFighter( - BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 5, 0, 50, 20 - ); - - battleStart(BATTLE_ENCOUNTER_REGULAR, true); - battleUpdate();// OPENING -> PRE_ROUND - battleUpdate();// PRE_ROUND -> PLAYER_SELECTION, positions the cursor - - // ally1 is fastest, so it's first to decide. - assert_int_equal(BATTLE.state, BATTLE_STATE_PLAYER_SELECTION); - assert_ptr_equal(battleGetCurrentFighter(), ally1); - - // A forced move (as a scripted-battle cutscene item would push) marks - // ally2 as already decided, so PLAYER_SELECTION must skip it. - battleQueueMove(ally2->id, BATTLE_MOVE_ID_ATTACK, enemy->id); - - battlePlayerAttack(enemy->id); - assert_int_equal(BATTLE.moves[ally1->id].move, BATTLE_MOVE_ID_ATTACK); - assert_int_equal(BATTLE.moves[ally1->id].targets[0], enemy->id); - - // ally2 already had a decision queued, and enemy is AI-controlled, so - // there's nothing left for PLAYER_SELECTION -- it should have moved on. - assert_int_equal(BATTLE.state, BATTLE_STATE_AI_SELECTION); -} - -static void test_battleAiSelectionAutoQueuesUndecidedAiFighters(void **state) { - - - battleInit(); - battlefighter_t *ally = addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 5, 0, 10, 20 - ); - battlefighter_t *enemy = addFighter( - BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 5, 0, 5, 20 - ); - - battleStart(BATTLE_ENCOUNTER_REGULAR, true); - battleUpdate();// OPENING -> PRE_ROUND - battleUpdate();// PRE_ROUND -> PLAYER_SELECTION - - battlePlayerAttack(enemy->id);// ally decides, selection moves to AI - assert_int_equal(BATTLE.state, BATTLE_STATE_AI_SELECTION); - - battleUpdate();// AI_SELECTION auto-decides for enemy - assert_int_equal(BATTLE.state, BATTLE_STATE_MOVES_EXECUTING); - assert_int_equal(BATTLE.moves[enemy->id].move, BATTLE_MOVE_ID_ATTACK); - assert_int_equal(BATTLE.moves[enemy->id].targets[0], ally->id); -} - -static void test_battleMovesExecutingResolvesInSpeedOrderAndFizzles( - void **state -) { - - - battleInit(); - // ally1 is fast enough to kill enemy1 before enemy1 or ally2 act. enemy2 - // keeps the battle alive so execution continues to ally2's now-moot - // attack against the already-dead enemy1. - battlefighter_t *ally1 = addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 50, 0, 100, 20 - ); - battlefighter_t *ally2 = addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 50, 0, 10, 20 - ); - battlefighter_t *enemy1 = addFighter( - BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 1, 0, 50, 10 - ); - battlefighter_t *enemy2 = addFighter( - BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 1, 0, 1, 100 - ); - - battleStart(BATTLE_ENCOUNTER_REGULAR, true); - battleUpdate();// OPENING -> PRE_ROUND - battleUpdate();// PRE_ROUND -> PLAYER_SELECTION (ally1 first, fastest) - - assert_ptr_equal(battleGetCurrentFighter(), ally1); - battlePlayerAttack(enemy1->id); - assert_ptr_equal(battleGetCurrentFighter(), ally2); - battlePlayerAttack(enemy1->id);// both allies target enemy1 - assert_int_equal(BATTLE.state, BATTLE_STATE_AI_SELECTION); - - battleUpdate();// AI_SELECTION - assert_int_equal(BATTLE.state, BATTLE_STATE_MOVES_EXECUTING); - - // Execution order: ally1 (100), enemy1 (50), ally2 (10), enemy2 (1). - battleUpdate();// ally1 kills enemy1 - assert_false(battleFighterIsAlive(enemy1)); - assert_int_equal(BATTLE.state, BATTLE_STATE_MOVES_EXECUTING);// enemy2 alive - - battleUpdate();// enemy1's own (now dead) turn is skipped - battleUpdate();// ally2's attack against the dead enemy1 fizzles silently - assert_int_equal(enemy1->health, 0);// no double-kill, no underflow - - battleUpdate();// enemy2 acts - battleUpdate();// all 4 slots processed -> POST_ROUND - assert_int_equal(BATTLE.state, BATTLE_STATE_POST_ROUND); - - battleUpdate();// POST_ROUND -> PRE_ROUND, round advances - assert_int_equal(BATTLE.round, 2); - assert_int_equal(BATTLE.state, BATTLE_STATE_PRE_ROUND); -} - -static void test_battleWinEndsInStateEnded(void **state) { - - - battleInit(); - battlefighter_t *ally = addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 50, 0, 10, 20 - ); - battlefighter_t *enemy = addFighter( - BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 1, 0, 5, 10 - ); - - battleStart(BATTLE_ENCOUNTER_REGULAR, true); - battleUpdate();// OPENING -> PRE_ROUND - battleUpdate();// PRE_ROUND -> PLAYER_SELECTION - - battlePlayerAttack(enemy->id);// selection -> AI_SELECTION - battleUpdate();// AI_SELECTION -> MOVES_EXECUTING - battleUpdate();// ally kills the only enemy - - assert_int_equal(BATTLE.state, BATTLE_STATE_ENDED); - assert_int_equal(BATTLE.result, BATTLE_RESULT_WIN); -} - -static void test_battleLossEndsInStateEnded(void **state) { - - - battleInit(); - battlefighter_t *ally = addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 1, 0, 5, 10 - ); - battlefighter_t *enemy = addFighter( - BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 50, 0, 10, 20 - ); - - battleStart(BATTLE_ENCOUNTER_REGULAR, true); - battleUpdate();// OPENING -> PRE_ROUND - battleUpdate();// PRE_ROUND -> PLAYER_SELECTION - - battlePlayerAttack(enemy->id);// selection -> AI_SELECTION - battleUpdate();// AI_SELECTION -> MOVES_EXECUTING - battleUpdate();// ally attacks first (irrelevant to outcome) - battleUpdate();// enemy kills the only ally - - assert_int_equal(BATTLE.state, BATTLE_STATE_ENDED); - assert_int_equal(BATTLE.result, BATTLE_RESULT_LOSS); -} - -static void test_battleFleeEndsInStateEndedImmediately(void **state) { - - - battleInit(); - addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 5, 0, 10, 20 - ); - addFighter( - BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 5, 0, 5, 20 - ); - - battleStart(BATTLE_ENCOUNTER_REGULAR, true); - battleUpdate();// OPENING -> PRE_ROUND - battleUpdate();// PRE_ROUND -> PLAYER_SELECTION - - battlePlayerFlee(); - assert_int_equal(BATTLE.state, BATTLE_STATE_ENDED); - assert_int_equal(BATTLE.result, BATTLE_RESULT_FLED); -} - -static void test_battleFleeUnavailableIsIgnored(void **state) { - - - battleInit(); - addFighter( - BATTLE_FIGHTER_TEAM_ALLY, BATTLE_FIGHTER_CONTROLLER_PLAYER, 5, 0, 10, 20 - ); - addFighter( - BATTLE_FIGHTER_TEAM_ENEMY, BATTLE_FIGHTER_CONTROLLER_AI, 5, 0, 5, 20 - ); - - battleStart(BATTLE_ENCOUNTER_REGULAR, false); - battleUpdate();// OPENING -> PRE_ROUND - battleUpdate();// PRE_ROUND -> PLAYER_SELECTION - - battlePlayerFlee(); - assert_int_equal(BATTLE.state, BATTLE_STATE_PLAYER_SELECTION); - assert_int_equal(BATTLE.result, BATTLE_RESULT_NONE); -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_battleStartEntersOpeningThenPreRound), - cmocka_unit_test(test_battlePlayerSelectionAdvancesAndSkipsDecidedFighters), - cmocka_unit_test(test_battleAiSelectionAutoQueuesUndecidedAiFighters), - cmocka_unit_test(test_battleMovesExecutingResolvesInSpeedOrderAndFizzles), - cmocka_unit_test(test_battleWinEndsInStateEnded), - cmocka_unit_test(test_battleLossEndsInStateEnded), - cmocka_unit_test(test_battleFleeEndsInStateEndedImmediately), - cmocka_unit_test(test_battleFleeUnavailableIsIgnored), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/cutscene/CMakeLists.txt b/test/rpg/cutscene/CMakeLists.txt deleted file mode 100644 index 52464928..00000000 --- a/test/rpg/cutscene/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 - -include(dusktest) - -# Tests -dusktest(test_cutscene.c) -dusktest(test_cutscenesystem.c) -dusktest(test_cutscenecontrol.c) -dusktest(test_cutscenemaparea.c) diff --git a/test/rpg/cutscene/test_cutscene.c b/test/rpg/cutscene/test_cutscene.c deleted file mode 100644 index 2f0457cf..00000000 --- a/test/rpg/cutscene/test_cutscene.c +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "rpg/cutscene/cutscene.h" -#include "util/memory.h" -#include "util/string.h" - -// ============================================================ -// cutsceneRemoveFront -// ============================================================ - -static cutsceneitem_t MARKER_ITEM(const char_t *name) { - cutsceneitem_t item; - memoryZero(&item, sizeof(item)); - item.type = CUTSCENE_ITEM_TYPE_MARKER; - stringCopy(item.marker.name, name, CUTSCENE_MARKER_NAME_MAX - 1); - return item; -} - -static void test_cutsceneRemoveFrontShiftsRemainderToIndexZero( - void **state -) { - - cutsceneitem_t items[4] = { - MARKER_ITEM("a"), MARKER_ITEM("b"), MARKER_ITEM("c"), MARKER_ITEM("d") - }; - cutscene_t scene = { .items = items, .itemCount = 4, .itemsMax = 4 }; - - cutsceneRemoveFront(&scene, 2); - - assert_int_equal(scene.itemCount, 2); - assert_string_equal(scene.items[0].marker.name, "c"); - assert_string_equal(scene.items[1].marker.name, "d"); -} - -static void test_cutsceneRemoveFrontAllItemsLeavesEmptyScene(void **state) { - - cutsceneitem_t items[3] = { - MARKER_ITEM("a"), MARKER_ITEM("b"), MARKER_ITEM("c") - }; - cutscene_t scene = { .items = items, .itemCount = 3, .itemsMax = 3 }; - - cutsceneRemoveFront(&scene, 3); - - assert_int_equal(scene.itemCount, 0); -} - -static void test_cutsceneRemoveFrontZeroIsNoop(void **state) { - - cutsceneitem_t items[2] = { MARKER_ITEM("a"), MARKER_ITEM("b") }; - cutscene_t scene = { .items = items, .itemCount = 2, .itemsMax = 2 }; - - cutsceneRemoveFront(&scene, 0); - - assert_int_equal(scene.itemCount, 2); - assert_string_equal(scene.items[0].marker.name, "a"); - assert_string_equal(scene.items[1].marker.name, "b"); -} - -static void test_cutsceneRemoveFrontAssertsWhenCountExceedsItemCount( - void **state -) { - - cutsceneitem_t items[2] = { MARKER_ITEM("a"), MARKER_ITEM("b") }; - cutscene_t scene = { .items = items, .itemCount = 2, .itemsMax = 2 }; - - expect_assert_failure(cutsceneRemoveFront(&scene, 3)); -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_cutsceneRemoveFrontShiftsRemainderToIndexZero), - cmocka_unit_test(test_cutsceneRemoveFrontAllItemsLeavesEmptyScene), - cmocka_unit_test(test_cutsceneRemoveFrontZeroIsNoop), - cmocka_unit_test(test_cutsceneRemoveFrontAssertsWhenCountExceedsItemCount), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/cutscene/test_cutscenecontrol.c b/test/rpg/cutscene/test_cutscenecontrol.c deleted file mode 100644 index 60b4d57d..00000000 --- a/test/rpg/cutscene/test_cutscenecontrol.c +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "time/time.h" -#include "util/memory.h" - -static void test_cutsceneWaitCompletesAfterItsDuration(void **state) { - - cutsceneitem_t item = { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f }; - cutsceneitemdata_t data; - memoryZero(&data, sizeof(data)); - - cutsceneWaitStart(&item, &data); - - TIME.delta = 0.5f; - assert_false(cutsceneWaitUpdate(&item, &data)); - - TIME.delta = 0.6f; - assert_true(cutsceneWaitUpdate(&item, &data)); -} - -static void *lastUserData; -static uint8_t callbackCallCount; - -static void recordCallback(void *userData) { - lastUserData = userData; - callbackCallCount++; -} - -static void test_cutsceneCallbackFiresOnStartWithNullUserData(void **state) { - - callbackCallCount = 0; - lastUserData = (void *)0x1; - - cutsceneitem_t item = - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordCallback }; - cutsceneitemdata_t data; - - cutsceneCallbackStart(&item, &data); - assert_int_equal(callbackCallCount, 1); - assert_null(lastUserData); - - // Callback items always complete immediately -- the effect already - // happened in Start, not Update. - assert_true(cutsceneCallbackUpdate(&item, &data)); - assert_int_equal(callbackCallCount, 1);// Update doesn't fire it again -} - -static void test_cutsceneCallbackNullIsNoop(void **state) { - - callbackCallCount = 0; - cutsceneitem_t item = - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = NULL }; - cutsceneitemdata_t data; - - cutsceneCallbackStart(&item, &data);// should not crash - assert_int_equal(callbackCallCount, 0); -} - -static void test_cutsceneSetPauseAppliesImmediately(void **state) { - - CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE; - cutsceneitem_t item = { - .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, - .setPause = CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_BATTLE - }; - cutsceneitemdata_t data; - - cutsceneSetPauseStart(&item, &data); - assert_int_equal( - CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_NPC | CUTSCENE_PAUSE_BATTLE - ); - assert_true(cutsceneSetPauseUpdate(&item, &data)); -} - -static void test_cutsceneConcurrentCompletesOnceAllChildrenDo(void **state) { - - cutsceneitem_t item = { - .type = CUTSCENE_ITEM_TYPE_CONCURRENT, - .concurrent = { - .items = (const cutsceneitem_t[]){ - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 0.2f }, - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 0.5f } - }, - .count = 2 - } - }; - cutsceneitemdata_t data; - memoryZero(&data, sizeof(data)); - - cutsceneConcurrentStart(&item, &data); - - TIME.delta = 0.3f; - // Child 0 (0.2s) elapses; child 1 (0.5s) still has 0.2s left. - assert_false(cutsceneConcurrentUpdate(&item, &data)); - - TIME.delta = 0.3f; - // Child 1 now elapses too. - assert_true(cutsceneConcurrentUpdate(&item, &data)); -} - -static void test_cutsceneConcurrentDoesNotReUpdateFinishedChildren( - void **state -) { - - cutsceneitem_t item = { - .type = CUTSCENE_ITEM_TYPE_CONCURRENT, - .concurrent = { - .items = (const cutsceneitem_t[]){ - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 0.1f }, - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 10.0f } - }, - .count = 2 - } - }; - cutsceneitemdata_t data; - memoryZero(&data, sizeof(data)); - - cutsceneConcurrentStart(&item, &data); - - TIME.delta = 0.2f; - cutsceneConcurrentUpdate(&item, &data);// child 0 finishes - - // If child 0 were re-updated, its stored wait value would keep dropping - // further below zero -- not observable directly, but the overall result - // must not falsely report done just because child 0 keeps completing. - TIME.delta = 0.2f; - assert_false(cutsceneConcurrentUpdate(&item, &data)); -} - -static void test_cutsceneConcurrentCannotNest(void **state) { - - cutsceneitem_t outer = { - .type = CUTSCENE_ITEM_TYPE_CONCURRENT, - .concurrent = { - .items = (const cutsceneitem_t[]){ - { - .type = CUTSCENE_ITEM_TYPE_CONCURRENT, - .concurrent = { - .items = (const cutsceneitem_t[]){ - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f } - }, - .count = 1 - } - } - }, - .count = 1 - } - }; - cutsceneitemdata_t data; - - expect_assert_failure(cutsceneConcurrentStart(&outer, &data)); -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_cutsceneWaitCompletesAfterItsDuration), - cmocka_unit_test(test_cutsceneCallbackFiresOnStartWithNullUserData), - cmocka_unit_test(test_cutsceneCallbackNullIsNoop), - cmocka_unit_test(test_cutsceneSetPauseAppliesImmediately), - cmocka_unit_test(test_cutsceneConcurrentCompletesOnceAllChildrenDo), - cmocka_unit_test(test_cutsceneConcurrentDoesNotReUpdateFinishedChildren), - cmocka_unit_test(test_cutsceneConcurrentCannotNest), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/cutscene/test_cutscenemaparea.c b/test/rpg/cutscene/test_cutscenemaparea.c deleted file mode 100644 index 9c70980f..00000000 --- a/test/rpg/cutscene/test_cutscenemaparea.c +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "rpg/overworld/maparea.h" -#include "util/memory.h" - -static void resetMapAreas(void) { - memoryZero(MAP_AREAS, sizeof(MAP_AREAS)); -} - -static void test_cutsceneMapAreaAddCreatesAreaAndStoresLastCreated( - void **state -) { - - resetMapAreas(); - cutsceneitem_t item = { - .type = CUTSCENE_ITEM_TYPE_MAP_AREA_ADD, - .mapAreaAdd = { - .min = { 0, 0, 0 }, .max = { 5, 5, 0 }, - .callback = mapAreaNoopCallback, - .notify = MAP_AREA_NOTIFY_ALL, .trigger = MAP_TRIGGER_ALL - } - }; - cutsceneitemdata_t data; - - cutsceneMapAreaAddStart(&item, &data); - assert_true(cutsceneMapAreaAddUpdate(&item, &data)); - - uint8_t id = CUTSCENE_SYSTEM.areaLastCreated; - assert_ptr_equal(MAP_AREAS[id].callback, mapAreaNoopCallback); - assert_int_equal(MAP_AREAS[id].max.x, 5); -} - -static void test_cutsceneMapAreaRemoveClearsSlot(void **state) { - - resetMapAreas(); - uint8_t id = mapAreaAdd( - (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, - mapAreaNoopCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - - cutsceneitem_t item = { - .type = CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, - .mapAreaRemove = { .areaId = id } - }; - cutsceneitemdata_t data; - - cutsceneMapAreaRemoveStart(&item, &data); - assert_true(cutsceneMapAreaRemoveUpdate(&item, &data)); - assert_null(MAP_AREAS[id].callback); -} - -static void test_cutsceneMapAreaRemoveResolvesLastCreatedSentinel( - void **state -) { - - resetMapAreas(); - cutsceneitem_t addItem = { - .type = CUTSCENE_ITEM_TYPE_MAP_AREA_ADD, - .mapAreaAdd = { - .min = { 0, 0, 0 }, .max = { 5, 5, 0 }, - .callback = mapAreaNoopCallback, - .notify = MAP_AREA_NOTIFY_ALL, .trigger = MAP_TRIGGER_ALL - } - }; - cutsceneitemdata_t data; - cutsceneMapAreaAddStart(&addItem, &data); - uint8_t id = CUTSCENE_SYSTEM.areaLastCreated; - - cutsceneitem_t removeItem = { - .type = CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, - .mapAreaRemove = { .areaId = CUTSCENE_REF_AREA_LAST_CREATED } - }; - cutsceneMapAreaRemoveStart(&removeItem, &data); - assert_null(MAP_AREAS[id].callback); -} - -static void test_cutsceneMapAreaWaitCompletesWhenTriggerCountChanges( - void **state -) { - - resetMapAreas(); - uint8_t id = mapAreaAdd( - (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, - mapAreaNoopCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - - cutsceneitem_t item = { - .type = CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, - .mapAreaWait = { .areaIds = { id }, .count = 1 } - }; - cutsceneitemdata_t data; - - cutsceneMapAreaWaitStart(&item, &data); - assert_false(cutsceneMapAreaWaitUpdate(&item, &data)); - - MAP_AREAS[id].triggerCount++;// simulates the area's callback firing - assert_true(cutsceneMapAreaWaitUpdate(&item, &data)); -} - -static void test_cutsceneMapAreaWaitCompletesWhenAnyWatchedAreaChanges( - void **state -) { - - resetMapAreas(); - uint8_t idA = mapAreaAdd( - (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, - mapAreaNoopCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - uint8_t idB = mapAreaAdd( - (worldpos_t){ 10, 10, 0 }, (worldpos_t){ 15, 15, 0 }, - mapAreaNoopCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - - cutsceneitem_t item = { - .type = CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, - .mapAreaWait = { .areaIds = { idA, idB }, .count = 2 } - }; - cutsceneitemdata_t data; - - cutsceneMapAreaWaitStart(&item, &data); - assert_false(cutsceneMapAreaWaitUpdate(&item, &data)); - - MAP_AREAS[idB].triggerCount++;// only the second watched area fires - assert_true(cutsceneMapAreaWaitUpdate(&item, &data)); -} - -static void test_cutsceneMapAreaWaitResolvesLastCreatedSentinel( - void **state -) { - - resetMapAreas(); - cutsceneitem_t addItem = { - .type = CUTSCENE_ITEM_TYPE_MAP_AREA_ADD, - .mapAreaAdd = { - .min = { 0, 0, 0 }, .max = { 5, 5, 0 }, - .callback = mapAreaNoopCallback, - .notify = MAP_AREA_NOTIFY_ALL, .trigger = MAP_TRIGGER_ALL - } - }; - cutsceneitemdata_t data; - cutsceneMapAreaAddStart(&addItem, &data); - uint8_t id = CUTSCENE_SYSTEM.areaLastCreated; - - cutsceneitem_t waitItem = { - .type = CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, - .mapAreaWait = { .areaIds = { CUTSCENE_REF_AREA_LAST_CREATED }, .count = 1 } - }; - cutsceneMapAreaWaitStart(&waitItem, &data); - assert_false(cutsceneMapAreaWaitUpdate(&waitItem, &data)); - - MAP_AREAS[id].triggerCount++; - assert_true(cutsceneMapAreaWaitUpdate(&waitItem, &data)); -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_cutsceneMapAreaAddCreatesAreaAndStoresLastCreated), - cmocka_unit_test(test_cutsceneMapAreaRemoveClearsSlot), - cmocka_unit_test(test_cutsceneMapAreaRemoveResolvesLastCreatedSentinel), - cmocka_unit_test(test_cutsceneMapAreaWaitCompletesWhenTriggerCountChanges), - cmocka_unit_test( - test_cutsceneMapAreaWaitCompletesWhenAnyWatchedAreaChanges - ), - cmocka_unit_test(test_cutsceneMapAreaWaitResolvesLastCreatedSentinel), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/cutscene/test_cutscenesystem.c b/test/rpg/cutscene/test_cutscenesystem.c deleted file mode 100644 index 845257fd..00000000 --- a/test/rpg/cutscene/test_cutscenesystem.c +++ /dev/null @@ -1,583 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "rpg/entity/entity.h" -#include "time/time.h" -#include "util/string.h" - -static uint8_t callbackFired; - -static void recordCallback(void *userData) { - callbackFired++; -} - -// INNER is jumped into by OUTER's second item, never returning -- this is -// the "nested cutscene is a one-way jump" behavior: cutsceneCutsceneStart -// replaces CUTSCENE_SYSTEM.scene outright, and the callback item's effect -// fires in Start, not Update, so it fires the same frame the jump happens. -static cutsceneitem_t CUTSCENE_TEST_INNER_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordCallback } -}; -static cutscene_t CUTSCENE_TEST_INNER = { - .items = CUTSCENE_TEST_INNER_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INNER_ITEMS) / sizeof(cutsceneitem_t), - .itemsMax = sizeof(CUTSCENE_TEST_INNER_ITEMS) / sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; - -static cutsceneitem_t CUTSCENE_TEST_OUTER_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 0.5f }, - { - .type = CUTSCENE_ITEM_TYPE_CUTSCENE, - .cutsceneRef = { .cutscene = &CUTSCENE_TEST_INNER } - } -}; -static cutscene_t CUTSCENE_TEST_OUTER = { - .items = CUTSCENE_TEST_OUTER_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_OUTER_ITEMS) / sizeof(cutsceneitem_t), - .itemsMax = sizeof(CUTSCENE_TEST_OUTER_ITEMS) / sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_DEFAULT -}; - -static void test_cutsceneSystemStartSetsUpInitialItem(void **state) { - - cutsceneSystemInit(); - - cutsceneSystemStartCutscene(&CUTSCENE_TEST_OUTER); - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_OUTER); - assert_int_equal(CUTSCENE_SYSTEM.currentItem, 0); - assert_int_equal(CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_DEFAULT); -} - -static void test_cutsceneSystemNestedCutsceneIsOneWayJump(void **state) { - - cutsceneSystemInit(); - callbackFired = 0; - cutsceneSystemStartCutscene(&CUTSCENE_TEST_OUTER); - - TIME.delta = 0.1f; - cutsceneSystemUpdate();// wait not elapsed yet - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_OUTER); - assert_int_equal(callbackFired, 0); - - // The wait elapses, advancing to the nested-cutscene item, which jumps - // straight into INNER and starts its first item (the callback) -- all - // within this single update call. - TIME.delta = 1.0f; - cutsceneSystemUpdate(); - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INNER); - assert_int_equal(CUTSCENE_SYSTEM.currentItem, 0); - assert_int_equal(callbackFired, 1); - - // INNER's only item (the callback) always reports complete -- one more - // update ends the whole cutscene. - cutsceneSystemUpdate(); - assert_null(CUTSCENE_SYSTEM.scene); - assert_int_equal(CUTSCENE_SYSTEM.currentItem, 0xFF); - assert_int_equal(CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_NONE); -} - -static void test_cutsceneSystemUpdateIsNoopWithNoActiveCutscene(void **state) { - - cutsceneSystemInit(); - - cutsceneSystemUpdate();// should not crash - assert_null(CUTSCENE_SYSTEM.scene); -} - -static cutsceneitem_t CUTSCENE_TEST_SINGLE_WAIT_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f } -}; -static cutscene_t CUTSCENE_TEST_SINGLE_WAIT = { - .items = CUTSCENE_TEST_SINGLE_WAIT_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_SINGLE_WAIT_ITEMS) / sizeof(cutsceneitem_t), - .itemsMax = sizeof(CUTSCENE_TEST_SINGLE_WAIT_ITEMS) / sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; - -static void test_cutsceneSystemStartWithSetsInteractEntities(void **state) { - - cutsceneSystemInit(); - entityInit(&ENTITIES[0], ENTITY_TYPE_PLAYER); - entityInit(&ENTITIES[1], ENTITY_TYPE_NPC); - - cutsceneSystemStartCutsceneWith( - &CUTSCENE_TEST_SINGLE_WAIT, &ENTITIES[0], &ENTITIES[1] - ); - - assert_ptr_equal(CUTSCENE_SYSTEM.refs.entityInteract, &ENTITIES[0]); - assert_ptr_equal(CUTSCENE_SYSTEM.refs.entityInteracted, &ENTITIES[1]); - assert_ptr_equal( - cutsceneSystemGetEntity(CUTSCENE_ENTITY_INTERACT), &ENTITIES[0] - ); - assert_ptr_equal( - cutsceneSystemGetEntity(CUTSCENE_ENTITY_INTERACTED), &ENTITIES[1] - ); - - TIME.delta = 2.0f; - cutsceneSystemUpdate();// ends the cutscene - assert_null(CUTSCENE_SYSTEM.refs.entityInteract);// reset on end - assert_null(CUTSCENE_SYSTEM.refs.entityInteracted); -} - -static void test_cutsceneSystemGetEntitySentinelsRequireBeingSet( - void **state -) { - - cutsceneSystemInit(); - - expect_assert_failure(cutsceneSystemGetEntity(CUTSCENE_ENTITY_INTERACT)); - expect_assert_failure(cutsceneSystemGetEntity(CUTSCENE_ENTITY_INTERACTED)); - expect_assert_failure(cutsceneSystemGetEntity(CUTSCENE_ENTITY_LAST_CREATED)); - expect_assert_failure(cutsceneSystemGetEntity(CUTSCENE_ENTITY_LAST_REF)); -} - -static void test_cutsceneSystemGetEntityDirectIndexUpdatesLastRef( - void **state -) { - - cutsceneSystemInit(); - entityInit(&ENTITIES[2], ENTITY_TYPE_NPC); - - entity_t *resolved = cutsceneSystemGetEntity(2); - assert_ptr_equal(resolved, &ENTITIES[2]); - // Resolving by direct index also updates LAST_REF. - assert_ptr_equal( - cutsceneSystemGetEntity(CUTSCENE_ENTITY_LAST_REF), &ENTITIES[2] - ); -} - -static void test_cutsceneSystemGetAreaId(void **state) { - - cutsceneSystemInit(); - // cutsceneSystemInit() zero-inits the field -- only actually starting a - // cutscene sets it to the "nothing created yet" sentinel. - CUTSCENE_SYSTEM.refs.areaLastCreated = CUTSCENE_REF_AREA_LAST_CREATED; - - expect_assert_failure(cutsceneSystemGetAreaId(CUTSCENE_REF_AREA_LAST_CREATED)); - assert_int_equal(cutsceneSystemGetAreaId(5), 5);// direct IDs pass through - - CUTSCENE_SYSTEM.refs.areaLastCreated = 3; - assert_int_equal(cutsceneSystemGetAreaId(CUTSCENE_REF_AREA_LAST_CREATED), 3); -} - -static void test_cutsceneSystemGetTextMiniId(void **state) { - - cutsceneSystemInit(); - CUTSCENE_SYSTEM.refs.textMiniLastCreated = CUTSCENE_REF_TEXT_MINI_LAST_CREATED; - - expect_assert_failure( - cutsceneSystemGetTextMiniId(CUTSCENE_REF_TEXT_MINI_LAST_CREATED) - ); - assert_int_equal(cutsceneSystemGetTextMiniId(4), 4); - - CUTSCENE_SYSTEM.refs.textMiniLastCreated = 1; - assert_int_equal( - cutsceneSystemGetTextMiniId(CUTSCENE_REF_TEXT_MINI_LAST_CREATED), 1 - ); -} - -static void test_cutsceneSystemDisposeResetsState(void **state) { - - cutsceneSystemInit(); - entityInit(&ENTITIES[0], ENTITY_TYPE_PLAYER); - cutsceneSystemStartCutsceneWith( - &CUTSCENE_TEST_SINGLE_WAIT, &ENTITIES[0], &ENTITIES[0] - ); - - cutsceneSystemDispose(); - assert_null(CUTSCENE_SYSTEM.scene); - assert_int_equal(CUTSCENE_SYSTEM.currentItem, 0xFF); - assert_int_equal(CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_NONE); - assert_null(CUTSCENE_SYSTEM.refs.entityInteract); - assert_null(CUTSCENE_SYSTEM.refs.entityInteracted); -} - -// --- loadedFile survival across cutsceneSystemPrepare ------------------- -// -// cutsceneCutsceneResolve (and cutsceneSystemLoad) stamp -// CUTSCENE_SYSTEM.loadedFile with what they just parsed loadedScene from -// - cutsceneSystemGoTo needs that to survive so it can reload the same source -// later. cutsceneCutsceneResolve's caller stamps it BEFORE calling -// cutsceneSystemStartCutscene(&CUTSCENE_SYSTEM.loadedScene), so -// cutsceneSystemPrepare must not blindly clear loadedFile on every -// start - only when switching away from loadedScene to some other -// cutscene_t entirely (see cutsceneSystemPrepare's doc comment). This -// regression-tests the exact bug hit via initial.jsonc's CUTSCENE item -// into main_menu.jsonc: cutsceneCutsceneResolve stamped loadedFile, then -// the immediately following cutsceneSystemStartCutscene call wiped it -// straight back out, so a later cutsceneSystemGoTo on the still-running -// cutscene had nothing to reload from. -static void test_cutsceneSystemPrepareKeepsLoadedFileWhenReenteringLoadedScene( - void **state -) { - - cutsceneSystemInit(); - - // Simulate cutsceneCutsceneResolve: populate loadedScene/loadedItems - // and stamp loadedFile with its source, before the cutscene has - // actually started running. - CUTSCENE_SYSTEM.loadedItems[0] = - (cutsceneitem_t){ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f }; - CUTSCENE_SYSTEM.loadedScene = (cutscene_t){ - .items = CUTSCENE_SYSTEM.loadedItems, - .itemCount = 1, - .itemsMax = CUTSCENE_LOADED_ITEMS_MAX, - .pause = CUTSCENE_PAUSE_NONE - }; - stringCopy( - CUTSCENE_SYSTEM.loadedFile, "cutscenes/main_menu.jsonc", - ASSET_FILE_NAME_MAX - 1 - ); - - cutsceneSystemStartCutscene(&CUTSCENE_SYSTEM.loadedScene); - - assert_string_equal( - CUTSCENE_SYSTEM.loadedFile, "cutscenes/main_menu.jsonc" - ); - - // Starting a genuinely different cutscene, though, must still - // invalidate it - there's nothing left to reload it from. - cutsceneSystemStartCutscene(&CUTSCENE_TEST_SINGLE_WAIT); - - assert_string_equal(CUTSCENE_SYSTEM.loadedFile, ""); -} - -// --- CUTSCENE_INSERT --------------------------------------------------- -// -// INSERT now splices its target's items directly into the running -// scene's own array (see cutsceneSystemInsertCutscene) rather than -// swapping CUTSCENE_SYSTEM.scene to a different cutscene_t and pushing a -// return frame - CUTSCENE_SYSTEM.scene therefore stays the same outer -// cutscene throughout every test below. cutsceneSystemNext pops each -// already-run item off the front of the array as it advances (see -// CUTSCENE_SYSTEM.currentItem's doc comment), so itemCount reflects only -// what's left to run at any given point, not a running total of -// everything ever spliced in - the itemCount assertions below are sized -// accordingly. Since the running scene's own array is what gets appended -// into, any outer cutscene used with INSERT must still declare its items -// array (and .itemsMax) with enough spare capacity for whatever ends up -// spliced into it at once, including transitively through any nested -// INSERT. - -static uint8_t insertOrderLog[8]; -static uint8_t insertOrderLogCount; - -static void recordOrder0(void *userData) { insertOrderLog[insertOrderLogCount++] = 0; } -static void recordOrder1(void *userData) { insertOrderLog[insertOrderLogCount++] = 1; } -static void recordOrder2(void *userData) { insertOrderLog[insertOrderLogCount++] = 2; } -static void recordOrderA(void *userData) { insertOrderLog[insertOrderLogCount++] = 3; } -static void recordOrderB(void *userData) { insertOrderLog[insertOrderLogCount++] = 4; } - -static cutsceneitem_t CUTSCENE_TEST_INSERT_SNIPPET_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA }, - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 2.0f }, - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderB } -}; -static cutscene_t CUTSCENE_TEST_INSERT_SNIPPET = { - .items = CUTSCENE_TEST_INSERT_SNIPPET_ITEMS, - .itemCount = - sizeof(CUTSCENE_TEST_INSERT_SNIPPET_ITEMS) / sizeof(cutsceneitem_t), - .itemsMax = - sizeof(CUTSCENE_TEST_INSERT_SNIPPET_ITEMS) / sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; - -// 5 items to start, +3 spare slots for CUTSCENE_TEST_INSERT_SNIPPET's items. -static cutsceneitem_t CUTSCENE_TEST_INSERT_OUTER_ITEMS[8] = { - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder0 }, - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f }, - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder1 }, - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_SNIPPET } - }, - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder2 } -}; -static cutscene_t CUTSCENE_TEST_INSERT_OUTER = { - .items = CUTSCENE_TEST_INSERT_OUTER_ITEMS, - .itemCount = 5, - .itemsMax = sizeof(CUTSCENE_TEST_INSERT_OUTER_ITEMS) / sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; - -static void test_cutsceneInsertSplicesItemsInPlaceThenResumes(void **state) { - - insertOrderLogCount = 0; - cutsceneSystemInit(); - cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_OUTER); - - TIME.delta = 1.5f;// elapses the outer WAIT(1.0) - cutsceneSystemUpdate();// order0 done -> starts the outer WAIT - cutsceneSystemUpdate();// the WAIT elapses -> starts order1 - cutsceneSystemUpdate();// order1 done -> starts the INSERT, cascading into orderA - - // The splice happened in place - CUTSCENE_SYSTEM.scene never changed. - // order0/the WAIT/order1/the INSERT item itself have all since been - // popped off the front, leaving the snippet's 3 items (orderA now - // running, its WAIT, orderB) plus order2. - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_OUTER); - assert_int_equal(CUTSCENE_SYSTEM.scene->itemCount, 4); - - TIME.delta = 2.5f;// elapses the snippet's WAIT(2.0) - cutsceneSystemUpdate();// orderA done -> starts snippet's WAIT - cutsceneSystemUpdate();// snippet's WAIT elapses -> starts orderB - cutsceneSystemUpdate();// orderB done -> continues on to order2 - - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_OUTER); - - cutsceneSystemUpdate();// order2 done -> cutscene ends naturally - assert_null(CUTSCENE_SYSTEM.scene); - - const uint8_t expected[] = { 0, 1, 3, 4, 2 }; - assert_int_equal(insertOrderLogCount, sizeof(expected)); - assert_memory_equal(insertOrderLog, expected, sizeof(expected)); -} - -static cutsceneitem_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA } -}; -static cutscene_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET = { - .items = CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS) / - sizeof(cutsceneitem_t), - .itemsMax = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; - -// 2 items to start, +1 spare slot for the snippet's single item. -static cutsceneitem_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER_ITEMS[3] = { - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder0 }, - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { - .cutscene = &CUTSCENE_TEST_INSERT_AS_LAST_ITEM_SNIPPET - } - } -}; -static cutscene_t CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER = { - .items = CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER_ITEMS, - .itemCount = 2, - .itemsMax = sizeof(CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; - -static void test_cutsceneInsertAsLastItemEndsCutsceneNaturally( - void **state -) { - - insertOrderLogCount = 0; - cutsceneSystemInit(); - cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER); - - cutsceneSystemUpdate();// order0 done -> starts INSERT, cascades to orderA - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_AS_LAST_ITEM_OUTER); - // order0 and the INSERT item itself have both been popped off the - // front by now, leaving just orderA (currently running). - assert_int_equal(CUTSCENE_SYSTEM.scene->itemCount, 1); - - // orderA is the snippet's only item, and the snippet was spliced in as - // the outer's last item -- finishing it should reach the natural end - // of the whole cutscene, not get stuck. - cutsceneSystemUpdate(); - assert_null(CUTSCENE_SYSTEM.scene); - - const uint8_t expected[] = { 0, 3 }; - assert_int_equal(insertOrderLogCount, sizeof(expected)); - assert_memory_equal(insertOrderLog, expected, sizeof(expected)); -} - -static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA } -}; -static cutscene_t CUTSCENE_TEST_INSERT_NESTED_INNER = { - .items = CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS, - .itemCount = - sizeof(CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS) / sizeof(cutsceneitem_t), - .itemsMax = - sizeof(CUTSCENE_TEST_INSERT_NESTED_INNER_ITEMS) / sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; - -// INSERT is MID's first item, so entering MID (from OUTER) cascades -// straight through into INNER within the same Start chain, one level -// deeper than a single insert. MID is only ever read from as a splice -// source (see CUTSCENE_TEST_INSERT_NESTED_OUTER) - it's never itself the -// running scene, so it needs no spare capacity of its own. -static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS[] = { - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_NESTED_INNER } - }, - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderB } -}; -static cutscene_t CUTSCENE_TEST_INSERT_NESTED_MID = { - .items = CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS, - .itemCount = - sizeof(CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS) / sizeof(cutsceneitem_t), - .itemsMax = - sizeof(CUTSCENE_TEST_INSERT_NESTED_MID_ITEMS) / sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; - -// 3 items to start, +2 spare for MID's items, +1 more for MID's own -// nested INSERT(INNER) once MID's items become part of this array too. -static cutsceneitem_t CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS[6] = { - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder0 }, - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_NESTED_MID } - }, - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrder2 } -}; -static cutscene_t CUTSCENE_TEST_INSERT_NESTED_OUTER = { - .items = CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS, - .itemCount = 3, - .itemsMax = sizeof(CUTSCENE_TEST_INSERT_NESTED_OUTER_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NONE -}; - -static void test_cutsceneInsertNestsThroughMultipleLevels(void **state) { - - insertOrderLogCount = 0; - cutsceneSystemInit(); - cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_NESTED_OUTER); - - // order0 done -> starts INSERT(MID), cascading through INSERT(INNER) -> - // orderA, all within this one Update call's Start chain - both splices - // land in OUTER's own array. - cutsceneSystemUpdate(); - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_NESTED_OUTER); - // order0 and both INSERT items themselves have all been popped off the - // front by now, leaving orderA (currently running), orderB, order2. - assert_int_equal(CUTSCENE_SYSTEM.scene->itemCount, 3); - - cutsceneSystemUpdate();// orderA done -> continues on to orderB - cutsceneSystemUpdate();// orderB done -> continues on to order2 - cutsceneSystemUpdate();// order2 done -> ends naturally - assert_null(CUTSCENE_SYSTEM.scene); - - const uint8_t expected[] = { 0, 3, 4, 2 }; - assert_int_equal(insertOrderLogCount, sizeof(expected)); - assert_memory_equal(insertOrderLog, expected, sizeof(expected)); -} - -static cutsceneitem_t CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = recordOrderA } -}; -static cutscene_t CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET = { - .items = CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET_ITEMS, - .itemCount = 1, - .itemsMax = 1, - .pause = CUTSCENE_PAUSE_NONE -}; - -// No spare capacity at all - itemsMax equals itemCount, leaving no room -// to splice anything into it. -static cutsceneitem_t CUTSCENE_TEST_INSERT_NO_CAPACITY_OUTER_ITEMS[1] = { - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_NO_CAPACITY_SNIPPET } - } -}; -static cutscene_t CUTSCENE_TEST_INSERT_NO_CAPACITY_OUTER = { - .items = CUTSCENE_TEST_INSERT_NO_CAPACITY_OUTER_ITEMS, - .itemCount = 1, - .itemsMax = 1, - .pause = CUTSCENE_PAUSE_NONE -}; - -static void test_cutsceneInsertAssertsWhenExceedingItemsMax(void **state) { - - cutsceneSystemInit(); - - expect_assert_failure( - cutsceneSystemStartCutscene(&CUTSCENE_TEST_INSERT_NO_CAPACITY_OUTER) - ); -} - -static void noopInsertCallback(void *userData) {} - -static cutsceneitem_t CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_CALLBACK, .callback = noopInsertCallback } -}; -static cutscene_t CUTSCENE_TEST_INSERT_PERSIST_SNIPPET = { - .items = CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS, - .itemCount = sizeof(CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS) / - sizeof(cutsceneitem_t), - .itemsMax = sizeof(CUTSCENE_TEST_INSERT_PERSIST_SNIPPET_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_ALL -}; - -// 1 item to start, +1 spare slot for the snippet's single item. -static cutsceneitem_t CUTSCENE_TEST_INSERT_PERSIST_OUTER_ITEMS[2] = { - { - .type = CUTSCENE_ITEM_TYPE_INSERT, - .insert = { .cutscene = &CUTSCENE_TEST_INSERT_PERSIST_SNIPPET } - } -}; -static cutscene_t CUTSCENE_TEST_INSERT_PERSIST_OUTER = { - .items = CUTSCENE_TEST_INSERT_PERSIST_OUTER_ITEMS, - .itemCount = 1, - .itemsMax = sizeof(CUTSCENE_TEST_INSERT_PERSIST_OUTER_ITEMS) / - sizeof(cutsceneitem_t), - .pause = CUTSCENE_PAUSE_NPC -}; - -static void test_cutsceneInsertDoesNotResetPauseOrInteractEntities( - void **state -) { - - cutsceneSystemInit(); - entityInit(&ENTITIES[3], ENTITY_TYPE_PLAYER); - entityInit(&ENTITIES[4], ENTITY_TYPE_NPC); - - cutsceneSystemStartCutsceneWith( - &CUTSCENE_TEST_INSERT_PERSIST_OUTER, &ENTITIES[3], &ENTITIES[4] - ); - - // The INSERT item's Start already spliced the snippet into place and - // cascaded into it by now - and since the INSERT item itself has since - // been popped off the front, only the snippet's one item is left. - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INSERT_PERSIST_OUTER); - assert_int_equal(CUTSCENE_SYSTEM.scene->itemCount, 1); - assert_ptr_equal(CUTSCENE_SYSTEM.refs.entityInteract, &ENTITIES[3]); - assert_ptr_equal(CUTSCENE_SYSTEM.refs.entityInteracted, &ENTITIES[4]); - // The snippet declares ALL, but INSERT doesn't apply it -- the outer - // cutscene's NPC pause, already in effect, stands. - assert_int_equal(CUTSCENE_SYSTEM.pause, CUTSCENE_PAUSE_NPC); -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_cutsceneSystemStartSetsUpInitialItem), - cmocka_unit_test(test_cutsceneSystemNestedCutsceneIsOneWayJump), - cmocka_unit_test(test_cutsceneSystemUpdateIsNoopWithNoActiveCutscene), - cmocka_unit_test(test_cutsceneSystemStartWithSetsInteractEntities), - cmocka_unit_test(test_cutsceneSystemGetEntitySentinelsRequireBeingSet), - cmocka_unit_test(test_cutsceneSystemGetEntityDirectIndexUpdatesLastRef), - cmocka_unit_test(test_cutsceneSystemGetAreaId), - cmocka_unit_test(test_cutsceneSystemGetTextMiniId), - cmocka_unit_test(test_cutsceneSystemDisposeResetsState), - cmocka_unit_test( - test_cutsceneSystemPrepareKeepsLoadedFileWhenReenteringLoadedScene - ), - cmocka_unit_test(test_cutsceneInsertSplicesItemsInPlaceThenResumes), - cmocka_unit_test(test_cutsceneInsertAsLastItemEndsCutsceneNaturally), - cmocka_unit_test(test_cutsceneInsertNestsThroughMultipleLevels), - cmocka_unit_test(test_cutsceneInsertAssertsWhenExceedingItemsMax), - cmocka_unit_test(test_cutsceneInsertDoesNotResetPauseOrInteractEntities), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/entity/CMakeLists.txt b/test/rpg/entity/CMakeLists.txt deleted file mode 100644 index 453ff3da..00000000 --- a/test/rpg/entity/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 - -include(dusktest) - -# Tests -dusktest(test_entity.c) -target_sources(test_entity PRIVATE entitytestfixture.c) - -dusktest(test_npc.c) -target_sources(test_npc PRIVATE entitytestfixture.c) - -dusktest(test_entityinteract.c) -target_sources(test_entityinteract PRIVATE entitytestfixture.c) - -dusktest(test_entityitem.c) -target_sources(test_entityitem PRIVATE entitytestfixture.c) diff --git a/test/rpg/entity/entitytestfixture.c b/test/rpg/entity/entitytestfixture.c deleted file mode 100644 index bbc9d447..00000000 --- a/test/rpg/entity/entitytestfixture.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 "entitytestfixture.h" -#include "rpg/overworld/map.h" -#include "rpg/overworld/tile.h" -#include "util/memory.h" -#include "util/string.h" - -void entityTestFixtureReset(void) { - memoryZero(ENTITIES, sizeof(ENTITIES)); - - memoryZero(&MAP, sizeof(map_t)); - stringCopy(MAP.name, "test", MAP_NAME_MAX); - MAP.chunkPosition = (chunkpos_t){ 0, 0, 0 }; - - chunk_t *chunk = &MAP.chunks[0]; - chunk->position = (chunkpos_t){ 0, 0, 0 }; - for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) { - chunk->tiles[i] = (tile_t){ .shape = TILE_SHAPE_GROUND, .z = 0 }; - } - for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) { - chunk->entities[i] = 0xFF; - } - - MAP.chunkOrder[0] = chunk; -} diff --git a/test/rpg/entity/entitytestfixture.h b/test/rpg/entity/entitytestfixture.h deleted file mode 100644 index 89e591e4..00000000 --- a/test/rpg/entity/entitytestfixture.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 "rpg/entity/entity.h" - -/** - * Resets ENTITIES[] to all-empty and builds a single minimal, synchronously - * "loaded" chunk at chunk position (0, 0, 0) on the MAP global, entirely - * TILE_SHAPE_GROUND at local Z 0. Sufficient for entityWalk/Turn/Run, chunk - * bookkeeping, and NPC movement tests -- does not exercise the real async - * chunk-loading pipeline (mapChunkLoad). - * - * World positions with x/y in [0, CHUNK_WIDTH-1]/[0, CHUNK_HEIGHT-1] and - * z == 0 fall within the fixture chunk. Individual tests may override - * specific columns afterward (e.g. to place a ramp). - */ -void entityTestFixtureReset(void); diff --git a/test/rpg/entity/test_entity.c b/test/rpg/entity/test_entity.c deleted file mode 100644 index 54a2bcf0..00000000 --- a/test/rpg/entity/test_entity.c +++ /dev/null @@ -1,300 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "entitytestfixture.h" -#include "rpg/overworld/map.h" -#include "rpg/overworld/worldpos.h" - -static void setTile( - const worldpos_t pos, const tileshape_t shape, const uint8_t z -) { - chunktileindex_t index = worldPosToChunkTileIndex(&pos); - MAP.chunks[0].tiles[index] = (tile_t){ .shape = shape, .z = z }; -} - -static void test_entityInit(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[3], ENTITY_TYPE_ITEM); - assert_int_equal(ENTITIES[3].id, 3); - assert_int_equal(ENTITIES[3].type, ENTITY_TYPE_ITEM); - assert_int_equal(ENTITIES[3].chunkIndex, 0xFF); - assert_int_equal(ENTITIES[3].globalId, ENTITY_GLOBAL_ID_NULL); - // entityItemInit wires the interact callback -- confirms init dispatch ran. - assert_int_equal(ENTITIES[3].interact.type, ENTITY_INTERACT_CALLBACK); - - expect_assert_failure(entityInit(NULL, ENTITY_TYPE_ITEM)); - expect_assert_failure(entityInit(&ENTITIES[0], ENTITY_TYPE_NULL)); - expect_assert_failure(entityInit(&ENTITIES[0], ENTITY_TYPE_COUNT)); - - entity_t offBounds; - expect_assert_failure(entityInit(&offBounds, ENTITY_TYPE_ITEM)); -} - -static void test_entityGetAvailable(void **state) { - - entityTestFixtureReset(); - - assert_int_equal(entityGetAvailable(), 0); - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - assert_int_equal(entityGetAvailable(), 1); - entityInit(&ENTITIES[1], ENTITY_TYPE_NPC); - assert_int_equal(entityGetAvailable(), 2); -} - -static void test_entityGetAt(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = (worldpos_t){ 3, 4, 0 }; - - assert_ptr_equal(entityGetAt((worldpos_t){ 3, 4, 0 }), &ENTITIES[0]); - assert_null(entityGetAt((worldpos_t){ 3, 4, 1 })); - assert_null(entityGetAt((worldpos_t){ 0, 0, 0 })); -} - -static void test_entityGetByGlobalId(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[2], ENTITY_TYPE_NPC); - ENTITIES[2].globalId = 42; - - assert_ptr_equal(entityGetByGlobalId(42), &ENTITIES[2]); - assert_null(entityGetByGlobalId(43)); - assert_null(entityGetByGlobalId(ENTITY_GLOBAL_ID_NULL)); -} - -static void test_entityCanUnload(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].globalId = ENTITY_GLOBAL_ID_START - 1; - assert_true(entityCanUnload(&ENTITIES[0])); - - ENTITIES[0].globalId = ENTITY_GLOBAL_ID_START; - assert_false(entityCanUnload(&ENTITIES[0])); -} - -static void test_entityCanTurnWalkRun(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - assert_true(entityCanTurn(&ENTITIES[0])); - assert_true(entityCanWalk(&ENTITIES[0])); - assert_true(entityCanRun(&ENTITIES[0])); - - ENTITIES[0].animation = ENTITY_ANIM_WALK; - assert_false(entityCanTurn(&ENTITIES[0])); - assert_false(entityCanWalk(&ENTITIES[0])); - assert_false(entityCanRun(&ENTITIES[0])); - - ENTITIES[0].animation = ENTITY_ANIM_IDLE; - ENTITIES[0].walkEndCooldown = 0.1f; - assert_false(entityCanTurn(&ENTITIES[0]));// cooldown blocks turning only - assert_true(entityCanWalk(&ENTITIES[0])); -} - -static void test_entityTurn(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - entityTurn(&ENTITIES[0], ENTITY_DIR_EAST); - assert_int_equal(ENTITIES[0].direction, ENTITY_DIR_EAST); - assert_int_equal(ENTITIES[0].animation, ENTITY_ANIM_TURN); - - // Can't turn again mid-turn. - ENTITIES[0].direction = ENTITY_DIR_NORTH; - entityTurn(&ENTITIES[0], ENTITY_DIR_WEST); - assert_int_equal(ENTITIES[0].direction, ENTITY_DIR_NORTH); -} - -static void test_entityWalkOnOpenGround(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = (worldpos_t){ 5, 5, 0 }; - - entityWalk(&ENTITIES[0], ENTITY_DIR_NORTH); - assert_int_equal(ENTITIES[0].position.x, 5); - assert_int_equal(ENTITIES[0].position.y, 6); - assert_int_equal(ENTITIES[0].position.z, 0); - assert_int_equal(ENTITIES[0].animation, ENTITY_ANIM_WALK); - assert_int_equal(ENTITIES[0].direction, ENTITY_DIR_NORTH); - // entityUpdateChunk should have assigned it to the fixture chunk. - assert_int_equal(ENTITIES[0].chunkIndex, 0); - assert_int_equal(MAP.chunks[0].entities[0], 0); -} - -static void test_entityWalkBlockedAtChunkEdge(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = (worldpos_t){ 0, 0, 0 }; - - // West/south of the loaded chunk is unloaded (TILE_NULL) -- blocked. - entityWalk(&ENTITIES[0], ENTITY_DIR_WEST); - assert_int_equal(ENTITIES[0].position.x, 0); - assert_int_equal(ENTITIES[0].animation, ENTITY_ANIM_IDLE); -} - -static void test_entityWalkBlockedByOtherEntity(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = (worldpos_t){ 5, 5, 0 }; - - entityInit(&ENTITIES[1], ENTITY_TYPE_NPC); - ENTITIES[1].position = (worldpos_t){ 5, 6, 0 };// directly north - - entityWalk(&ENTITIES[0], ENTITY_DIR_NORTH); - assert_int_equal(ENTITIES[0].position.x, 5); - assert_int_equal(ENTITIES[0].position.y, 5); - assert_int_equal(ENTITIES[0].animation, ENTITY_ANIM_IDLE); -} - -static void test_entityWalkCannotWhileNotIdle(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = (worldpos_t){ 5, 5, 0 }; - ENTITIES[0].animation = ENTITY_ANIM_WALK; - - entityWalk(&ENTITIES[0], ENTITY_DIR_NORTH); - assert_int_equal(ENTITIES[0].position.y, 5);// unchanged -} - -// Ramp at (5,5) facing north, raised GROUND at (5,6)/z1 -- walking north -// off the ramp raises the entity by one Z layer, and walking back south -// from the raised tile falls back down onto the ramp. -static void setUpRamp(void) { - setTile((worldpos_t){ 5, 5, 0 }, TILE_SHAPE_RAMP_NORTH, 0); - setTile((worldpos_t){ 5, 6, 0 }, TILE_SHAPE_GROUND, 1); -} - -static void test_entityWalkUpRamp(void **state) { - - entityTestFixtureReset(); - setUpRamp(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = (worldpos_t){ 5, 5, 0 }; - - entityWalk(&ENTITIES[0], ENTITY_DIR_NORTH); - assert_int_equal(ENTITIES[0].position.x, 5); - assert_int_equal(ENTITIES[0].position.y, 6); - assert_int_equal(ENTITIES[0].position.z, 1); -} - -static void test_entityWalkFallDownRamp(void **state) { - - entityTestFixtureReset(); - setUpRamp(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = (worldpos_t){ 5, 6, 1 }; - - entityWalk(&ENTITIES[0], ENTITY_DIR_SOUTH); - assert_int_equal(ENTITIES[0].position.x, 5); - assert_int_equal(ENTITIES[0].position.y, 5); - assert_int_equal(ENTITIES[0].position.z, 0); -} - -static void test_entityWalkCannotClimbRampFromTheSide(void **state) { - - entityTestFixtureReset(); - setUpRamp(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = (worldpos_t){ 5, 5, 0 }; - - // Only NORTH climbs this ramp -- EAST off the ramp tile is just blocked - // ground movement, not a climb. - entityWalk(&ENTITIES[0], ENTITY_DIR_EAST); - assert_int_equal(ENTITIES[0].position.x, 6); - assert_int_equal(ENTITIES[0].position.z, 0); -} - -static void test_entityRun(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = (worldpos_t){ 5, 5, 0 }; - - entityRun(&ENTITIES[0], ENTITY_DIR_NORTH); - assert_int_equal(ENTITIES[0].position.y, 6); - assert_int_equal(ENTITIES[0].animation, ENTITY_ANIM_RUN); -} - -static void test_entitySetChunkAndUpdateChunk(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - entitySetChunk(&ENTITIES[0], 0); - assert_int_equal(ENTITIES[0].chunkIndex, 0); - assert_int_equal(MAP.chunks[0].entities[0], 0); - - entitySetChunk(&ENTITIES[0], 0xFF); - assert_int_equal(ENTITIES[0].chunkIndex, 0xFF); - assert_int_equal(MAP.chunks[0].entities[0], 0xFF); - - ENTITIES[0].position = (worldpos_t){ 3, 3, 0 }; - entityUpdateChunk(&ENTITIES[0]); - assert_int_equal(ENTITIES[0].chunkIndex, 0); -} - -static void test_entityPositionSet(void **state) { - - entityTestFixtureReset(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].animation = ENTITY_ANIM_WALK; - ENTITIES[0].walkEndCooldown = 5; - - entityPositionSet(&ENTITIES[0], (worldpos_t){ 7, 8, 0 }); - assert_int_equal(ENTITIES[0].position.x, 7); - assert_int_equal(ENTITIES[0].position.y, 8); - assert_int_equal(ENTITIES[0].animation, ENTITY_ANIM_IDLE); - assert_int_equal(ENTITIES[0].walkEndCooldown, 0); - assert_int_equal(ENTITIES[0].chunkIndex, 0);// updated as a side effect -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_entityInit), - cmocka_unit_test(test_entityGetAvailable), - cmocka_unit_test(test_entityGetAt), - cmocka_unit_test(test_entityGetByGlobalId), - cmocka_unit_test(test_entityCanUnload), - cmocka_unit_test(test_entityCanTurnWalkRun), - cmocka_unit_test(test_entityTurn), - cmocka_unit_test(test_entityWalkOnOpenGround), - cmocka_unit_test(test_entityWalkBlockedAtChunkEdge), - cmocka_unit_test(test_entityWalkBlockedByOtherEntity), - cmocka_unit_test(test_entityWalkCannotWhileNotIdle), - cmocka_unit_test(test_entityWalkUpRamp), - cmocka_unit_test(test_entityWalkFallDownRamp), - cmocka_unit_test(test_entityWalkCannotClimbRampFromTheSide), - cmocka_unit_test(test_entityRun), - cmocka_unit_test(test_entitySetChunkAndUpdateChunk), - cmocka_unit_test(test_entityPositionSet), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/entity/test_entityinteract.c b/test/rpg/entity/test_entityinteract.c deleted file mode 100644 index 4980e21f..00000000 --- a/test/rpg/entity/test_entityinteract.c +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "entitytestfixture.h" -#include "ui/rpg/textbox/uitextboxmain.h" -#include "ui/focus/uifocus.h" -#include "rpg/cutscene/cutscenesystem.h" - -static cutsceneitem_t CUTSCENE_TEST_INTERACT_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f } -}; -static cutscene_t CUTSCENE_TEST_INTERACT = { - .items = CUTSCENE_TEST_INTERACT_ITEMS, - .itemCount = 1, - .itemsMax = 1, - .pause = CUTSCENE_PAUSE_NONE -}; - -static entity_t *player; -static entity_t *target; - -static void resetInteractFixture(void) { - entityTestFixtureReset(); - uiFocusInit(); - uiTextboxMainInit(); - uiTextboxMainFocusClosed(NULL);// force-clear stale focus from a prior test - cutsceneSystemInit(); - - entityInit(&ENTITIES[0], ENTITY_TYPE_PLAYER); - entityInit(&ENTITIES[1], ENTITY_TYPE_NPC); - player = &ENTITIES[0]; - target = &ENTITIES[1]; - player->direction = ENTITY_DIR_NORTH; -} - -static void test_entityInteractWithNull(void **state) { - - resetInteractFixture(); - - target->interact.type = ENTITY_INTERACT_NULL; - entityInteractWith(player, target);// should not crash or change anything - assert_int_equal(target->interact.type, ENTITY_INTERACT_NULL); -} - -static void test_entityInteractWithPrintTurnsNpcToFacePlayer(void **state) { - - resetInteractFixture(); - - target->interact.type = ENTITY_INTERACT_PRINT; - target->interact.data.message = "Hello!"; - - entityInteractWith(player, target); - - assert_true(uiTextboxMainIsActive()); - assert_int_equal( - target->data.npc.interactState, NPC_INTERACT_STATE_CONVERSING - ); - // NPC turns to face the opposite of the player's facing direction. - assert_int_equal(target->direction, entityDirGetOpposite(player->direction)); -} - -static entity_t *callbackPlayerArg; -static entity_t *callbackTargetArg; -static uint8_t callbackCount; - -static void recordInteractCallback(entity_t *p, entity_t *t) { - callbackPlayerArg = p; - callbackTargetArg = t; - callbackCount++; -} - -static void test_entityInteractWithCallback(void **state) { - - resetInteractFixture(); - callbackCount = 0; - - target->interact.type = ENTITY_INTERACT_CALLBACK; - target->interact.data.callback = recordInteractCallback; - - entityInteractWith(player, target); - - assert_int_equal(callbackCount, 1); - assert_ptr_equal(callbackPlayerArg, player); - assert_ptr_equal(callbackTargetArg, target); -} - -static void test_entityInteractWithCallbackRequiresNonNull(void **state) { - - resetInteractFixture(); - - target->interact.type = ENTITY_INTERACT_CALLBACK; - target->interact.data.callback = NULL; - - expect_assert_failure(entityInteractWith(player, target)); -} - -static void test_entityInteractWithCutsceneStartsIt(void **state) { - - resetInteractFixture(); - - target->interact.type = ENTITY_INTERACT_CUTSCENE; - target->interact.data.cutscene = &CUTSCENE_TEST_INTERACT; - - entityInteractWith(player, target); - - assert_ptr_equal(CUTSCENE_SYSTEM.scene, &CUTSCENE_TEST_INTERACT); - assert_ptr_equal(CUTSCENE_SYSTEM.entityInteract, player); - assert_ptr_equal(CUTSCENE_SYSTEM.entityInteracted, target); -} - -static void test_entityInteractWithRequiresNonNullEntities(void **state) { - - resetInteractFixture(); - - expect_assert_failure(entityInteractWith(NULL, target)); - expect_assert_failure(entityInteractWith(player, NULL)); -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_entityInteractWithNull), - cmocka_unit_test(test_entityInteractWithPrintTurnsNpcToFacePlayer), - cmocka_unit_test(test_entityInteractWithCallback), - cmocka_unit_test(test_entityInteractWithCallbackRequiresNonNull), - cmocka_unit_test(test_entityInteractWithCutsceneStartsIt), - cmocka_unit_test(test_entityInteractWithRequiresNonNullEntities), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/entity/test_entityitem.c b/test/rpg/entity/test_entityitem.c deleted file mode 100644 index f4936ec2..00000000 --- a/test/rpg/entity/test_entityitem.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 "dusktest.h" -#include "entitytestfixture.h" -#include "ui/rpg/textbox/uitextboxmain.h" -#include "ui/focus/uifocus.h" - -// An arbitrary, non-null itemid_t -- entityItemSet just stores/reports it -// verbatim, it doesn't need a real item registered in ITEM. -#define TEST_ITEM_ID ((itemid_t)1) - -static entity_t *setUpItemEntity(void) { - entityTestFixtureReset(); - uiFocusInit(); - uiTextboxMainInit(); - uiTextboxMainFocusClosed(NULL);// force-clear stale focus from a prior test - - entityInit(&ENTITIES[0], ENTITY_TYPE_ITEM); - return &ENTITIES[0]; -} - -static void test_entityItemInit(void **state) { - - entity_t *item = setUpItemEntity(); - - assert_int_equal(item->interact.type, ENTITY_INTERACT_CALLBACK); - assert_ptr_equal(item->interact.data.callback, entityItemInteract); -} - -static void test_entityItemSet(void **state) { - - entity_t *item = setUpItemEntity(); - - entityItemSet(item, TEST_ITEM_ID, 3); - assert_int_equal(item->data.item.item, TEST_ITEM_ID); - assert_int_equal(item->data.item.quantity, 3); - assert_false(item->data.item.collected); -} - -// entityItemInteract itself is NOT covered here: it unconditionally calls -// itemGive(), which calls itemGetName(), which dereferences -// LOCALE.entry->data.locale -- a real locale asset populated only by the -// async asset-loading pipeline (see localemanager.h/assetlocaleloader.h). -// That's exactly the asset-loading dependency this test pass scoped out; -// faking it cheaply isn't possible without hand-building the loader's -// internal hash format, which is more coupling than it's worth. Backpack -// bookkeeping itself is already covered by test/item/test_inventory.c-style -// tests at the inventory/backpack layer. - -static void test_entityItemMovementWaitsForCollectionAndTextbox(void **state) { - - entity_t *item = setUpItemEntity(); - - // Not collected yet -- stays. - entityItemMovement(item); - assert_int_equal(item->type, ENTITY_TYPE_ITEM); - - // Collected (set directly -- see note above on why entityItemInteract - // itself isn't driven here), but a message is still showing -- stays. - item->data.item.collected = true; - uiTextboxMainSetText("Picked up!"); - entityItemMovement(item); - assert_int_equal(item->type, ENTITY_TYPE_ITEM); - - // Once the textbox is dismissed, the entity despawns. - uiFocusPop(); - assert_false(uiTextboxMainIsActive()); - entityItemMovement(item); - assert_int_equal(item->type, ENTITY_TYPE_NULL); -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_entityItemInit), - cmocka_unit_test(test_entityItemSet), - cmocka_unit_test(test_entityItemMovementWaitsForCollectionAndTextbox), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/entity/test_npc.c b/test/rpg/entity/test_npc.c deleted file mode 100644 index aaa5174b..00000000 --- a/test/rpg/entity/test_npc.c +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "entitytestfixture.h" -#include "rpg/cutscene/cutscenesystem.h" -#include "time/time.h" - -static entity_t *setUpNpc(const worldpos_t position) { - entityTestFixtureReset(); - entityInit(&ENTITIES[0], ENTITY_TYPE_NPC); - ENTITIES[0].position = position; - return &ENTITIES[0]; -} - -static void test_npcSetMoveType(void **state) { - - entity_t *npc = setUpNpc((worldpos_t){ 8, 8, 0 }); - - npcSetMoveType(npc, NPC_MOVE_TYPE_RANDOM_TURN); - assert_int_equal(npc->data.npc.moveType, NPC_MOVE_TYPE_RANDOM_TURN); - // Init ran: timer was seeded within the default frequency range. - assert_true(npc->data.npc.moveData.randomTurn.timer > 0.0f); -} - -static void test_npcRandomTurnMovementFiresOnceTimerElapses(void **state) { - - entity_t *npc = setUpNpc((worldpos_t){ 8, 8, 0 }); - npcSetMoveType(npc, NPC_MOVE_TYPE_RANDOM_TURN); - npc->data.npc.moveData.randomTurn.timer = 0.05f; - - TIME.delta = 0.01f; - npcRandomTurnMovement(npc); - assert_int_equal(npc->animation, ENTITY_ANIM_IDLE);// timer not elapsed yet - - TIME.delta = 1.0f; - npcRandomTurnMovement(npc); - assert_int_equal(npc->animation, ENTITY_ANIM_TURN); - // Timer is reseeded, not left at/below zero. - assert_true(npc->data.npc.moveData.randomTurn.timer > 0.0f); -} - -static void test_npcRandomWalkMovementFiresOnceTimerElapses(void **state) { - - entity_t *npc = setUpNpc((worldpos_t){ 8, 8, 0 }); - npcSetMoveType(npc, NPC_MOVE_TYPE_RANDOM_WALK); - npc->data.npc.moveData.randomWalk.timer = 0.05f; - - TIME.delta = 0.01f; - npcRandomWalkMovement(npc); - assert_int_equal(npc->animation, ENTITY_ANIM_IDLE); - - TIME.delta = 1.0f; - npcRandomWalkMovement(npc); - // Open ground on all sides -- whichever direction is chosen, it moves. - assert_int_equal(npc->animation, ENTITY_ANIM_WALK); -} - -static void test_npcRandomTurnAndWalkMovementRunsBothIndependently( - void **state -) { - - entity_t *npc = setUpNpc((worldpos_t){ 8, 8, 0 }); - npcSetMoveType(npc, NPC_MOVE_TYPE_RANDOM_TURN_AND_WALK); - npcrandomturnandwalk_t *tw = &npc->data.npc.moveData.randomTurnAndWalk; - tw->turn.timer = 999.0f;// don't fire this tick - tw->walk.timer = 0.05f;// fires this tick - - TIME.delta = 1.0f; - npcRandomTurnAndWalkMovement(npc); - assert_int_equal(npc->animation, ENTITY_ANIM_WALK); - assert_true(tw->turn.timer > 900.0f);// unaffected by the walk firing -} - -static void test_npcMovementGatedByCutscenePause(void **state) { - - entity_t *npc = setUpNpc((worldpos_t){ 8, 8, 0 }); - npcSetMoveType(npc, NPC_MOVE_TYPE_RANDOM_WALK); - npc->data.npc.moveData.randomWalk.timer = 0.0f; - TIME.delta = 1.0f; - - CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NPC; - npcMovement(npc); - assert_int_equal(npc->animation, ENTITY_ANIM_IDLE);// blocked by pause - - CUTSCENE_SYSTEM.pause = CUTSCENE_PAUSE_NONE; - npcMovement(npc); - assert_int_equal(npc->animation, ENTITY_ANIM_WALK); -} - -static void test_npcMovementGatedByInteractState(void **state) { - - entity_t *npc = setUpNpc((worldpos_t){ 8, 8, 0 }); - npcSetMoveType(npc, NPC_MOVE_TYPE_RANDOM_WALK); - npc->data.npc.moveData.randomWalk.timer = 0.0f; - TIME.delta = 1.0f; - - npc->data.npc.interactState = NPC_INTERACT_STATE_CONVERSING; - npcMovement(npc); - assert_int_equal(npc->animation, ENTITY_ANIM_IDLE);// blocked while conversing - - npc->data.npc.interactState = NPC_INTERACT_STATE_NONE; - npcMovement(npc); - assert_int_equal(npc->animation, ENTITY_ANIM_WALK); -} - -static void test_npcPathMovementFollowsAndLoopsWaypoints(void **state) { - - entity_t *npc = setUpNpc((worldpos_t){ 5, 5, 0 }); - npcSetMoveType(npc, NPC_MOVE_TYPE_PATH); - npcpath_t *path = &npc->data.npc.moveData.path; - npcPathAddNode(&npc->data.npc, (worldpos_t){ 5, 6, 0 }); - npcPathAddNode(&npc->data.npc, (worldpos_t){ 5, 5, 0 }); - - npcPathMovement(npc);// steps toward waypoint 0 - assert_int_equal(npc->position.x, 5); - assert_int_equal(npc->position.y, 6); - assert_int_equal(path->index, 0);// not yet considered "arrived" - - npc->animation = ENTITY_ANIM_IDLE;// simulate the walk animation finishing - npcPathMovement(npc);// arrives at waypoint 0, advances, steps toward wp 1 - assert_int_equal(path->index, 1); - assert_int_equal(npc->position.x, 5); - assert_int_equal(npc->position.y, 5); -} - -static void test_npcPathMovementNoopWithEmptyPath(void **state) { - - entity_t *npc = setUpNpc((worldpos_t){ 5, 5, 0 }); - npcSetMoveType(npc, NPC_MOVE_TYPE_PATH); - - npcPathMovement(npc); - assert_int_equal(npc->position.x, 5); - assert_int_equal(npc->position.y, 5); - assert_int_equal(npc->animation, ENTITY_ANIM_IDLE); -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_npcSetMoveType), - cmocka_unit_test(test_npcRandomTurnMovementFiresOnceTimerElapses), - cmocka_unit_test(test_npcRandomWalkMovementFiresOnceTimerElapses), - cmocka_unit_test(test_npcRandomTurnAndWalkMovementRunsBothIndependently), - cmocka_unit_test(test_npcMovementGatedByCutscenePause), - cmocka_unit_test(test_npcMovementGatedByInteractState), - cmocka_unit_test(test_npcPathMovementFollowsAndLoopsWaypoints), - cmocka_unit_test(test_npcPathMovementNoopWithEmptyPath), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/overworld/CMakeLists.txt b/test/rpg/overworld/CMakeLists.txt deleted file mode 100644 index 7f8ecc94..00000000 --- a/test/rpg/overworld/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 - -include(dusktest) - -# Tests -dusktest(test_maparea.c) - -# Subdirs \ No newline at end of file diff --git a/test/rpg/overworld/test_maparea.c b/test/rpg/overworld/test_maparea.c deleted file mode 100644 index 259200af..00000000 --- a/test/rpg/overworld/test_maparea.c +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" -#include "rpg/overworld/maparea.h" -#include "rpg/overworld/map.h" -#include "rpg/entity/entity.h" -#include "util/memory.h" - -static uint8_t callbackCount; -static uint8_t lastTrigger; - -static void recordCallback(entity_t *entity, const uint8_t trigger) { - callbackCount++; - lastTrigger = trigger; -} - -static void resetMapAreas(void) { - memoryZero(MAP_AREAS, sizeof(MAP_AREAS)); - callbackCount = 0; - lastTrigger = 0; -} - -static void test_mapAreaInit(void **state) { - - - maparea_t area; - const worldpos_t min = { 5, 5, 0 }; - const worldpos_t max = { 0, 0, 0 }; - - // min/max should be normalized regardless of argument order. - mapAreaInit( - &area, min, max, recordCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - assert_int_equal(area.min.x, 0); - assert_int_equal(area.min.y, 0); - assert_int_equal(area.max.x, 5); - assert_int_equal(area.max.y, 5); - assert_int_equal(area.triggerCount, 0); - - expect_assert_failure( - mapAreaInit(&area, min, max, NULL, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL) - ); -} - -static void test_mapAreaIsInside(void **state) { - - - maparea_t area; - mapAreaInit( - &area, (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 10, 10, 0 }, - recordCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - - assert_true(mapAreaIsInside(&area, (worldpos_t){ 5, 5, 0 })); - assert_true(mapAreaIsInside(&area, (worldpos_t){ 0, 0, 0 }));// inclusive min - assert_true( - mapAreaIsInside(&area, (worldpos_t){ 10, 10, 0 }) - );// inclusive max - assert_false(mapAreaIsInside(&area, (worldpos_t){ 11, 5, 0 })); - assert_false(mapAreaIsInside(&area, (worldpos_t){ 5, 5, 1 })); -} - -static void test_mapAreaShouldNotify(void **state) { - - - maparea_t area; - mapAreaInit( - &area, (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 10, 10, 0 }, - recordCallback, MAP_AREA_NOTIFY_PLAYER, MAP_TRIGGER_ALL - ); - - entityInit(&ENTITIES[0], ENTITY_TYPE_PLAYER); - entityInit(&ENTITIES[1], ENTITY_TYPE_NPC); - entityInit(&ENTITIES[2], ENTITY_TYPE_ITEM); - - assert_true(mapAreaShouldNotify(&area, &ENTITIES[0])); - assert_false(mapAreaShouldNotify(&area, &ENTITIES[1]));// not notified - assert_false(mapAreaShouldNotify(&area, &ENTITIES[2]));// item never notifies - - area.notify = MAP_AREA_NOTIFY_ALL; - assert_true(mapAreaShouldNotify(&area, &ENTITIES[1])); -} - -static void test_mapAreaAddAndRemove(void **state) { - - resetMapAreas(); - - uint8_t id = mapAreaAdd( - (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, - recordCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - assert_int_equal(id, 0); - assert_ptr_equal(MAP_AREAS[0].callback, recordCallback); - - // Adding again should reuse the next free slot, not the same one. - uint8_t id2 = mapAreaAdd( - (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, - recordCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - assert_int_equal(id2, 1); - - mapAreaRemove(id); - assert_null(MAP_AREAS[0].callback);// slot freed - - // Removing frees the slot for reuse. - uint8_t id3 = mapAreaAdd( - (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, - recordCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - assert_int_equal(id3, 0); -} - -static void test_mapAreaCheckEntityTriggersEnterStepExit(void **state) { - - resetMapAreas(); - - mapAreaAdd( - (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, - recordCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - - entityInit(&ENTITIES[0], ENTITY_TYPE_PLAYER); - ENTITIES[0].position = (worldpos_t){ 10, 10, 0 };// outside - - // Outside -> outside: no callback. - mapAreaCheckEntity(&ENTITIES[0]); - assert_int_equal(callbackCount, 0); - assert_int_equal(MAP_AREAS[0].triggerCount, 0); - - // Outside -> inside: ENTER. - ENTITIES[0].position = (worldpos_t){ 2, 2, 0 }; - mapAreaCheckEntity(&ENTITIES[0]); - assert_int_equal(callbackCount, 1); - assert_int_equal(lastTrigger, MAP_TRIGGER_ENTER); - assert_int_equal(MAP_AREAS[0].triggerCount, 1); - - // Inside -> inside: STEP, every subsequent call. - mapAreaCheckEntity(&ENTITIES[0]); - assert_int_equal(callbackCount, 2); - assert_int_equal(lastTrigger, MAP_TRIGGER_STEP); - - mapAreaCheckEntity(&ENTITIES[0]); - assert_int_equal(callbackCount, 3); - assert_int_equal(lastTrigger, MAP_TRIGGER_STEP); - - // Inside -> outside: EXIT. - ENTITIES[0].position = (worldpos_t){ 10, 10, 0 }; - mapAreaCheckEntity(&ENTITIES[0]); - assert_int_equal(callbackCount, 4); - assert_int_equal(lastTrigger, MAP_TRIGGER_EXIT); -} - -static void test_mapAreaCheckEntityRespectsTriggerMask(void **state) { - - resetMapAreas(); - - // Only interested in ENTER, not STEP or EXIT. - mapAreaAdd( - (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, - recordCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ENTER - ); - - entityInit(&ENTITIES[0], ENTITY_TYPE_PLAYER); - ENTITIES[0].position = (worldpos_t){ 2, 2, 0 }; - - mapAreaCheckEntity(&ENTITIES[0]);// ENTER: fires - assert_int_equal(callbackCount, 1); - - mapAreaCheckEntity(&ENTITIES[0]);// STEP: masked out - assert_int_equal(callbackCount, 1); - - ENTITIES[0].position = (worldpos_t){ 10, 10, 0 }; - mapAreaCheckEntity(&ENTITIES[0]);// EXIT: masked out - assert_int_equal(callbackCount, 1); -} - -static void test_mapAreaCanUnload(void **state) { - - resetMapAreas(); - memoryZero(&MAP, sizeof(map_t)); - - maparea_t area; - mapAreaInit( - &area, (worldpos_t){ 0, 0, 0 }, (worldpos_t){ 5, 5, 0 }, - recordCallback, MAP_AREA_NOTIFY_ALL, MAP_TRIGGER_ALL - ); - - // No chunk overlaps the area's bounds (all chunks default to position 0, - // 0, 0 after memoryZero -- move them far away first). - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - MAP.chunks[i].position = (chunkpos_t){ 100, 100, 100 }; - } - assert_true(mapAreaCanUnload(&area)); - - // One chunk overlapping the area's bounds blocks unload. - MAP.chunks[0].position = (chunkpos_t){ 0, 0, 0 }; - assert_false(mapAreaCanUnload(&area)); -} - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - cmocka_unit_test(test_mapAreaInit), - cmocka_unit_test(test_mapAreaIsInside), - cmocka_unit_test(test_mapAreaShouldNotify), - cmocka_unit_test(test_mapAreaAddAndRemove), - cmocka_unit_test(test_mapAreaCheckEntityTriggersEnterStepExit), - cmocka_unit_test(test_mapAreaCheckEntityRespectsTriggerMask), - cmocka_unit_test(test_mapAreaCanUnload), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/rpg/test_rpg.c b/test/rpg/test_rpg.c deleted file mode 100644 index 7a5e36d7..00000000 --- a/test/rpg/test_rpg.c +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "dusktest.h" - -int main(int argc, char** argv) { - const struct CMUnitTest tests[] = { - // Add RPG tests here in the future - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} \ No newline at end of file diff --git a/test/save/savetestfixture.c b/test/save/savetestfixture.c index eb1aee01..8f14bfe8 100644 --- a/test/save/savetestfixture.c +++ b/test/save/savetestfixture.c @@ -10,9 +10,6 @@ #include "savetestfixture.h" #include "assert/assert.h" -#include "asset/asset.h" -#include "asset/loader/assetloader.h" -#include "asset/loader/assetentry.h" #include "util/memory.h" #include "util/string.h" #include @@ -25,51 +22,6 @@ static char_t SAVE_TEST_FIXTURE_HOME[PATH_MAX]; static char_t SAVE_TEST_FIXTURE_REAL_HOME[PATH_MAX]; static bool_t SAVE_TEST_FIXTURE_HAD_REAL_HOME; -static const char_t *SAVE_TEST_FIXTURE_PARTY_JSON = - "{\"members\":{\"hero\":{" - "\"attack\":10,\"defense\":5,\"magic\":0,\"speed\":10,\"luck\":0," - "\"healthMax\":30,\"mpMax\":10" - "}}}"; - -static yyjson_doc *SAVE_TEST_FIXTURE_PARTY_DOC = NULL; - -errorret_t saveTestFixturePartyJsonLoadSyncStub(assetloading_t *loading) { - loading->entry->data.json = SAVE_TEST_FIXTURE_PARTY_DOC; - loading->entry->state = ASSET_ENTRY_STATE_LOADED; - errorOk(); -} - -errorret_t saveTestFixturePartyJsonDisposeStub(assetentry_t *entry) { - errorOk(); -} - -void saveTestFixtureStubPartyAsset(void) { - memoryZero(&ASSET, sizeof(ASSET)); - for(size_t i = 0; i < ASSET_LOADING_COUNT_MAX; i++) { - threadMutexInit(&ASSET.loading[i].mutex); - } - - if(SAVE_TEST_FIXTURE_PARTY_DOC == NULL) { - SAVE_TEST_FIXTURE_PARTY_DOC = yyjson_read( - SAVE_TEST_FIXTURE_PARTY_JSON, strlen(SAVE_TEST_FIXTURE_PARTY_JSON), 0 - ); - assertNotNull( - SAVE_TEST_FIXTURE_PARTY_DOC, "Failed to parse party JSON fixture" - ); - } - - ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_JSON].loadSync = - saveTestFixturePartyJsonLoadSyncStub; - ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_JSON].dispose = - saveTestFixturePartyJsonDisposeStub; -} - -void saveTestFixtureUnstubPartyAsset(void) { - if(SAVE_TEST_FIXTURE_PARTY_DOC == NULL) return; - yyjson_doc_free(SAVE_TEST_FIXTURE_PARTY_DOC); - SAVE_TEST_FIXTURE_PARTY_DOC = NULL; -} - static int_t saveTestFixtureRemoveEntry( const char *path, const struct stat *statBuffer, @@ -97,8 +49,6 @@ int saveTestFixtureSetup(void **state) { assertTrue(setenv("HOME", SAVE_TEST_FIXTURE_HOME, 1) == 0, "setenv failed"); - saveTestFixtureStubPartyAsset(); - errorret_t ret = saveInit(); assertTrue(errorIsOk(ret), "saveInit failed in test fixture"); @@ -106,7 +56,6 @@ int saveTestFixtureSetup(void **state) { } int saveTestFixtureTeardown(void **state) { - saveTestFixtureUnstubPartyAsset(); nftw( SAVE_TEST_FIXTURE_HOME, saveTestFixtureRemoveEntry, 16, diff --git a/test/save/savetestfixture.h b/test/save/savetestfixture.h index 2ac2bde8..59f65fff 100644 --- a/test/save/savetestfixture.h +++ b/test/save/savetestfixture.h @@ -36,20 +36,3 @@ int saveTestFixtureTeardown(void **state); */ void saveTestFixtureBlockSaveDirectory(void); -/** - * Stubs the JSON asset loader's sync callback to hand back a small - * in-memory party.jsonc-shaped doc instead of doing a real file/zip read - - * saveInit()/saveSlotInit() seed the party roster via partyInit(), which - * loads party.jsonc through the real asset system, and there's no real - * asset bundle available under the test harness. No thread, no real I/O. - * Resets ASSET to a fresh, otherwise-empty state each call, so it's safe - * to call once per test. Called automatically by saveTestFixtureSetup. - */ -void saveTestFixtureStubPartyAsset(void); - -/** - * Frees the in-memory doc created by saveTestFixtureStubPartyAsset. Safe - * to call even if that function was never called. Called automatically - * by saveTestFixtureTeardown. - */ -void saveTestFixtureUnstubPartyAsset(void); diff --git a/test/save/test_saveslot.c b/test/save/test_saveslot.c index fab7b8a0..44fdd67d 100644 --- a/test/save/test_saveslot.c +++ b/test/save/test_saveslot.c @@ -15,19 +15,6 @@ #include #include -// saveSlotInit() seeds the party roster via partyInit(), which loads -// party.jsonc through the real asset system - stub it out (see -// savetestfixture.h) rather than needing a real asset bundle here. -static int assetGroupSetup(void **state) { - saveTestFixtureStubPartyAsset(); - return 0; -} - -static int assetGroupTeardown(void **state) { - saveTestFixtureUnstubPartyAsset(); - return 0; -} - // ============================================================ // Helpers - wrap the writeInit/readInit macros (which can only be used // inside a function returning errorret_t) so tests can call them plainly. @@ -156,12 +143,6 @@ static void test_saveSlotReadJSON_roundTrip(void **state) { SAVE.slot.cachedData.name, "Hero", sizeof(SAVE.slot.cachedData.name) ); SAVE.slot.cachedData.playerLevel = 42; - // An in-use slot (non-empty name) requires a non-empty map name too - - // see saveSlotNewGame/requireStrMin("mapName", 1) in saveSlotVer1ReadJSON. - stringCopy( - SAVE.slot.cachedData.mapName, "overworld", - sizeof(SAVE.slot.cachedData.mapName) - ); char_t *json; size_t len; @@ -178,7 +159,6 @@ static void test_saveSlotReadJSON_roundTrip(void **state) { assert_true(stringEquals(read.cachedData.name, "Hero")); assert_int_equal(read.cachedData.playerLevel, 42); - assert_true(stringEquals(read.cachedData.mapName, "overworld")); assert_true(read.cachedData.time.time == writtenTime.time); // version/dataType are struct-only bookkeeping, never serialized - the @@ -199,33 +179,18 @@ static void test_saveSlotReadJSON_blankSlot_shortCircuits(void **state) { // No "name" key (or an empty one) means a blank/never-used slot (see // saveSlotInUse) - the reader returns right after reading that, without - // needing or validating time/mapName/partyMembers/partyOrder at all, so + // needing or validating time at all, so // this succeeds despite omitting fields that would otherwise be required. errorret_t ret = slotFromJSON(&slot, "{\"version\":1}"); assert_true(errorIsOk(ret)); assert_int_equal(slot.cachedData.name[0], '\0'); - assert_int_equal(slot.cachedData.mapName[0], '\0'); // Not readInt32's own default of 1 - the early return happens before // playerLevel is ever read, leaving the zero-init saveSlotCurrentReadJSON // seeds ver1 with (see the memoryZero in saveSlotCurrentReadJSON). assert_int_equal(slot.cachedData.playerLevel, 0); } -static void test_saveSlotReadJSON_inUseMissingMapName_errors(void **state) { - saveslot_t slot; - - // "name" is present (in use) but "mapName" is missing - a real save - // (via saveSlotNewGame) always has both, so this is corrupt. - errorret_t ret = slotFromJSON( - &slot, - "{\"version\":1,\"name\":\"Hero\"," - "\"time\":{\"time\":1.0,\"timeZone\":0.0,\"offsetTime\":0.0}}" - ); - assert_true(errorIsNotOk(ret)); - errorCatch(ret); -} - static void test_saveSlotReadJSON_missingTimeKey_errors(void **state) { saveslot_t slot; @@ -302,7 +267,6 @@ int main(void) { cmocka_unit_test(test_saveSlotReadJSON_nullAsserts), cmocka_unit_test(test_saveSlotReadJSON_roundTrip), cmocka_unit_test(test_saveSlotReadJSON_blankSlot_shortCircuits), - cmocka_unit_test(test_saveSlotReadJSON_inUseMissingMapName_errors), cmocka_unit_test(test_saveSlotReadJSON_missingTimeKey_errors), cmocka_unit_test(test_saveSlotReadJSON_nameTooLong_errors), cmocka_unit_test(test_saveSlotReadJSON_nonObjectRoot_errors), @@ -310,5 +274,5 @@ int main(void) { cmocka_unit_test(test_saveSlotReadJSON_versionMismatch_errors), }; - return cmocka_run_group_tests(tests, assetGroupSetup, assetGroupTeardown); + return cmocka_run_group_tests(tests, NULL, NULL); }