diff --git a/assets/init.lua b/assets/init.lua index 38c7b72..457140c 100644 --- a/assets/init.lua +++ b/assets/init.lua @@ -1,6 +1,7 @@ module('platform') module('input') module('scene') +module('item') -- Default Input bindings. if PLATFORM == "psp" then @@ -34,8 +35,17 @@ else inputBind("q", "cancel") inputBind("z", "ragequit") - end + end end -sceneSet('map') +print("Backpack:", BACKPACK) +print('Has Potion?', inventoryItemExists(BACKPACK, 1)) +inventoryAdd(BACKPACK, 1, 3) +print('Has Potion?', inventoryItemExists(BACKPACK, 1)) +inventoryRemove(BACKPACK, 1) +print('Has Potion?', inventoryItemExists(BACKPACK, 1)) +print('Potion Count:', inventoryGetCount(BACKPACK, 1)) +print('Is Backpack Full?', inventoryIsFull(BACKPACK)) + +-- sceneSet('map') -- mapLoad('map/testmap/testmap.dmf') \ No newline at end of file diff --git a/cmake/modules/csvtoenum.cmake b/cmake/modules/csvtoenum.cmake new file mode 100644 index 0000000..961f3b9 --- /dev/null +++ b/cmake/modules/csvtoenum.cmake @@ -0,0 +1,66 @@ +# csvtoenum( ) +# Example: +# csvtoenum(myfile.csv outhead.h itemtype_t id ITEM_TYPE_) +# +# Will generate a header with: +# typedef enum { +# ITEM_TYPE_NULL, +# ITEM_TYPE_FOO, +# ... +# ITEM_TYPE_COUNT, +# } itemtype_t; + +function(csvtoenum CSV_FILE OUT_HEADER C_TYPE TAKE_COLUMN PREFIX_COLUMN) + # Read the CSV file + file(READ "${CSV_FILE}" CSV_CONTENTS) + string(REPLACE "\r\n" "\n" CSV_CONTENTS "${CSV_CONTENTS}") + string(REPLACE "\r" "\n" CSV_CONTENTS "${CSV_CONTENTS}") + string(REGEX REPLACE "\n+$" "" CSV_CONTENTS "${CSV_CONTENTS}") + string(REPLACE "\n" ";" CSV_LINES "${CSV_CONTENTS}") + + # Get header row and find column indices + list(GET CSV_LINES 0 HEADER_ROW) + string(REPLACE "," ";" HEADER_LIST "${HEADER_ROW}") + set(COLUMN_INDEX -1) + set(PREFIX_INDEX -1) + set(IDX 0) + foreach(COL ${HEADER_LIST}) + if(COL STREQUAL "${TAKE_COLUMN}") + set(COLUMN_INDEX ${IDX}) + endif() + if(COL STREQUAL "${PREFIX_COLUMN}") + set(PREFIX_INDEX ${IDX}) + endif() + math(EXPR IDX "${IDX} + 1") + endforeach() + if(COLUMN_INDEX EQUAL -1) + message(FATAL_ERROR "csvtoenum: TAKE_COLUMN '${TAKE_COLUMN}' not found in header of ${CSV_FILE}") + endif() + if(PREFIX_INDEX EQUAL -1) + message(FATAL_ERROR "csvtoenum: PREFIX_COLUMN '${PREFIX_COLUMN}' not found in header of ${CSV_FILE}") + endif() + + # Prepare enum entries + set(ENUM_ENTRIES " ${PREFIX_COLUMN}NULL,") + set(ROW_IDX 1) + list(LENGTH CSV_LINES NUM_LINES) + while(ROW_IDX LESS NUM_LINES) + list(GET CSV_LINES ${ROW_IDX} ROW) + string(REPLACE "," ";" ROW_LIST "${ROW}") + list(LENGTH ROW_LIST ROW_LEN) + if(ROW_LEN GREATER COLUMN_INDEX) + list(GET ROW_LIST ${COLUMN_INDEX} ENTRY) + list(GET ROW_LIST ${PREFIX_INDEX} PREFIX) + # Only add if ENTRY is not empty + string(STRIP "${ENTRY}" ENTRY) + if(NOT ENTRY STREQUAL "") + set(ENUM_ENTRIES "${ENUM_ENTRIES}\n ${PREFIX}${ENTRY},") + endif() + endif() + math(EXPR ROW_IDX "${ROW_IDX} + 1") + endwhile() + set(ENUM_ENTRIES "${ENUM_ENTRIES}\n ${PREFIX_COLUMN}COUNT,") + + # Write header file + file(WRITE "${OUT_HEADER}" "/**\n * Auto-generated by csvtoenum.cmake\n * Source: ${CSV_FILE}\n */\n\ntypedef enum {\n${ENUM_ENTRIES}\n} ${C_TYPE};\n") +endfunction() \ No newline at end of file diff --git a/cmake/modules/envtoh.cmake b/cmake/modules/envtoh.cmake index 62c506d..d6b0d86 100644 --- a/cmake/modules/envtoh.cmake +++ b/cmake/modules/envtoh.cmake @@ -1,3 +1,8 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + if(NOT DEFINED ENV_FILE) message(FATAL_ERROR "ENV_FILE is not set") endif() diff --git a/src/asset/asset.c b/src/asset/asset.c index 68f126a..cd72175 100644 --- a/src/asset/asset.c +++ b/src/asset/asset.c @@ -18,11 +18,6 @@ errorret_t assetInit(void) { // Engine may have been provided the launch path if(ENGINE.argc > 0) { - // This first arg is the executable, so on most platforms it is say - // "/path/file" or "C:\Path\file.exe". On PSP this would be something - // like "ms0:/PSP/GAME/DUSK/EBOOT.PBP" or if we are debugging it is - // "host0:/Dusk.prx" - // Get the directory of the executable char_t buffer[FILENAME_MAX]; stringCopy(buffer, ENGINE.argv[0], FILENAME_MAX); @@ -55,11 +50,15 @@ errorret_t assetInit(void) { // Default system path, intended to be overridden by the platform stringCopy(ASSET.systemPath, ".", FILENAME_MAX); - // PSP specific time. + // PSP specific asset loading. #if PSP assertTrue(ENGINE.argc >= 1, "PSP requires launch argument."); - // PSP is given either host0:/Dusk.prx (debugging) OR the PBP file. + // PSP is given either the prx OR the PBP file. + // In the format of "ms0:/PSP/GAME/DUSK/EBOOT.PBP" or "host0:/Dusk.prx" + // IF the file is the PBP file, we are loading directly on the PSP itself. + // IF the file is the .prx then we are debugging and fopen will return + // relative filepaths correctly, e.g. host0:/dusk.dsk will be on host. if( stringEndsWithCaseInsensitive(ENGINE.argv[0], ".pbp") || ASSET_PBP_READ_PBP_FROM_HOST @@ -143,7 +142,6 @@ errorret_t assetInit(void) { } #endif - // Open zip file char_t searchPath[FILENAME_MAX]; const char_t **path = ASSET_SEARCH_PATHS; diff --git a/src/asset/asset.h b/src/asset/asset.h index c482053..b56866f 100644 --- a/src/asset/asset.h +++ b/src/asset/asset.h @@ -56,6 +56,7 @@ typedef struct { char_t systemPath[FILENAME_MAX]; uint8_t assetCount; + // PSP specific information. #if PSP FILE *pbpFile; assetpbp_t pbpHeader; diff --git a/src/engine/engine.c b/src/engine/engine.c index 066a1f2..f7f5732 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -17,6 +17,7 @@ // #include "rpg/rpg.h" #include "script/scriptmanager.h" #include "debug/debug.h" +#include "item/backpack.h" engine_t ENGINE; @@ -38,6 +39,8 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { // errorChain(rpgInit()); errorChain(sceneInit()); + backpackInit(); + // Run the initial script. scriptcontext_t ctx; errorChain(scriptContextInit(&ctx)); diff --git a/src/input/input.c b/src/input/input.c index bd34425..e2bdd7d 100644 --- a/src/input/input.c +++ b/src/input/input.c @@ -24,44 +24,6 @@ void inputInit(void) { } INPUT.deadzone = 0.2f; - - // Setup Default Binds - // #if INPUT_SDL2 == 1 - // #if INPUT_KEYBOARD == 1 - // inputBind(inputButtonGetByName("up"), INPUT_ACTION_UP); - // inputBind(inputButtonGetByName("down"), INPUT_ACTION_DOWN); - // inputBind(inputButtonGetByName("left"), INPUT_ACTION_LEFT); - // inputBind(inputButtonGetByName("right"), INPUT_ACTION_RIGHT); - // inputBind(inputButtonGetByName("w"), INPUT_ACTION_UP); - // inputBind(inputButtonGetByName("s"), INPUT_ACTION_DOWN); - // inputBind(inputButtonGetByName("a"), INPUT_ACTION_LEFT); - // inputBind(inputButtonGetByName("d"), INPUT_ACTION_RIGHT); - // inputBind(inputButtonGetByName("enter"), INPUT_ACTION_ACCEPT); - // inputBind(inputButtonGetByName("escape"), INPUT_ACTION_RAGEQUIT); - // inputBind(inputButtonGetByName("space"), INPUT_ACTION_ACCEPT); - // inputBind(inputButtonGetByName("backspace"), INPUT_ACTION_CANCEL); - // inputBind(inputButtonGetByName("e"), INPUT_ACTION_ACCEPT); - // inputBind(inputButtonGetByName("q"), INPUT_ACTION_CANCEL); - // #endif - - // #if INPUT_GAMEPAD == 1 - // #if PSP - // INPUT.deadzone = 0.2890625f;// Taken from the PSP firmware - - // inputBind(inputButtonGetByName("up"), INPUT_ACTION_UP); - // inputBind(inputButtonGetByName("down"), INPUT_ACTION_DOWN); - // inputBind(inputButtonGetByName("left"), INPUT_ACTION_LEFT); - // inputBind(inputButtonGetByName("right"), INPUT_ACTION_RIGHT); - // inputBind(inputButtonGetByName("circle"), INPUT_ACTION_CANCEL); - // inputBind(inputButtonGetByName("cross"), INPUT_ACTION_ACCEPT); - // inputBind(inputButtonGetByName("lstick_negative_y"), INPUT_ACTION_UP); - // inputBind(inputButtonGetByName("lstick_positive_y"), INPUT_ACTION_DOWN); - // inputBind(inputButtonGetByName("lstick_negative_x"), INPUT_ACTION_LEFT); - // inputBind(inputButtonGetByName("lstick_positive_x"), INPUT_ACTION_RIGHT); - // inputBind(inputButtonGetByName("select"), INPUT_ACTION_RAGEQUIT); - // #endif - // #endif - // #endif } void inputUpdate(void) { diff --git a/src/item/CMakeLists.txt b/src/item/CMakeLists.txt index 3a44978..013eff9 100644 --- a/src/item/CMakeLists.txt +++ b/src/item/CMakeLists.txt @@ -3,9 +3,29 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT +include(cmake/modules/csvtoenum.cmake) + # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC item.c inventory.c -) \ No newline at end of file + backpack.c +) + +# Generate itemtype_t enum from CSV +set(ITEM_CSV "${CMAKE_CURRENT_LIST_DIR}/itemtypes.csv") +set(ITEM_HEADER "${CMAKE_CURRENT_LIST_DIR}/itemtypes2.h") +csvtoenum(${ITEM_CSV} ${ITEM_HEADER} itemtype_t id ITEM_TYPE_) + +add_custom_command( + OUTPUT ${OUTPUT_FULL_PATH} + COMMAND ${CMAKE_COMMAND} + -DENV_FILE=${INPUT_FULL_PATH} + -DOUT_HEADER=${OUTPUT_FULL_PATH} + -P ${CMAKE_SOURCE_DIR}/cmake/modules/envtoh.cmake + DEPENDS ${INPUT_FULL_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/envtoh.cmake + COMMENT "Generating ${OUTPUT_NAME_RELATIVE}" +) +add_custom_target(${OUTPUT_NAME_RELATIVE}_header DEPENDS ${OUTPUT_FULL_PATH}) +add_dependencies(${DUSK_LIBRARY_TARGET_NAME} ${OUTPUT_NAME_RELATIVE}_header) \ No newline at end of file diff --git a/src/item/backpack.c b/src/item/backpack.c new file mode 100644 index 0000000..4a36e0e --- /dev/null +++ b/src/item/backpack.c @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "backpack.h" + +inventorystack_t BACKPACK_STORAGE[BACKPACK_STORAGE_SIZE_MAX]; +inventory_t BACKPACK; + +void backpackInit() { + inventoryInit(&BACKPACK, BACKPACK_STORAGE, BACKPACK_STORAGE_SIZE_MAX); +} \ No newline at end of file diff --git a/src/item/backpack.h b/src/item/backpack.h index 6050da7..4cb4935 100644 --- a/src/item/backpack.h +++ b/src/item/backpack.h @@ -6,4 +6,14 @@ */ #pragma once -#include "inventory.h" \ No newline at end of file +#include "inventory.h" + +#define BACKPACK_STORAGE_SIZE_MAX 20 + +extern inventorystack_t BACKPACK_STORAGE[BACKPACK_STORAGE_SIZE_MAX]; +extern inventory_t BACKPACK; + +/** + * Initializes the backpack inventory for the player. + */ +void backpackInit(); \ No newline at end of file diff --git a/src/item/item.csv b/src/item/item.csv new file mode 100644 index 0000000..8b25944 --- /dev/null +++ b/src/item/item.csv @@ -0,0 +1,4 @@ +id,type, +POTION,MEDICINE, +POTATO,FOOD, +APPLE,FOOD, \ No newline at end of file diff --git a/src/script/module/moduleitem.h b/src/script/module/moduleitem.h new file mode 100644 index 0000000..9c22234 --- /dev/null +++ b/src/script/module/moduleitem.h @@ -0,0 +1,259 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" +#include "item/inventory.h" +#include "item/backpack.h" + +int moduleInventoryItemExists(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + // Expect inventory pointer and item ID + if(!lua_islightuserdata(L, 1)) { + luaL_error(L, "inventoryItemExists: Expected inventory pointer as first argument"); + return 0; + } + + if(!lua_isinteger(L, 2)) { + luaL_error(L, "inventoryItemExists: Expected item ID as second argument"); + return 0; + } + + inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1); + itemid_t item = (itemid_t)lua_tointeger(L, 2); + + assertNotNull(inventory, "Inventory pointer cannot be NULL."); + + // Error if item is ITEM_ID_NULL + if(item == ITEM_ID_NULL) { + luaL_error(L, "inventoryItemExists: Item ID cannot be ITEM_ID_NULL"); + return 0; + } + + bool_t hasItem = inventoryItemExists(inventory, item); + lua_pushboolean(L, hasItem); + return 1; +} + +int moduleInventorySet(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + // Requires inventory pointer, item ID and quantity (uint8_t) + if(!lua_islightuserdata(L, 1)) { + luaL_error(L, "inventorySet: Expected inventory pointer as first argument"); + return 0; + } + + if(!lua_isinteger(L, 2)) { + luaL_error(L, "inventorySet: Expected item ID as second argument"); + return 0; + } + + if(!lua_isinteger(L, 3)) { + luaL_error(L, "inventorySet: Expected quantity as third argument"); + return 0; + } + + inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1); + itemid_t item = (itemid_t)lua_tointeger(L, 2); + uint8_t quantity = (uint8_t)lua_tointeger(L, 3); + + assertNotNull(inventory, "Inventory pointer cannot be NULL."); + inventorySet(inventory, item, quantity); + return 0; +} + +int moduleInventoryAdd(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + // Requires inventory pointer, item ID and quantity (uint8_t) + if(!lua_islightuserdata(L, 1)) { + luaL_error(L, "inventoryAdd: Expected inventory pointer as first argument"); + return 0; + } + + if(!lua_isinteger(L, 2)) { + luaL_error(L, "inventoryAdd: Expected item ID as second argument"); + return 0; + } + + if(!lua_isinteger(L, 3)) { + luaL_error(L, "inventoryAdd: Expected quantity as third argument"); + return 0; + } + + inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1); + itemid_t item = (itemid_t)lua_tointeger(L, 2); + uint8_t quantity = (uint8_t)lua_tointeger(L, 3); + + assertNotNull(inventory, "Inventory pointer cannot be NULL."); + inventoryAdd(inventory, item, quantity); + return 0; +} + +int moduleInventoryRemove(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + // Requires inventory pointer and item ID + if(!lua_islightuserdata(L, 1)) { + luaL_error(L, "inventoryRemove: Expected inventory pointer as first argument"); + return 0; + } + + if(!lua_isinteger(L, 2)) { + luaL_error(L, "inventoryRemove: Expected item ID as second argument"); + return 0; + } + + inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1); + itemid_t item = (itemid_t)lua_tointeger(L, 2); + assertNotNull(inventory, "Inventory pointer cannot be NULL."); + + // if there is a third argument (quantity), then we are actually doing a + // partial removal. + if(lua_gettop(L) >= 3) { + if(!lua_isinteger(L, 3)) { + luaL_error(L, "inventoryRemove: Expected quantity as third argument"); + return 0; + } + uint8_t amount = (uint8_t)lua_tointeger(L, 3); + uint8_t currentQuantity = inventoryGetCount(inventory, item); + if(amount >= currentQuantity) { + // Remove entire stack + inventoryRemove(inventory, item); + return 0; + } + + // Set new quantity + inventorySet(inventory, item, currentQuantity - amount); + return 0; + } + + inventoryRemove(inventory, item); + return 0; +} + +int moduleInventoryGetCount(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + // Requires inventory pointer and item ID + if(!lua_islightuserdata(L, 1)) { + luaL_error(L, "inventoryGetCount: Expected inventory pointer as first argument"); + return 0; + } + + if(!lua_isinteger(L, 2)) { + luaL_error(L, "inventoryGetCount: Expected item ID as second argument"); + return 0; + } + + inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1); + itemid_t item = (itemid_t)lua_tointeger(L, 2); + + assertNotNull(inventory, "Inventory pointer cannot be NULL."); + + uint8_t count = inventoryGetCount(inventory, item); + lua_pushinteger(L, count); + return 1; +} + +int moduleInventoryIsFull(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + // Requires inventory pointer + if(!lua_islightuserdata(L, 1)) { + luaL_error(L, "inventoryIsFull: Expected inventory pointer as first argument"); + return 0; + } + + inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1); + + assertNotNull(inventory, "Inventory pointer cannot be NULL."); + + bool_t isFull = inventoryIsFull(inventory); + lua_pushboolean(L, isFull); + return 1; +} + +int moduleInventoryItemFull(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + // Requires inventory pointer and item ID + if(!lua_islightuserdata(L, 1)) { + luaL_error(L, "inventoryItemFull: Expected inventory pointer as first argument"); + return 0; + } + + if(!lua_isinteger(L, 2)) { + luaL_error(L, "inventoryItemFull: Expected item ID as second argument"); + return 0; + } + + inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1); + itemid_t item = (itemid_t)lua_tointeger(L, 2); + + assertNotNull(inventory, "Inventory pointer cannot be NULL."); + + bool_t isFull = inventoryItemFull(inventory, item); + lua_pushboolean(L, isFull); + return 1; +} + +int moduleInventorySort(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + // Requires inventory pointer, sort type and reverse flag + if(!lua_islightuserdata(L, 1)) { + luaL_error(L, "inventorySort: Expected inventory pointer as first argument"); + return 0; + } + + if(!lua_isinteger(L, 2)) { + luaL_error(L, "inventorySort: Expected sort type as second argument"); + return 0; + } + + // Optional, reverse + bool_t reverse = false; + if(lua_gettop(L) >= 3) { + if(!lua_isboolean(L, 3)) { + luaL_error(L, "inventorySort: Expected reverse flag as third argument"); + return 0; + } + + reverse = (bool_t)lua_toboolean(L, 3); + } + + inventory_t *inventory = (inventory_t *)lua_touserdata(L, 1); + inventorysort_t sortBy = (inventorysort_t)lua_tointeger(L, 2); + + assertNotNull(inventory, "Inventory pointer cannot be NULL."); + inventorySort(inventory, sortBy, reverse); + return 0; +} + +void moduleItem(scriptcontext_t *context) { + assertNotNull(context, "Script context cannot be NULL"); + + // Item information + + // Bind BACKPACK const pointer + scriptContextRegPointer(context, "BACKPACK", (void *)&BACKPACK); + + // Bind Methods + scriptContextRegFunc( + context, "inventoryItemExists", moduleInventoryItemExists + ); + scriptContextRegFunc(context, "inventoryAdd", moduleInventoryAdd); + scriptContextRegFunc(context, "inventorySet", moduleInventorySet); + scriptContextRegFunc(context, "inventoryRemove", moduleInventoryRemove); + scriptContextRegFunc(context, "inventoryGetCount", moduleInventoryGetCount); + scriptContextRegFunc(context, "inventoryIsFull", moduleInventoryIsFull); + scriptContextRegFunc(context, "inventoryItemFull", moduleInventoryItemFull); + scriptContextRegFunc(context, "inventorySort", moduleInventorySort); +} \ No newline at end of file diff --git a/src/script/scriptcontext.c b/src/script/scriptcontext.c index 85346d1..7527548 100644 --- a/src/script/scriptcontext.c +++ b/src/script/scriptcontext.c @@ -49,6 +49,19 @@ void scriptContextRegFunc( lua_register(context->luaState, fnName, function); } +void scriptContextRegPointer( + scriptcontext_t *context, + const char_t *name, + void *pointer +) { + assertNotNull(context, "Script context cannot be NULL"); + assertNotNull(name, "Name cannot be NULL"); + assertNotNull(pointer, "Pointer cannot be NULL"); + + lua_pushlightuserdata(context->luaState, pointer); + lua_setglobal(context->luaState, name); +} + errorret_t scriptContextCallFunc( scriptcontext_t *context, const char_t *fnName, diff --git a/src/script/scriptcontext.h b/src/script/scriptcontext.h index b78eb57..3c63b95 100644 --- a/src/script/scriptcontext.h +++ b/src/script/scriptcontext.h @@ -37,6 +37,19 @@ void scriptContextRegFunc( lua_CFunction function ); +/** + * Register a pointer within a script context. + * + * @param context The script context to use. + * @param name The name of the pointer in Lua. + * @param pointer The pointer to register. + */ +void scriptContextRegPointer( + scriptcontext_t *context, + const char_t *name, + void *pointer +); + /** * Call a Lua function within a script context. * diff --git a/src/script/scriptmodule.c b/src/script/scriptmodule.c index 3ff670f..05284b9 100644 --- a/src/script/scriptmodule.c +++ b/src/script/scriptmodule.c @@ -10,12 +10,14 @@ #include "script/module/moduleinput.h" #include "script/module/moduleplatform.h" #include "script/module/modulescene.h" +#include "script/module/moduleitem.h" const scriptmodule_t SCRIPT_MODULE_LIST[] = { { .name = "system", .callback = moduleSystem }, { .name = "input", .callback = moduleInput }, { .name = "platform", .callback = modulePlatform }, { .name = "scene", .callback = moduleScene }, + { .name = "item", .callback = moduleItem }, }; #define SCRIPT_MODULE_COUNT ( \ diff --git a/src/ui/ui.c b/src/ui/ui.c index 1faba12..c6bf883 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -42,7 +42,6 @@ void uiRender(void) { // Render UI elements here if(UI.fontTexture.width > 0) { uiDebugRender(UI.fontTileset, &UI.fontTexture); - // uiTextboxRender(); } cameraPopMatrix(); } diff --git a/tools/csvtoenum.py b/tools/csvtoenum.py new file mode 100644 index 0000000..b91d252 --- /dev/null +++ b/tools/csvtoenum.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +""" +csvtoenum.py: Generate a C enum header from a CSV file. +Usage: + python csvtoenum.py +""" +import sys +import csv + +def main(): + if len(sys.argv) != 6: + print("Usage: csvtoenum.py ", file=sys.stderr) + sys.exit(1) + csv_file, out_header, c_type, take_column, prefix_column = sys.argv[1:6] + + with open(csv_file, newline='') as f: + reader = csv.DictReader(f) + if take_column not in reader.fieldnames: + print(f"TAKE_COLUMN '{take_column}' not found in CSV header", file=sys.stderr) + sys.exit(2) + if prefix_column not in reader.fieldnames: + print(f"PREFIX_COLUMN '{prefix_column}' not found in CSV header", file=sys.stderr) + sys.exit(2) + entries = [] + for row in reader: + entry = row[take_column].strip() + prefix = row[prefix_column].strip() + if entry: + entries.append(f" {prefix}{entry},") + + # Compose enum + enum_entries = [f" {prefix_column}NULL,"] + enum_entries.extend(entries) + enum_entries.append(f" {prefix_column}COUNT,") + enum_body = "\n".join(enum_entries) + header = f"""/** + * Auto-generated by csvtoenum.py + * Source: {csv_file} + */ + +typedef enum {{ +{enum_body} +}} {c_type}; +""" + with open(out_header, 'w') as f: + f.write(header) + +if __name__ == "__main__": + main()