diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 899f565e..2bcef630 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ # https://opensource.org/licenses/MIT add_subdirectory(dusk) +add_subdirectory(duskrpg) if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli") add_subdirectory(dusklinux) diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index ce93e862..ec8dc97c 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -62,6 +62,7 @@ add_subdirectory(entity) add_subdirectory(log) add_subdirectory(engine) add_subdirectory(error) +add_subdirectory(game) add_subdirectory(input) add_subdirectory(locale) add_subdirectory(scene) diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c index 1145b757..85f3f10f 100644 --- a/src/dusk/console/console.c +++ b/src/dusk/console/console.c @@ -56,7 +56,7 @@ void consoleUpdate(void) { if(TIME.dynamicUpdate) return; #endif - if(inputPressed(INPUT_ACTION_CONSOLE)) { + if(inputPressed(INPUT_BIND_CONSOLE)) { CONSOLE.visible = !CONSOLE.visible; } } diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 7450852a..fa7d47eb 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -16,6 +16,7 @@ #include "ui/ui.h" #include "assert/assert.h" #include "network/network.h" +#include "game/game.h" #include "system/system.h" #include "console/console.h" #include "save/save.h" @@ -45,6 +46,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(uiInit()); errorChain(networkInit()); errorChain(sceneInit()); + errorChain(gameInit()); consolePrint("Engine initialized"); @@ -69,6 +71,7 @@ errorret_t engineUpdate(void) { inputUpdate(); consoleUpdate(); + errorChain(gameUpdate()); errorChain(sceneUpdate()); errorChain(assetUpdate()); errorChain(uiUpdate()); @@ -78,7 +81,7 @@ errorret_t engineUpdate(void) { errorChain(displayUpdate()); if( dialogType == SYSTEM_DIALOG_TYPE_NONE && - inputPressed(INPUT_ACTION_RAGEQUIT) + inputPressed(INPUT_BIND_RAGEQUIT) ) { ENGINE.running = false; } @@ -90,6 +93,7 @@ void engineExit(void) { } errorret_t engineDispose(void) { + errorChain(gameDispose()); errorChain(sceneDispose()); errorChain(networkDispose()); localeManagerDispose(); diff --git a/src/dusk/game/CMakeLists.txt b/src/dusk/game/CMakeLists.txt new file mode 100644 index 00000000..38a23e97 --- /dev/null +++ b/src/dusk/game/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Header-only: game.h's implementation lives in src/duskrpg/game/game.c diff --git a/src/dusk/game/game.h b/src/dusk/game/game.h new file mode 100644 index 00000000..1da43eae --- /dev/null +++ b/src/dusk/game/game.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +/** + * Initializes the game. Called once, after the engine and all of its + * subsystems have finished initializing. + * + * @return An error code indicating success or failure. + */ +errorret_t gameInit(void); + +/** + * Updates the game. Called once per frame. + * + * @return An error code indicating success or failure. + */ +errorret_t gameUpdate(void); + +/** + * Disposes of the game. Called once, before the engine disposes of its + * own subsystems. + * + * @return An error code indicating success or failure. + */ +errorret_t gameDispose(void); diff --git a/src/dusk/input/CMakeLists.txt b/src/dusk/input/CMakeLists.txt index 6cf2100f..d4e4e50f 100644 --- a/src/dusk/input/CMakeLists.txt +++ b/src/dusk/input/CMakeLists.txt @@ -9,13 +9,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} input.c inputbutton.c inputaction.c -) - -# Input Action Definitions -dusk_run_python( - dusk_input_csv_defs - tools.input - --csv ${CMAKE_CURRENT_SOURCE_DIR}/input.csv - --output ${DUSK_GENERATED_HEADERS_DIR}/input/inputactiondefs.h -) -add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_input_csv_defs) \ No newline at end of file +) \ No newline at end of file diff --git a/src/dusk/input/input.c b/src/dusk/input/input.c index d019f4fc..ee049487 100644 --- a/src/dusk/input/input.c +++ b/src/dusk/input/input.c @@ -18,8 +18,8 @@ errorret_t inputInit(void) { memoryZero(&INPUT, sizeof(input_t)); INPUT.deadzone = INPUT_DEADZONE_DEFAULT; - for(uint8_t i = 0; i < INPUT_ACTION_COUNT; i++) { - INPUT.actions[i].action = (inputaction_t)i; + for(uint8_t i = 0; i < INPUT_BIND_COUNT; i++) { + INPUT.actions[i].action = (inputbind_t)i; INPUT.actions[i].lastValue = 0.0f; INPUT.actions[i].currentValue = 0.0f; } @@ -52,7 +52,7 @@ void inputUpdate(void) { #endif action++; - } while(action < &INPUT.actions[INPUT_ACTION_COUNT]); + } while(action < &INPUT.actions[INPUT_BIND_COUNT]); // For each button... inputbuttondata_t *cur = &INPUT_BUTTON_DATA[0]; @@ -85,62 +85,62 @@ void inputUpdate(void) { } } -float_t inputGetCurrentValue(const inputaction_t action) { +float_t inputGetCurrentValue(const inputbind_t action) { #ifdef DUSK_TIME_DYNAMIC if(TIME.dynamicUpdate) return inputGetCurrentValueDynamic(action); #endif - assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); + assertTrue(action < INPUT_BIND_COUNT, "Input action out of bounds"); return INPUT.actions[action].currentValue; } -float_t inputGetLastValue(const inputaction_t action) { +float_t inputGetLastValue(const inputbind_t action) { #ifdef DUSK_TIME_DYNAMIC if(TIME.dynamicUpdate) return inputGetLastValueDynamic(action); #endif - assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); + assertTrue(action < INPUT_BIND_COUNT, "Input action out of bounds"); return INPUT.actions[action].lastValue; } #ifdef DUSK_TIME_DYNAMIC - float_t inputGetCurrentValueDynamic(const inputaction_t action) { - assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); + float_t inputGetCurrentValueDynamic(const inputbind_t action) { + assertTrue(action < INPUT_BIND_COUNT, "Input action out of bounds"); return INPUT.actions[action].currentDynamicValue; } - float_t inputGetLastValueDynamic(const inputaction_t action) { - assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds"); + float_t inputGetLastValueDynamic(const inputbind_t action) { + assertTrue(action < INPUT_BIND_COUNT, "Input action out of bounds"); return INPUT.actions[action].lastDynamicValue; } - bool_t inputIsDownDynamic(const inputaction_t action) { + bool_t inputIsDownDynamic(const inputbind_t action) { return inputGetCurrentValueDynamic(action) > 0.0f; } - bool_t inputWasDownDynamic(const inputaction_t action) { + bool_t inputWasDownDynamic(const inputbind_t action) { return inputGetLastValueDynamic(action) > 0.0f; } - bool_t inputPressedDynamic(const inputaction_t action) { + bool_t inputPressedDynamic(const inputbind_t action) { return inputIsDownDynamic(action) && !inputWasDownDynamic(action); } - bool_t inputReleasedDynamic(const inputaction_t action) { + bool_t inputReleasedDynamic(const inputbind_t action) { return !inputIsDownDynamic(action) && inputWasDownDynamic(action); } float_t inputAxisDynamic( - const inputaction_t neg, - const inputaction_t pos + const inputbind_t neg, + const inputbind_t pos ) { return inputGetCurrentValueDynamic(pos) - inputGetCurrentValueDynamic(neg); } void inputAxis2DDynamic( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, + const inputbind_t negX, const inputbind_t posX, + const inputbind_t negY, const inputbind_t posY, vec2 result ) { assertNotNull(result, "Result vector cannot be null"); @@ -149,8 +149,8 @@ float_t inputGetLastValue(const inputaction_t action) { } void inputAngle2DDynamic( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, + const inputbind_t negX, const inputbind_t posX, + const inputbind_t negY, const inputbind_t posY, vec2 result ) { assertNotNull(result, "Result vector cannot be null"); @@ -171,32 +171,32 @@ float_t inputGetLastValue(const inputaction_t action) { } #endif -bool_t inputIsDown(const inputaction_t action) { +bool_t inputIsDown(const inputbind_t action) { return inputGetCurrentValue(action) > 0.0f; } -bool_t inputWasDown(const inputaction_t action) { +bool_t inputWasDown(const inputbind_t action) { return inputGetLastValue(action) > 0.0f; } -bool_t inputPressed(const inputaction_t action) { +bool_t inputPressed(const inputbind_t action) { return inputIsDown(action) && !inputWasDown(action); } -bool_t inputReleased(const inputaction_t action) { +bool_t inputReleased(const inputbind_t action) { return !inputIsDown(action) && inputWasDown(action); } -float_t inputAxis(const inputaction_t neg, const inputaction_t pos) { - assertTrue(neg < INPUT_ACTION_COUNT, "Negative input action out of bounds"); - assertTrue(pos < INPUT_ACTION_COUNT, "Positive input action out of bounds"); +float_t inputAxis(const inputbind_t neg, const inputbind_t pos) { + assertTrue(neg < INPUT_BIND_COUNT, "Negative input action out of bounds"); + assertTrue(pos < INPUT_BIND_COUNT, "Positive input action out of bounds"); return inputGetCurrentValue(pos) - inputGetCurrentValue(neg); } void inputAxis2D( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, + const inputbind_t negX, const inputbind_t posX, + const inputbind_t negY, const inputbind_t posY, vec2 result ) { assertNotNull(result, "Result vector cannot be null"); @@ -205,8 +205,8 @@ void inputAxis2D( } void inputAngle2D( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, + const inputbind_t negX, const inputbind_t posX, + const inputbind_t negY, const inputbind_t posY, vec2 result ) { assertNotNull(result, "Result vector cannot be null"); @@ -226,12 +226,12 @@ void inputAngle2D( result[1] = y; } -void inputBind(const inputbutton_t button, const inputaction_t act) { +void inputBind(const inputbutton_t button, const inputbind_t act) { assertTrue( - act < INPUT_ACTION_COUNT, + act < INPUT_BIND_COUNT, "Invalid input action" ); - assertTrue(act != INPUT_ACTION_NULL, "Cannot bind to NULL action"); + assertTrue(act != INPUT_BIND_NULL, "Cannot bind to NULL action"); // Get the button data for this button. inputbuttondata_t *data = inputButtonGetData(button); diff --git a/src/dusk/input/input.csv b/src/dusk/input/input.csv deleted file mode 100644 index 2fa6a46e..00000000 --- a/src/dusk/input/input.csv +++ /dev/null @@ -1,12 +0,0 @@ -id, -UP, -DOWN, -LEFT, -RIGHT, -ACCEPT, -CANCEL, -PAUSE, -RAGEQUIT, -CONSOLE, -POINTERX, -POINTERY, \ No newline at end of file diff --git a/src/dusk/input/input.h b/src/dusk/input/input.h index 06093eef..25dc9038 100644 --- a/src/dusk/input/input.h +++ b/src/dusk/input/input.h @@ -15,7 +15,7 @@ #define INPUT_DEADZONE_DEFAULT 0.1f typedef struct { - inputactiondata_t actions[INPUT_ACTION_COUNT]; + inputactiondata_t actions[INPUT_BIND_COUNT]; inputplatform_t platform; @@ -43,7 +43,7 @@ void inputUpdate(void); * @param action The input action to get the value for. * @return The current value of the action (0.0f to 1.0f). */ -float_t inputGetCurrentValue(const inputaction_t action); +float_t inputGetCurrentValue(const inputbind_t action); /** * Gets the last value of a specific input action. @@ -51,7 +51,7 @@ float_t inputGetCurrentValue(const inputaction_t action); * @param action The input action to get the value for. * @return The last value of the action (0.0f to 1.0f). */ -float_t inputGetLastValue(const inputaction_t action); +float_t inputGetLastValue(const inputbind_t action); #ifdef DUSK_TIME_DYNAMIC /** @@ -60,7 +60,7 @@ float_t inputGetLastValue(const inputaction_t action); * @param action The input action to get the value for. * @return The current value of the action (0.0f to 1.0f). */ - float_t inputGetCurrentValueDynamic(const inputaction_t action); + float_t inputGetCurrentValueDynamic(const inputbind_t action); /** * Gets the last value of a specific input action (dynamic timestep). @@ -68,7 +68,7 @@ float_t inputGetLastValue(const inputaction_t action); * @param action The input action to get the value for. * @return The last value of the action (0.0f to 1.0f). */ - float_t inputGetLastValueDynamic(const inputaction_t action); + float_t inputGetLastValueDynamic(const inputbind_t action); /** * Checks if an action is currently down on the dynamic timestep. @@ -76,7 +76,7 @@ float_t inputGetLastValue(const inputaction_t action); * @param action The input action to check. * @return true if the action is currently down. */ - bool_t inputIsDownDynamic(const inputaction_t action); + bool_t inputIsDownDynamic(const inputbind_t action); /** * Checks if an action was down on the previous dynamic frame. @@ -84,7 +84,7 @@ float_t inputGetLastValue(const inputaction_t action); * @param action The input action to check. * @return true if the action was down last dynamic frame. */ - bool_t inputWasDownDynamic(const inputaction_t action); + bool_t inputWasDownDynamic(const inputbind_t action); /** * Checks if an action was pressed this dynamic frame (down now, not before). @@ -92,7 +92,7 @@ float_t inputGetLastValue(const inputaction_t action); * @param action The input action to check. * @return true if the action was just pressed this dynamic frame. */ - bool_t inputPressedDynamic(const inputaction_t action); + bool_t inputPressedDynamic(const inputbind_t action); /** * Checks if an action was released this dynamic frame (up now, down before). @@ -100,7 +100,7 @@ float_t inputGetLastValue(const inputaction_t action); * @param action The input action to check. * @return true if the action was just released this dynamic frame. */ - bool_t inputReleasedDynamic(const inputaction_t action); + bool_t inputReleasedDynamic(const inputbind_t action); /** * Gets the value of an input axis on the dynamic timestep. @@ -110,8 +110,8 @@ float_t inputGetLastValue(const inputaction_t action); * @return The current axis value (-1.0f to 1.0f). */ float_t inputAxisDynamic( - const inputaction_t neg, - const inputaction_t pos + const inputbind_t neg, + const inputbind_t pos ); /** @@ -124,8 +124,8 @@ float_t inputGetLastValue(const inputaction_t action); * @param result vec2 to store the result (-1.0f to 1.0f per axis). */ void inputAxis2DDynamic( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, + const inputbind_t negX, const inputbind_t posX, + const inputbind_t negY, const inputbind_t posY, vec2 result ); @@ -139,8 +139,8 @@ float_t inputGetLastValue(const inputaction_t action); * @param result vec2 to store the result. */ void inputAngle2DDynamic( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, + const inputbind_t negX, const inputbind_t posX, + const inputbind_t negY, const inputbind_t posY, vec2 result ); #endif @@ -151,7 +151,7 @@ float_t inputGetLastValue(const inputaction_t action); * @param action The input action to check. * @return true if the action is currently pressed, false otherwise. */ -bool_t inputIsDown(const inputaction_t action); +bool_t inputIsDown(const inputbind_t action); /** * Checks if a specific input action was pressed in the last update. @@ -159,7 +159,7 @@ bool_t inputIsDown(const inputaction_t action); * @param action The input action to check. * @return true if the action was pressed in the last update, false otherwise. */ -bool_t inputWasDown(const inputaction_t action); +bool_t inputWasDown(const inputbind_t action); /** * Checks if a specific input action was down this frame but not in the the @@ -168,7 +168,7 @@ bool_t inputWasDown(const inputaction_t action); * @param action The input action to check. * @return true if the action is currently pressed, false otherwise. */ -bool_t inputPressed(const inputaction_t action); +bool_t inputPressed(const inputbind_t action); /** * Checks if a specific input action was released this frame. @@ -176,7 +176,7 @@ bool_t inputPressed(const inputaction_t action); * @param action The input action to check. * @return true if the action was released this frame, false otherwise. */ -bool_t inputReleased(const inputaction_t action); +bool_t inputReleased(const inputbind_t action); /** * Gets the value of an input axis, defined by two actions (negative and @@ -186,7 +186,7 @@ bool_t inputReleased(const inputaction_t action); * @param pos The action representing the positive direction of the axis. * @return The current value of the axis (-1.0f to 1.0f). */ -float_t inputAxis(const inputaction_t neg, const inputaction_t pos); +float_t inputAxis(const inputbind_t neg, const inputbind_t pos); /** * Gets the values of a 2D input axis, defined by two pairs of actions (negative @@ -199,8 +199,8 @@ float_t inputAxis(const inputaction_t neg, const inputaction_t pos); * @param result A vec2 to store the resulting axis values (-1.0f to 1.0f). */ void inputAxis2D( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, + const inputbind_t negX, const inputbind_t posX, + const inputbind_t negY, const inputbind_t posY, vec2 result ); @@ -215,8 +215,8 @@ void inputAxis2D( * @param result A vec2 to store the resulting axis values (-1.0f to 1.0f). */ void inputAngle2D( - const inputaction_t negX, const inputaction_t posX, - const inputaction_t negY, const inputaction_t posY, + const inputbind_t negX, const inputbind_t posX, + const inputbind_t negY, const inputbind_t posY, vec2 result ); @@ -226,7 +226,7 @@ void inputAngle2D( * @param button The input button to bind. * @param action The input action to bind the button to. */ -void inputBind(const inputbutton_t button, const inputaction_t act); +void inputBind(const inputbutton_t button, const inputbind_t act); /** * Applies deadzone formula to a raw input value. This will; diff --git a/src/dusk/input/inputaction.c b/src/dusk/input/inputaction.c index d2a34697..8e9b9eef 100644 --- a/src/dusk/input/inputaction.c +++ b/src/dusk/input/inputaction.c @@ -9,14 +9,14 @@ #include "assert/assert.h" #include "util/string.h" -inputaction_t inputActionGetByName(const char_t *name) { +inputbind_t inputActionGetByName(const char_t *name) { assertNotNull(name, "name must not be NULL"); - - for(inputaction_t i = 0; i < INPUT_ACTION_COUNT; i++) { - if(INPUT_ACTION_IDS[i] == NULL) continue; - if(stringCompareInsensitive(INPUT_ACTION_IDS[i], name) != 0) continue; + + for(inputbind_t i = 0; i < INPUT_BIND_COUNT; i++) { + if(INPUT_BIND_IDS[i] == NULL) continue; + if(stringCompareInsensitive(INPUT_BIND_IDS[i], name) != 0) continue; return i; } - - return INPUT_ACTION_COUNT; + + return INPUT_BIND_COUNT; } \ No newline at end of file diff --git a/src/dusk/input/inputaction.h b/src/dusk/input/inputaction.h index 5398465f..ed64bf35 100644 --- a/src/dusk/input/inputaction.h +++ b/src/dusk/input/inputaction.h @@ -7,10 +7,10 @@ #pragma once #include "time/time.h" -#include "input/inputactiondefs.h" +#include "input/inputbind.h" typedef struct { - inputaction_t action; + inputbind_t action; float_t lastValue; float_t currentValue; @@ -24,6 +24,6 @@ typedef struct { * Gets an input action by its name. * * @param name The name of the input action. - * @return The input action, or INPUT_ACTION_COUNT if not found. + * @return The input action, or INPUT_BIND_COUNT if not found. */ -inputaction_t inputActionGetByName(const char_t *name); \ No newline at end of file +inputbind_t inputActionGetByName(const char_t *name); \ No newline at end of file diff --git a/src/dusk/input/inputbutton.h b/src/dusk/input/inputbutton.h index 10199c40..a9a37ee5 100644 --- a/src/dusk/input/inputbutton.h +++ b/src/dusk/input/inputbutton.h @@ -6,7 +6,7 @@ */ #pragma once -#include "inputaction.h" +#include "input/inputaction.h" #include "input/inputplatform.h" #ifndef inputButtonGetValuePlatform @@ -77,7 +77,7 @@ typedef struct { inputbutton_t button; float_t curVal; float_t lastVal; - inputaction_t action; + inputbind_t action; } inputbuttondata_t; extern inputbuttondata_t INPUT_BUTTON_DATA[]; diff --git a/src/dusk/ui/focus/uifocus.c b/src/dusk/ui/focus/uifocus.c index 8ceb4b5a..5a7dc7b7 100644 --- a/src/dusk/ui/focus/uifocus.c +++ b/src/dusk/ui/focus/uifocus.c @@ -12,11 +12,11 @@ #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 }, + { INPUT_BIND_UP, UI_FOCUS_DIRECTION_UP, 0, -1 }, + { INPUT_BIND_DOWN, UI_FOCUS_DIRECTION_DOWN, 0, 1 }, + { INPUT_BIND_LEFT, UI_FOCUS_DIRECTION_LEFT, -1, 0 }, + { INPUT_BIND_RIGHT, UI_FOCUS_DIRECTION_RIGHT, 1, 0 }, + { INPUT_BIND_NULL, UI_FOCUS_DIRECTION_NONE, 0, 0 }, }; uifocus_t UI_FOCUS; @@ -108,7 +108,7 @@ void uiFocusMoveDirection( ) { for( const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - m->action != INPUT_ACTION_NULL; + m->action != INPUT_BIND_NULL; m++ ) { if(m->direction != dir) continue; @@ -142,19 +142,19 @@ void uiFocusUpdate(void) { uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1]; - if(inputPressed(INPUT_ACTION_ACCEPT)) { + if(inputPressed(INPUT_BIND_ACCEPT)) { if(item->selected != NULL) item->selected(item); return; } - if(inputPressed(INPUT_ACTION_CANCEL)) { + if(inputPressed(INPUT_BIND_CANCEL)) { uiFocusPop(); return; } for( const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - m->action != INPUT_ACTION_NULL; + m->action != INPUT_BIND_NULL; m++ ) { if(!inputPressed(m->action)) continue; @@ -167,7 +167,7 @@ void uiFocusUpdate(void) { bool_t held = false; for( const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP; - m->action != INPUT_ACTION_NULL; + m->action != INPUT_BIND_NULL; m++ ) { if(m->direction != UI_FOCUS.direction) continue; diff --git a/src/dusk/ui/focus/uifocus.h b/src/dusk/ui/focus/uifocus.h index d68de640..1ec6f7ea 100644 --- a/src/dusk/ui/focus/uifocus.h +++ b/src/dusk/ui/focus/uifocus.h @@ -23,7 +23,7 @@ #define UI_FOCUS_HOLD_REPEAT 0.1f typedef struct { - inputaction_t action; + inputbind_t action; uifocusdirection_t direction; int8_t dx; int8_t dy; @@ -31,7 +31,7 @@ typedef struct { /** * Mapping of input actions to focus directions, terminated by an - * entry with action == INPUT_ACTION_NULL. + * entry with action == INPUT_BIND_NULL. */ extern const uifocusdirmap_t UI_FOCUS_DIR_MAP[]; diff --git a/src/duskdolphin/input/inputdolphin.c b/src/duskdolphin/input/inputdolphin.c index c9fb4869..37c06dce 100644 --- a/src/duskdolphin/input/inputdolphin.c +++ b/src/duskdolphin/input/inputdolphin.c @@ -6,6 +6,7 @@ */ #include "input/input.h" +#include "input/inputbindmap.h" #include "assert/assert.h" #include "log/log.h" #include "util/string.h" @@ -98,48 +99,17 @@ inputbuttondata_t INPUT_BUTTON_DATA[] = { errorret_t inputInitDolphin(void) { PAD_Init(); - #define X(buttonName, buttonAction) \ - inputBind(inputButtonGetByName(buttonName), buttonAction); - - #if defined(DUSK_GAMECUBE) - X("up", INPUT_ACTION_UP); - X("down", INPUT_ACTION_DOWN); - X("left", INPUT_ACTION_LEFT); - X("right", INPUT_ACTION_RIGHT); - X("lstick_up", INPUT_ACTION_UP); - X("lstick_down", INPUT_ACTION_DOWN); - X("lstick_left", INPUT_ACTION_LEFT); - X("lstick_right", INPUT_ACTION_RIGHT); - - X("a", INPUT_ACTION_ACCEPT); - X("b", INPUT_ACTION_CANCEL); - X("z", INPUT_ACTION_CONSOLE); - X("start", INPUT_ACTION_RAGEQUIT); - X("start", INPUT_ACTION_PAUSE); - - #elif defined(DUSK_WII) - X("up", INPUT_ACTION_UP); - X("down", INPUT_ACTION_DOWN); - X("left", INPUT_ACTION_LEFT); - X("right", INPUT_ACTION_RIGHT); - X("lstick_up", INPUT_ACTION_UP); - X("lstick_down", INPUT_ACTION_DOWN); - X("lstick_left", INPUT_ACTION_LEFT); - X("lstick_right", INPUT_ACTION_RIGHT); - - X("a", INPUT_ACTION_ACCEPT); - X("b", INPUT_ACTION_CANCEL); - X("z", INPUT_ACTION_CONSOLE); - X("start", INPUT_ACTION_RAGEQUIT); - X("start", INPUT_ACTION_PAUSE); - - // TODO: Wiimote, USB Keyboard, probably more. - - #else + #if !defined(DUSK_GAMECUBE) && !defined(DUSK_WII) #error "Unknown Dolphin platform?" #endif - #undef X + for( + const inputbindmapentry_t *entry = INPUT_BIND_MAP; + entry->buttonName != NULL; + entry++ + ) { + inputBind(inputButtonGetByName(entry->buttonName), entry->bind); + } errorOk(); } diff --git a/src/dusklinux/input/inputlinux.c b/src/dusklinux/input/inputlinux.c index fb952efa..b7bd6792 100644 --- a/src/dusklinux/input/inputlinux.c +++ b/src/dusklinux/input/inputlinux.c @@ -6,6 +6,7 @@ */ #include "input/input.h" +#include "input/inputbindmap.h" inputbuttondata_t INPUT_BUTTON_DATA[] = { #ifdef DUSK_INPUT_GAMEPAD @@ -499,49 +500,13 @@ inputbuttondata_t INPUT_BUTTON_DATA[] = { }; errorret_t inputInitLinux(void) { - #define X(buttonName, buttonAction) \ - inputBind(inputButtonGetByName(buttonName), buttonAction); - - #ifdef DUSK_INPUT_KEYBOARD - X("w", INPUT_ACTION_UP); - X("s", INPUT_ACTION_DOWN); - X("a", INPUT_ACTION_LEFT); - X("d", INPUT_ACTION_RIGHT); - X("left", INPUT_ACTION_LEFT); - X("right", INPUT_ACTION_RIGHT); - X("up", INPUT_ACTION_UP); - X("down", INPUT_ACTION_DOWN); - X("enter", INPUT_ACTION_ACCEPT); - X("e", INPUT_ACTION_ACCEPT); - X("space", INPUT_ACTION_ACCEPT); - X("tab", INPUT_ACTION_CANCEL); - X("q", INPUT_ACTION_CANCEL); - X("escape", INPUT_ACTION_RAGEQUIT); - X("enter", INPUT_ACTION_PAUSE); - X("`", INPUT_ACTION_CONSOLE); - #endif - - #ifdef DUSK_INPUT_GAMEPAD - X("gamepad_up", INPUT_ACTION_UP); - X("gamepad_down", INPUT_ACTION_DOWN); - X("gamepad_left", INPUT_ACTION_LEFT); - X("gamepad_right", INPUT_ACTION_RIGHT); - X("gamepad_a", INPUT_ACTION_ACCEPT); - X("gamepad_b", INPUT_ACTION_CANCEL); - X("gamepad_back", INPUT_ACTION_RAGEQUIT); - X("gamepad_start", INPUT_ACTION_PAUSE); - X("gamepad_lstick_up", INPUT_ACTION_UP); - X("gamepad_lstick_down", INPUT_ACTION_DOWN); - X("gamepad_lstick_left", INPUT_ACTION_LEFT); - X("gamepad_lstick_right", INPUT_ACTION_RIGHT); - #endif - - #ifdef DUSK_INPUT_POINTER - X("mouse_x", INPUT_ACTION_POINTERX); - X("mouse_y", INPUT_ACTION_POINTERY); - #endif - - #undef X + for( + const inputbindmapentry_t *entry = INPUT_BIND_MAP; + entry->buttonName != NULL; + entry++ + ) { + inputBind(inputButtonGetByName(entry->buttonName), entry->bind); + } errorOk(); } diff --git a/src/duskpsp/input/inputpsp.c b/src/duskpsp/input/inputpsp.c index 6bc7ef62..2cde5a2f 100644 --- a/src/duskpsp/input/inputpsp.c +++ b/src/duskpsp/input/inputpsp.c @@ -6,6 +6,7 @@ */ #include "input/input.h" +#include "input/inputbindmap.h" // #define INPUT_PSP_GAMEPAD_BUTTON_ACCEPT INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM // #define INPUT_PSP_GAMEPAD_BUTTON_CANCEL INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM @@ -73,22 +74,13 @@ inputbuttondata_t INPUT_BUTTON_DATA[] = { }; errorret_t inputInitPSP(void) { - #define X(buttonName, buttonAction) \ - inputBind(inputButtonGetByName(buttonName), buttonAction); - X("up", INPUT_ACTION_UP); - X("down", INPUT_ACTION_DOWN); - X("left", INPUT_ACTION_LEFT); - X("right", INPUT_ACTION_RIGHT); - X("accept", INPUT_ACTION_ACCEPT); - X("cancel", INPUT_ACTION_CANCEL); - X("triangle", INPUT_ACTION_CONSOLE); - X("select", INPUT_ACTION_RAGEQUIT); - X("start", INPUT_ACTION_PAUSE); - X("lstick_up", INPUT_ACTION_UP); - X("lstick_down", INPUT_ACTION_DOWN); - X("lstick_left", INPUT_ACTION_LEFT); - X("lstick_right", INPUT_ACTION_RIGHT); - #undef X + for( + const inputbindmapentry_t *entry = INPUT_BIND_MAP; + entry->buttonName != NULL; + entry++ + ) { + inputBind(inputButtonGetByName(entry->buttonName), entry->bind); + } errorOk(); } diff --git a/src/duskrpg/CMakeLists.txt b/src/duskrpg/CMakeLists.txt new file mode 100644 index 00000000..2d11ff09 --- /dev/null +++ b/src/duskrpg/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Includes +target_include_directories(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +add_subdirectory(game) +add_subdirectory(input) +add_subdirectory(item) diff --git a/src/duskrpg/game/CMakeLists.txt b/src/duskrpg/game/CMakeLists.txt new file mode 100644 index 00000000..43d6e5fa --- /dev/null +++ b/src/duskrpg/game/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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 + game.c +) diff --git a/src/duskrpg/game/game.c b/src/duskrpg/game/game.c new file mode 100644 index 00000000..4ea5c851 --- /dev/null +++ b/src/duskrpg/game/game.c @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "game/game.h" + +errorret_t gameInit(void) { + errorOk(); +} + +errorret_t gameUpdate(void) { + errorOk(); +} + +errorret_t gameDispose(void) { + errorOk(); +} diff --git a/src/duskrpg/input/CMakeLists.txt b/src/duskrpg/input/CMakeLists.txt new file mode 100644 index 00000000..a9a850f5 --- /dev/null +++ b/src/duskrpg/input/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Header-only: inputbind.h has no implementation. diff --git a/src/duskrpg/input/inputbind.h b/src/duskrpg/input/inputbind.h new file mode 100644 index 00000000..f47dc917 --- /dev/null +++ b/src/duskrpg/input/inputbind.h @@ -0,0 +1,41 @@ +/** + * 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 { + INPUT_BIND_NULL, + + INPUT_BIND_UP, + INPUT_BIND_DOWN, + INPUT_BIND_LEFT, + INPUT_BIND_RIGHT, + INPUT_BIND_ACCEPT, + INPUT_BIND_CANCEL, + INPUT_BIND_PAUSE, + INPUT_BIND_RAGEQUIT, + INPUT_BIND_CONSOLE, + INPUT_BIND_POINTERX, + INPUT_BIND_POINTERY, + + INPUT_BIND_COUNT +} inputbind_t; + +static const char_t *INPUT_BIND_IDS[] = { + [INPUT_BIND_UP] = "UP", + [INPUT_BIND_DOWN] = "DOWN", + [INPUT_BIND_LEFT] = "LEFT", + [INPUT_BIND_RIGHT] = "RIGHT", + [INPUT_BIND_ACCEPT] = "ACCEPT", + [INPUT_BIND_CANCEL] = "CANCEL", + [INPUT_BIND_PAUSE] = "PAUSE", + [INPUT_BIND_RAGEQUIT] = "RAGEQUIT", + [INPUT_BIND_CONSOLE] = "CONSOLE", + [INPUT_BIND_POINTERX] = "POINTERX", + [INPUT_BIND_POINTERY] = "POINTERY", +}; diff --git a/src/duskrpg/input/inputbindmap.h b/src/duskrpg/input/inputbindmap.h new file mode 100644 index 00000000..e857b1e4 --- /dev/null +++ b/src/duskrpg/input/inputbindmap.h @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "input/inputbind.h" + +typedef struct { + const char_t *buttonName; + inputbind_t bind; +} inputbindmapentry_t; + +#ifdef DUSK_LINUX + static const inputbindmapentry_t INPUT_BIND_MAP[] = { + #ifdef DUSK_INPUT_KEYBOARD + { .buttonName = "w", .bind = INPUT_BIND_UP }, + { .buttonName = "s", .bind = INPUT_BIND_DOWN }, + { .buttonName = "a", .bind = INPUT_BIND_LEFT }, + { .buttonName = "d", .bind = INPUT_BIND_RIGHT }, + { .buttonName = "left", .bind = INPUT_BIND_LEFT }, + { .buttonName = "right", .bind = INPUT_BIND_RIGHT }, + { .buttonName = "up", .bind = INPUT_BIND_UP }, + { .buttonName = "down", .bind = INPUT_BIND_DOWN }, + { .buttonName = "enter", .bind = INPUT_BIND_ACCEPT }, + { .buttonName = "e", .bind = INPUT_BIND_ACCEPT }, + { .buttonName = "space", .bind = INPUT_BIND_ACCEPT }, + { .buttonName = "tab", .bind = INPUT_BIND_CANCEL }, + { .buttonName = "q", .bind = INPUT_BIND_CANCEL }, + { .buttonName = "escape", .bind = INPUT_BIND_RAGEQUIT }, + { .buttonName = "enter", .bind = INPUT_BIND_PAUSE }, + { .buttonName = "`", .bind = INPUT_BIND_CONSOLE }, + #endif + + #ifdef DUSK_INPUT_GAMEPAD + { .buttonName = "gamepad_up", .bind = INPUT_BIND_UP }, + { .buttonName = "gamepad_down", .bind = INPUT_BIND_DOWN }, + { .buttonName = "gamepad_left", .bind = INPUT_BIND_LEFT }, + { .buttonName = "gamepad_right", .bind = INPUT_BIND_RIGHT }, + { .buttonName = "gamepad_a", .bind = INPUT_BIND_ACCEPT }, + { .buttonName = "gamepad_b", .bind = INPUT_BIND_CANCEL }, + { .buttonName = "gamepad_back", .bind = INPUT_BIND_RAGEQUIT }, + { .buttonName = "gamepad_start", .bind = INPUT_BIND_PAUSE }, + { .buttonName = "gamepad_lstick_up", .bind = INPUT_BIND_UP }, + { .buttonName = "gamepad_lstick_down", .bind = INPUT_BIND_DOWN }, + { .buttonName = "gamepad_lstick_left", .bind = INPUT_BIND_LEFT }, + { .buttonName = "gamepad_lstick_right", .bind = INPUT_BIND_RIGHT }, + #endif + + #ifdef DUSK_INPUT_POINTER + { .buttonName = "mouse_x", .bind = INPUT_BIND_POINTERX }, + { .buttonName = "mouse_y", .bind = INPUT_BIND_POINTERY }, + #endif + + { .buttonName = NULL, .bind = INPUT_BIND_NULL } + }; +#endif + +#ifdef DUSK_PSP + static const inputbindmapentry_t INPUT_BIND_MAP[] = { + { .buttonName = "up", .bind = INPUT_BIND_UP }, + { .buttonName = "down", .bind = INPUT_BIND_DOWN }, + { .buttonName = "left", .bind = INPUT_BIND_LEFT }, + { .buttonName = "right", .bind = INPUT_BIND_RIGHT }, + { .buttonName = "accept", .bind = INPUT_BIND_ACCEPT }, + { .buttonName = "cancel", .bind = INPUT_BIND_CANCEL }, + { .buttonName = "triangle", .bind = INPUT_BIND_CONSOLE }, + { .buttonName = "select", .bind = INPUT_BIND_RAGEQUIT }, + { .buttonName = "start", .bind = INPUT_BIND_PAUSE }, + { .buttonName = "lstick_up", .bind = INPUT_BIND_UP }, + { .buttonName = "lstick_down", .bind = INPUT_BIND_DOWN }, + { .buttonName = "lstick_left", .bind = INPUT_BIND_LEFT }, + { .buttonName = "lstick_right", .bind = INPUT_BIND_RIGHT }, + { .buttonName = NULL, .bind = INPUT_BIND_NULL } + }; +#endif + +// GameCube and Wii share the same pad layout for now. +// TODO: Wiimote, USB Keyboard, probably more. +#if defined(DUSK_GAMECUBE) || defined(DUSK_WII) + static const inputbindmapentry_t INPUT_BIND_MAP[] = { + { .buttonName = "up", .bind = INPUT_BIND_UP }, + { .buttonName = "down", .bind = INPUT_BIND_DOWN }, + { .buttonName = "left", .bind = INPUT_BIND_LEFT }, + { .buttonName = "right", .bind = INPUT_BIND_RIGHT }, + { .buttonName = "lstick_up", .bind = INPUT_BIND_UP }, + { .buttonName = "lstick_down", .bind = INPUT_BIND_DOWN }, + { .buttonName = "lstick_left", .bind = INPUT_BIND_LEFT }, + { .buttonName = "lstick_right", .bind = INPUT_BIND_RIGHT }, + { .buttonName = "a", .bind = INPUT_BIND_ACCEPT }, + { .buttonName = "b", .bind = INPUT_BIND_CANCEL }, + { .buttonName = "z", .bind = INPUT_BIND_CONSOLE }, + { .buttonName = "start", .bind = INPUT_BIND_RAGEQUIT }, + { .buttonName = "start", .bind = INPUT_BIND_PAUSE }, + { .buttonName = NULL, .bind = INPUT_BIND_NULL } + }; +#endif diff --git a/src/duskrpg/item/CMakeLists.txt b/src/duskrpg/item/CMakeLists.txt new file mode 100644 index 00000000..c23c10f1 --- /dev/null +++ b/src/duskrpg/item/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +# itemgive.c/h are not built yet -- they depend on the RPG textbox UI +# (ui/rpg/textbox/uitextboxmain.h), which hasn't been restored. Add +# itemgive.c back here once that's back. +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + item.c + inventory.c + backpack.c +) + +# Item Definitions +dusk_run_python( + dusk_item_json_defs + tools.item + --json ${CMAKE_CURRENT_SOURCE_DIR}/item.json + --output ${DUSK_GENERATED_HEADERS_DIR}/item/itemdef.h +) +add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_item_json_defs) diff --git a/src/duskrpg/item/backpack.c b/src/duskrpg/item/backpack.c new file mode 100644 index 00000000..6743adff --- /dev/null +++ b/src/duskrpg/item/backpack.c @@ -0,0 +1,79 @@ +/** + * 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(uint8_t i = 0; i < ITEM_TYPE_COUNT; i++) { + inventoryInit( + &BACKPACK.inventories[i], + BACKPACK.storage[i], + ITEM_TYPE_COUNT_MAX + ); + } +} + +inventory_t *backpackGetInventory(const itemtype_t type) { + assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null"); + assertTrue(type < ITEM_TYPE_COUNT, "Item type out of range"); + return &BACKPACK.inventories[type]; +} + +void backpackAdd(const itemid_t item, const uint8_t quantity) { + assertTrue(item > ITEM_ID_NULL, "Item ID must not be null"); + assertTrue(item < ITEM_ID_COUNT, "Item ID out of range"); + inventoryAdd(backpackGetInventory(ITEMS[item].type), item, quantity); +} + +void backpackRemove(const itemid_t item) { + assertTrue(item > ITEM_ID_NULL, "Item ID must not be null"); + assertTrue(item < ITEM_ID_COUNT, "Item ID out of range"); + inventoryRemove(backpackGetInventory(ITEMS[item].type), item); +} + +void backpackSet(const itemid_t item, const uint8_t quantity) { + assertTrue(item > ITEM_ID_NULL, "Item ID must not be null"); + assertTrue(item < ITEM_ID_COUNT, "Item ID out of range"); + inventorySet(backpackGetInventory(ITEMS[item].type), item, quantity); +} + +uint8_t backpackGetCount(const itemid_t item) { + assertTrue(item > ITEM_ID_NULL, "Item ID must not be null"); + assertTrue(item < ITEM_ID_COUNT, "Item ID out of range"); + return inventoryGetCount(backpackGetInventory(ITEMS[item].type), item); +} + +bool_t backpackItemExists(const itemid_t item) { + assertTrue(item > ITEM_ID_NULL, "Item ID must not be null"); + assertTrue(item < ITEM_ID_COUNT, "Item ID out of range"); + return inventoryItemExists(backpackGetInventory(ITEMS[item].type), item); +} + +bool_t backpackIsFull(const itemtype_t type) { + assertTrue(type > ITEM_TYPE_NULL, "Item type must not be null"); + assertTrue(type < ITEM_TYPE_COUNT, "Item type out of range"); + return inventoryIsFull(backpackGetInventory(type)); +} + +bool_t backpackItemFull(const itemid_t item) { + assertTrue(item > ITEM_ID_NULL, "Item ID must not be null"); + assertTrue(item < ITEM_ID_COUNT, "Item ID out of range"); + return inventoryItemFull(backpackGetInventory(ITEMS[item].type), 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_TYPE_COUNT, "Item type out of range"); + inventorySort(backpackGetInventory(type), sortBy, reverse); +} diff --git a/src/duskrpg/item/backpack.h b/src/duskrpg/item/backpack.h new file mode 100644 index 00000000..b041381f --- /dev/null +++ b/src/duskrpg/item/backpack.h @@ -0,0 +1,97 @@ +/** + * 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_COUNT][ITEM_TYPE_COUNT_MAX]; + inventory_t inventories[ITEM_TYPE_COUNT]; +} 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 +); diff --git a/src/duskrpg/item/inventory.c b/src/duskrpg/item/inventory.c new file mode 100644 index 00000000..6be8c5d4 --- /dev/null +++ b/src/duskrpg/item/inventory.c @@ -0,0 +1,250 @@ +/** + * 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 = ITEMS[stackA->item].type; + const itemtype_t typeB = ITEMS[stackB->item].type; + 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 = ITEMS[stackA->item].type; + const itemtype_t typeB = ITEMS[stackB->item].type; + 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); +} diff --git a/src/duskrpg/item/inventory.h b/src/duskrpg/item/inventory.h new file mode 100644 index 00000000..bc03c46f --- /dev/null +++ b/src/duskrpg/item/inventory.h @@ -0,0 +1,159 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "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 +); + +/** + * Comparator for sorting inventory stacks by item ID, ascending. + * + * @param a First inventorystack_t to compare. + * @param b Second inventorystack_t to compare. + * @return Comparison result for use with sort(). + */ +int_t inventorySortById(const void *a, const void *b); + +/** + * Comparator for sorting inventory stacks by item ID, descending. + * + * @param a First inventorystack_t to compare. + * @param b Second inventorystack_t to compare. + * @return Comparison result for use with sort(). + */ +int_t inventorySortByIdReverse(const void *a, const void *b); + +/** + * Comparator for sorting inventory stacks by item type, ascending. + * + * @param a First inventorystack_t to compare. + * @param b Second inventorystack_t to compare. + * @return Comparison result for use with sort(). + */ +int_t inventorySortByType(const void *a, const void *b); + +/** + * Comparator for sorting inventory stacks by item type, descending. + * + * @param a First inventorystack_t to compare. + * @param b Second inventorystack_t to compare. + * @return Comparison result for use with sort(). + */ +int_t inventorySortByTypeReverse(const void *a, const void *b); diff --git a/src/duskrpg/item/item.c b/src/duskrpg/item/item.c new file mode 100644 index 00000000..d80a6b67 --- /dev/null +++ b/src/duskrpg/item/item.c @@ -0,0 +1,30 @@ +/** + * 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/loader/locale/assetlocaleloader.h" + +errorret_t itemGetName( + const itemid_t item, + char_t *buffer, + const size_t bufferSize +) { + assertTrue(item > ITEM_ID_NULL, "Item ID must not be null"); + assertTrue(item < ITEM_ID_COUNT, "Item ID out of range"); + + errorChain(assetLocaleGetString( + &LOCALE.entry->data.locale, + ITEMS[item].name, + 0, + buffer, + bufferSize + )); + + errorOk(); +} diff --git a/src/duskrpg/item/item.h b/src/duskrpg/item/item.h new file mode 100644 index 00000000..9aee1178 --- /dev/null +++ b/src/duskrpg/item/item.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" +#include "item/itemdef.h" + +/** + * 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/duskrpg/item/item.json b/src/duskrpg/item/item.json new file mode 100644 index 00000000..35aceb4d --- /dev/null +++ b/src/duskrpg/item/item.json @@ -0,0 +1,5 @@ +[ + { "id": "POTION", "type": "MEDICINE", "weight": 1.0, "name": "potion" }, + { "id": "POTATO", "type": "FOOD", "weight": 0.5, "name": "potato" }, + { "id": "APPLE", "type": "FOOD", "weight": 0.3, "name": "apple" } +] diff --git a/src/duskrpg/item/itemgive.c b/src/duskrpg/item/itemgive.c new file mode 100644 index 00000000..9a695f70 --- /dev/null +++ b/src/duskrpg/item/itemgive.c @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "itemgive.h" +#include "item/backpack.h" +#include "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/duskrpg/item/itemgive.h b/src/duskrpg/item/itemgive.h new file mode 100644 index 00000000..e272dcdc --- /dev/null +++ b/src/duskrpg/item/itemgive.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "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/tools/input.py b/tools/input.py deleted file mode 100644 index d7ad0786..00000000 --- a/tools/input.py +++ /dev/null @@ -1,58 +0,0 @@ -import argparse -import csv -import os - -parser = argparse.ArgumentParser(description="Input CSV to .h defines") -parser.add_argument("--csv", required=True, help="Path to Input CSV file") -parser.add_argument("--output", required=True, help="Path to output .h file") -args = parser.parse_args() - -def id_enum(name): - return "INPUT_ACTION_" + name.upper() - -# Load CSV -input_ids = [] -with open(args.csv, newline="", encoding="utf-8") as f: - reader = csv.DictReader(f) - if "id" not in reader.fieldnames: - raise ValueError("CSV must have an 'id' column") - for row in reader: - input_id = row["id"] - if input_id not in input_ids: - input_ids.append(input_id) - -# Assign enum values -id_values = {input_id: i + 1 for i, input_id in enumerate(input_ids)} - -# Build output -out = [ - "#pragma once", - '#include "dusk.h"', - "", - "typedef enum {", - " INPUT_ACTION_NULL = 0x0,", - "", -] -for input_id in input_ids: - out.append(f" {id_enum(input_id)} = 0x{id_values[input_id]:x},") -out += [ - "", - f" INPUT_ACTION_COUNT = 0x{len(input_ids) + 1:x}", - "} inputaction_t;", - "", - "static const char_t *INPUT_ACTION_IDS[] = {", -] -for input_id in input_ids: - out.append(f" [{id_enum(input_id)}] = \"{input_id}\",") -out += [ - "};", - "", - "static const char_t *INPUT_ACTION_SCRIPT =", -] -for input_id in input_ids: - out.append(f" \"var {id_enum(input_id)} = {id_values[input_id]};\\n\"") -out += [";", ""] - -os.makedirs(os.path.dirname(args.output), exist_ok=True) -with open(args.output, "w", encoding="utf-8") as f: - f.write("\n".join(out))