diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index 6ef5f59..b7a735e 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -12,5 +12,6 @@ add_subdirectory(palette) add_subdirectory(locale) add_subdirectory(entity) +add_subdirectory(script) add_subdirectory(map) add_subdirectory(ui) \ No newline at end of file diff --git a/assets/script/CMakeLists.txt b/assets/script/CMakeLists.txt new file mode 100644 index 0000000..d273641 --- /dev/null +++ b/assets/script/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_asset(SCRIPT test.lua) \ No newline at end of file diff --git a/assets/script/test.lua b/assets/script/test.lua new file mode 100644 index 0000000..32aca49 --- /dev/null +++ b/assets/script/test.lua @@ -0,0 +1 @@ +print("Test Lua script") \ No newline at end of file diff --git a/cmake/modules/Findlua.cmake b/cmake/modules/Findlua.cmake new file mode 100644 index 0000000..a31ceb0 --- /dev/null +++ b/cmake/modules/Findlua.cmake @@ -0,0 +1,59 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +if(NOT TARGET lua) + message(STATUS "Looking for Lua...") + + set(LUA_FOUND FALSE CACHE INTERNAL "Lua found") + set(LUA_DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/_lua") + set(LUA_SEARCH_ROOTS + "${LUA_ROOT}" + "$ENV{LUADEV}" + "$ENV{HOME}/luadev" + "/usr/local/luadev" + "/opt/luadev" + "/usr/luadev" + "${LUA_DOWNLOAD_DIR}/luadev" + ) + + foreach(root IN LISTS LUA_SEARCH_ROOTS) + list(APPEND LUA_BIN_HINTS "${root}/bin") + list(APPEND LUA_INCLUDE_HINTS "${root}/include") + list(APPEND LUA_LIB_HINTS "${root}/lib") + endforeach() + + # Find Lua interpreter + find_program(LUA_EXECUTABLE NAMES lua HINTS ${LUA_BIN_HINTS}) + + # Find Lua headers and library + find_path(LUA_INCLUDE_DIR lua.h HINTS ${LUA_INCLUDE_HINTS}) + find_library(LUA_LIBRARY NAMES lua HINTS ${LUA_LIB_HINTS}) + + # If not found, use FetchContent to download and build Lua + if(NOT LUA_EXECUTABLE OR NOT LUA_INCLUDE_DIR OR NOT LUA_LIBRARY) + message(STATUS "Lua not found in system paths. Using FetchContent to download and build Lua.") + include(FetchContent) + FetchContent_Declare( + lua + GIT_REPOSITORY https://github.com/lua/lua.git + GIT_TAG v5.4.6 # Change to desired version + ) + FetchContent_MakeAvailable(lua) + # Try to locate built Lua + set(LUA_INCLUDE_DIR "${lua_SOURCE_DIR}") + set(LUA_LIBRARY "${lua_BINARY_DIR}/liblua.a") + set(LUA_EXECUTABLE "${lua_BINARY_DIR}/lua") + endif() + + if(LUA_EXECUTABLE AND LUA_INCLUDE_DIR AND LUA_LIBRARY) + set(LUA_FOUND TRUE CACHE INTERNAL "Lua found") + add_library(lua INTERFACE IMPORTED) + set_target_properties(lua PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${LUA_LIBRARY}" + ) + message(STATUS "Lua found: ${LUA_EXECUTABLE}") + endif() +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ed2b94..98bf274 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ find_package(cglm REQUIRED) find_package(libzip REQUIRED) +find_package(lua REQUIRED) # Libs target_link_libraries(${DUSK_TARGET_NAME} @@ -12,6 +13,7 @@ target_link_libraries(${DUSK_TARGET_NAME} m cglm zip + lua pthread ) @@ -41,6 +43,7 @@ add_subdirectory(input) add_subdirectory(locale) add_subdirectory(rpg) add_subdirectory(scene) +add_subdirectory(script) add_subdirectory(thread) add_subdirectory(time) add_subdirectory(ui) diff --git a/src/asset/assettype.h b/src/asset/assettype.h index d232514..de12191 100644 --- a/src/asset/assettype.h +++ b/src/asset/assettype.h @@ -11,6 +11,7 @@ #include "type/assetlanguage.h" #include "type/assetmap.h" #include "type/assetchunk.h" +#include "type/assetscript.h" #include typedef enum { @@ -21,6 +22,7 @@ typedef enum { ASSET_TYPE_LANGUAGE, ASSET_TYPE_MAP, ASSET_TYPE_CHUNK, + ASSET_TYPE_SCRIPT, ASSET_TYPE_COUNT, } assettype_t; @@ -81,5 +83,11 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = { .header = "DCF", .loadStrategy = ASSET_LOAD_STRAT_CUSTOM, .custom = assetChunkLoad - } + }, + + [ASSET_TYPE_SCRIPT] = { + .header = "DSF", + .loadStrategy = ASSET_LOAD_STRAT_CUSTOM, + .custom = assetScriptHandler + } }; \ No newline at end of file diff --git a/src/asset/type/CMakeLists.txt b/src/asset/type/CMakeLists.txt index 1011933..1acad3d 100644 --- a/src/asset/type/CMakeLists.txt +++ b/src/asset/type/CMakeLists.txt @@ -11,4 +11,5 @@ target_sources(${DUSK_TARGET_NAME} assetlanguage.c assetmap.c assetchunk.c + assetscript.c ) \ No newline at end of file diff --git a/src/asset/type/assetscript.c b/src/asset/type/assetscript.c new file mode 100644 index 0000000..9077225 --- /dev/null +++ b/src/asset/type/assetscript.c @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "asset/asset.h" +#include "assert/assert.h" + +errorret_t assetScriptHandler(assetcustom_t custom) { + assertNotNull(custom.zipFile, "Custom asset zip file cannot be NULL"); + assertNotNull(custom.output, "Custom asset output cannot be NULL"); + + assetscript_t *script = (assetscript_t *)custom.output; + errorChain(assetScriptInit(script, custom.zipFile)); + + errorOk(); +} + +errorret_t assetScriptInit( + assetscript_t *script, + zip_file_t *zipFile +) { + assertNotNull(script, "Script asset cannot be NULL"); + assertNotNull(zipFile, "Zip file cannot be NULL"); + + // We now own the zip file handle. + script->zip = zipFile; + + errorOk(); +} + +const char_t * assetScriptReader(lua_State* lState, void* data, size_t* size) { + assetscript_t *script = (assetscript_t *)data; + zip_int64_t bytesRead = zip_fread( + script->zip, script->buffer, sizeof(script->buffer) + ); + + if(bytesRead < 0) { + *size = 0; + return NULL; + } + + *size = (size_t)bytesRead; + return script->buffer; +} + +errorret_t assetScriptDispose(assetscript_t *script) { + assertNotNull(script, "Script asset cannot be NULL"); + + if(script->zip != NULL) { + zip_fclose(script->zip); + script->zip = NULL; + } + + errorOk(); +} \ No newline at end of file diff --git a/src/asset/type/assetscript.h b/src/asset/type/assetscript.h new file mode 100644 index 0000000..0a3a2d4 --- /dev/null +++ b/src/asset/type/assetscript.h @@ -0,0 +1,57 @@ +/** + * 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 "duskdefs.h" +#include +#include + +#define ASSET_SCRIPT_BUFFER_SIZE 1024 + +typedef struct assetscript_s { + zip_file_t *zip; + char_t buffer[ASSET_SCRIPT_BUFFER_SIZE]; +} assetscript_t; + +typedef struct assetcustom_s assetcustom_t; + +/** + * Receiving function from the asset manager to handle script assets. + * + * @param custom Custom asset loading data. + * @return Error code. + */ +errorret_t assetScriptHandler(assetcustom_t custom); + +/** + * Initializes a script asset. + * + * @param script Script asset to initialize. + * @param zipFile Zip file handle for the script asset. + * @return Error code. + */ +errorret_t assetScriptInit(assetscript_t *script, zip_file_t *zipFile); + +/** + * Reader function for Lua to read script data from the asset. + * + * @param L Lua state. + * @param data Pointer to the assetscript_t structure. + * @param size Pointer to store the size of the read data. + * @return Pointer to the read data buffer. + */ +const char_t * assetScriptReader(lua_State* L, void* data, size_t* size); + +/** + * Disposes of a script asset, freeing any allocated resources. + * + * @param script Script asset to dispose of. + * @return Error code. + */ +errorret_t assetScriptDispose(assetscript_t *script); + diff --git a/src/engine/engine.c b/src/engine/engine.c index f536d65..6aef2de 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -15,6 +15,7 @@ #include "asset/asset.h" #include "ui/ui.h" #include "rpg/rpg.h" +#include "script/scriptmanager.h" #include "debug/debug.h" engine_t ENGINE; @@ -35,6 +36,12 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(uiInit()); errorChain(rpgInit()); errorChain(sceneManagerInit()); + errorChain(scriptManagerInit()); + + // scriptManagerExec( + // "print('Hello from Lua!')\n" + // "luaCallable()\n" + // ); errorOk(); } diff --git a/src/main.c b/src/main.c index d62883d..532b238 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,7 @@ #include "input/input.h" int main(int argc, char **argv) { + // Main applet errorret_t ret; // Init engine diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt new file mode 100644 index 0000000..5d90652 --- /dev/null +++ b/src/script/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_TARGET_NAME} + PRIVATE + scriptmanager.c +) \ No newline at end of file diff --git a/src/script/scriptmanager.c b/src/script/scriptmanager.c new file mode 100644 index 0000000..595ab47 --- /dev/null +++ b/src/script/scriptmanager.c @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "scriptmanager.h" +#include "util/memory.h" + +#include "asset/asset.h" + +int luaCallable(lua_State *L) { + printf("This function was called from Lua!\n"); + return 0; +} + +scriptmanager_t SCRIPT_MANAGER; + +errorret_t scriptManagerInit() { + memoryZero(&SCRIPT_MANAGER, sizeof(scriptmanager_t)); + + SCRIPT_MANAGER.luaState = luaL_newstate(); + if(SCRIPT_MANAGER.luaState == NULL) { + errorThrow("Failed to init Lua state"); + } + + luaL_openlibs(SCRIPT_MANAGER.luaState); + lua_register(SCRIPT_MANAGER.luaState, "luaCallable", luaCallable); + + // SCript test + assetscript_t script; + assetLoad("script/test.dsf", &script); + + if(lua_load(SCRIPT_MANAGER.luaState, assetScriptReader, &script, "script/test.dsf", NULL) != LUA_OK) { + const char_t *strErr = lua_tostring(SCRIPT_MANAGER.luaState, -1); + lua_pop(SCRIPT_MANAGER.luaState, 1); + errorThrow("Failed to load Lua script: %s", strErr); + } + + if(lua_pcall(SCRIPT_MANAGER.luaState, 0, LUA_MULTRET, 0) != LUA_OK) { + const char_t *strErr = lua_tostring(SCRIPT_MANAGER.luaState, -1); + lua_pop(SCRIPT_MANAGER.luaState, 1); + errorThrow("Failed to execute Lua script: %s", strErr); + } + + assetScriptDispose(&script); + + + errorOk(); +} + +errorret_t scriptManagerExec(const char_t *script) { + if(SCRIPT_MANAGER.luaState == NULL) { + errorThrow("Lua state is not initialized"); + } + + if(luaL_dostring(SCRIPT_MANAGER.luaState, script) != LUA_OK) { + const char_t *strErr = lua_tostring(SCRIPT_MANAGER.luaState, -1); + lua_pop(SCRIPT_MANAGER.luaState, 1); + errorThrow("Failed to execute Lua: ", strErr); + } + + errorOk(); +} + +errorret_t scriptManagerDispose() { + if(SCRIPT_MANAGER.luaState != NULL) { + lua_close(SCRIPT_MANAGER.luaState); + SCRIPT_MANAGER.luaState = NULL; + } + + errorOk(); +} \ No newline at end of file diff --git a/src/script/scriptmanager.h b/src/script/scriptmanager.h new file mode 100644 index 0000000..72661db --- /dev/null +++ b/src/script/scriptmanager.h @@ -0,0 +1,40 @@ +/** + * 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 +#include +#include + +typedef struct scriptmanager_s { + lua_State *luaState; +} scriptmanager_t; + +extern scriptmanager_t SCRIPT_MANAGER; + +/** + * Initialize the script manager. + * + * @return The error return value. + */ +errorret_t scriptManagerInit(); + +/** + * Execute a Lua script. + * + * @param script The script to execute. + * @return The error return value. + */ +errorret_t scriptManagerExec(const char_t *script); + +/** + * Dispose of the script manager. + * + * @return The error return value. + */ +errorret_t scriptManagerDispose(); \ No newline at end of file diff --git a/tools/assetstool/processasset.py b/tools/assetstool/processasset.py index f355b91..1cfad1e 100644 --- a/tools/assetstool/processasset.py +++ b/tools/assetstool/processasset.py @@ -5,6 +5,7 @@ from assetstool.processpalette import processPalette from assetstool.processtileset import processTileset from assetstool.processmap import processMap from assetstool.processlanguage import processLanguage +from assetstool.processscript import processScript processedAssets = [] @@ -25,6 +26,8 @@ def processAsset(asset): return processMap(asset) elif t == 'language': return processLanguage(asset) + elif t == 'script': + return processScript(asset) else: print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'") sys.exit(1) \ No newline at end of file diff --git a/tools/assetstool/processscript.py b/tools/assetstool/processscript.py new file mode 100644 index 0000000..26eddd5 --- /dev/null +++ b/tools/assetstool/processscript.py @@ -0,0 +1,39 @@ +import sys +import os +from assetstool.args import args +from assetstool.assetcache import assetCache, assetGetCache +from assetstool.assethelpers import getAssetRelativePath +from dusk.defs import defs + +def processScript(asset): + cache = assetGetCache(asset['path']) + if cache is not None: + return cache + + # Load the lua file as a string + with open(asset['path'], 'r', encoding='utf-8') as f: + luaCode = f.read() + + # TODO: I will precompile or minify the Lua code here in the future + + # Create output Dusk Script File (DSF) data + data = "" + data += "DSF" + data += luaCode + + # Write to relative output file path. + relative = getAssetRelativePath(asset['path']) + fileNameWithoutExt = os.path.splitext(os.path.basename(asset['path']))[0] + outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dsf") + outputFilePath = os.path.join(args.output_assets, outputFileRelative) + os.makedirs(os.path.dirname(outputFilePath), exist_ok=True) + with open(outputFilePath, "wb") as f: + f.write(data.encode('utf-8')) + + outScript = { + 'data': data, + 'path': asset['path'], + 'files': [ outputFilePath ], + 'scriptPath': outputFileRelative, + } + return assetCache(asset['path'], outScript) \ No newline at end of file