diff --git a/assets/init.lua b/assets/init.lua index 537b165..049cafc 100644 --- a/assets/init.lua +++ b/assets/init.lua @@ -1,6 +1,7 @@ module('platform') module('input') module('scene') +module('map') -- Default Input bindings. if PLATFORM == "psp" then @@ -37,4 +38,5 @@ else end end -sceneSet('map') \ No newline at end of file +sceneSet('map') +mapLoad('map/testmap/testmap.dmf') \ No newline at end of file diff --git a/assets/map/CMakeLists.txt b/assets/map/CMakeLists.txt index 03f4a37..2665d3f 100644 --- a/assets/map/CMakeLists.txt +++ b/assets/map/CMakeLists.txt @@ -3,4 +3,4 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -add_asset(MAP map.json) \ No newline at end of file +add_subdirectory(testmap) \ No newline at end of file diff --git a/assets/map/map.json b/assets/map/map.json deleted file mode 100644 index fa38343..0000000 --- a/assets/map/map.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mapName": "Test" -} \ No newline at end of file diff --git a/assets/map/testmap/CMakeLists.txt b/assets/map/testmap/CMakeLists.txt new file mode 100644 index 0000000..61ddf2b --- /dev/null +++ b/assets/map/testmap/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_asset(MAP testmap.json) +add_asset(SCRIPT testmap.lua) \ No newline at end of file diff --git a/assets/map/map/-1_-1_0.json b/assets/map/testmap/chunks/-1_-1_0.json similarity index 100% rename from assets/map/map/-1_-1_0.json rename to assets/map/testmap/chunks/-1_-1_0.json diff --git a/assets/map/map/-1_-1_1.json b/assets/map/testmap/chunks/-1_-1_1.json similarity index 100% rename from assets/map/map/-1_-1_1.json rename to assets/map/testmap/chunks/-1_-1_1.json diff --git a/assets/map/map/-1_0_0.json b/assets/map/testmap/chunks/-1_0_0.json similarity index 100% rename from assets/map/map/-1_0_0.json rename to assets/map/testmap/chunks/-1_0_0.json diff --git a/assets/map/map/-2_1_0.json b/assets/map/testmap/chunks/-2_1_0.json similarity index 100% rename from assets/map/map/-2_1_0.json rename to assets/map/testmap/chunks/-2_1_0.json diff --git a/assets/map/map/0_-1_0.json b/assets/map/testmap/chunks/0_-1_0.json similarity index 100% rename from assets/map/map/0_-1_0.json rename to assets/map/testmap/chunks/0_-1_0.json diff --git a/assets/map/map/0_-1_1.json b/assets/map/testmap/chunks/0_-1_1.json similarity index 100% rename from assets/map/map/0_-1_1.json rename to assets/map/testmap/chunks/0_-1_1.json diff --git a/assets/map/map/0_0_0.json b/assets/map/testmap/chunks/0_0_0.json similarity index 100% rename from assets/map/map/0_0_0.json rename to assets/map/testmap/chunks/0_0_0.json diff --git a/assets/map/map/1_0_0.json b/assets/map/testmap/chunks/1_0_0.json similarity index 100% rename from assets/map/map/1_0_0.json rename to assets/map/testmap/chunks/1_0_0.json diff --git a/assets/map/map/2_0_0.json b/assets/map/testmap/chunks/2_0_0.json similarity index 100% rename from assets/map/map/2_0_0.json rename to assets/map/testmap/chunks/2_0_0.json diff --git a/assets/map/map/3_0_0.json b/assets/map/testmap/chunks/3_0_0.json similarity index 100% rename from assets/map/map/3_0_0.json rename to assets/map/testmap/chunks/3_0_0.json diff --git a/assets/map/map/4_0_0.json b/assets/map/testmap/chunks/4_0_0.json similarity index 100% rename from assets/map/map/4_0_0.json rename to assets/map/testmap/chunks/4_0_0.json diff --git a/assets/map/map/5_0_0.json b/assets/map/testmap/chunks/5_0_0.json similarity index 100% rename from assets/map/map/5_0_0.json rename to assets/map/testmap/chunks/5_0_0.json diff --git a/assets/map/testmap/testmap.json b/assets/map/testmap/testmap.json new file mode 100644 index 0000000..d9e4030 --- /dev/null +++ b/assets/map/testmap/testmap.json @@ -0,0 +1,3 @@ +{ + "name": "Test" +} \ No newline at end of file diff --git a/assets/map/testmap/testmap.lua b/assets/map/testmap/testmap.lua new file mode 100644 index 0000000..7583401 --- /dev/null +++ b/assets/map/testmap/testmap.lua @@ -0,0 +1 @@ +print('Test Map Script Run') \ No newline at end of file diff --git a/assets/scene/map.lua b/assets/scene/map.lua index a4c5bff..ed42a51 100644 --- a/assets/scene/map.lua +++ b/assets/scene/map.lua @@ -1 +1 @@ -print('map') \ No newline at end of file +-- Map Scene \ No newline at end of file diff --git a/src/asset/assettype.h b/src/asset/assettype.h index de12191..a419e8a 100644 --- a/src/asset/assettype.h +++ b/src/asset/assettype.h @@ -89,5 +89,5 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = { .header = "DSF", .loadStrategy = ASSET_LOAD_STRAT_CUSTOM, .custom = assetScriptHandler - } + }, }; \ No newline at end of file diff --git a/src/rpg/world/map.c b/src/rpg/world/map.c index dc45fa8..ff9123c 100644 --- a/src/rpg/world/map.c +++ b/src/rpg/world/map.c @@ -11,6 +11,7 @@ #include "asset/asset.h" #include "rpg/entity/entity.h" #include "util/string.h" +#include "script/scriptcontext.h" map_t MAP; @@ -44,6 +45,19 @@ errorret_t mapLoad(const char_t *path, const chunkpos_t position) { // Store the map file path stringCopy(MAP.filePath, path, MAP_FILE_PATH_MAX); + // Determine directory path (it is dirname) + stringCopy(MAP.dirPath, path, MAP_FILE_PATH_MAX); + char_t *last = stringFindLastChar(MAP.dirPath, '/'); + if(last == NULL) errorThrow("Invalid map file path"); + + // Store filename, sans extension + stringCopy(MAP.fileName, last + 1, MAP_FILE_PATH_MAX); + *last = '\0'; // Terminate to get directory path + + last = stringFindLastChar(MAP.fileName, '.'); + if(last == NULL) errorThrow("Map file name has no extension"); + *last = '\0'; // Terminate to remove extension + // Reset map position MAP.chunkPosition = position; @@ -64,6 +78,18 @@ errorret_t mapLoad(const char_t *path, const chunkpos_t position) { } // Execute map script. + char_t scriptPath[MAP_FILE_PATH_MAX + 16]; + stringFormat( + scriptPath, sizeof(scriptPath), "%s/%s.dsf", + MAP.dirPath, MAP.fileName + ); + if(assetFileExists(scriptPath)) { + scriptcontext_t ctx; + errorChain(scriptContextInit(&ctx)); + errorChain(scriptContextExecFile(&ctx, scriptPath)); + scriptContextDispose(&ctx); + } + errorOk(); } @@ -183,8 +209,8 @@ errorret_t mapChunkLoad(chunk_t* chunk) { memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); // Get chunk filepath. - snprintf(buffer, sizeof(buffer), "%s/%d_%d_%d.dcf", - MAP.filePath, + snprintf(buffer, sizeof(buffer), "%s/chunks/%d_%d_%d.dcf", + MAP.dirPath, chunk->position.x, chunk->position.y, chunk->position.z diff --git a/src/rpg/world/map.h b/src/rpg/world/map.h index f8e7730..29137da 100644 --- a/src/rpg/world/map.h +++ b/src/rpg/world/map.h @@ -12,6 +12,9 @@ typedef struct map_s { char_t filePath[MAP_FILE_PATH_MAX]; + char_t dirPath[MAP_FILE_PATH_MAX]; + char_t fileName[MAP_FILE_PATH_MAX]; + chunk_t chunks[MAP_CHUNK_COUNT]; chunk_t *chunkOrder[MAP_CHUNK_COUNT]; chunkpos_t chunkPosition; diff --git a/src/script/module/modulemap.h b/src/script/module/modulemap.h new file mode 100644 index 0000000..e3b15a3 --- /dev/null +++ b/src/script/module/modulemap.h @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/scriptcontext.h" +#include "debug/debug.h" +#include "assert/assert.h" +#include "rpg/world/map.h" + +int32_t moduleMapLoad(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isstring(L, 1)) { + luaL_error(L, "Expected string map filename"); + return 0; + } + + // Potentially provide up to 3 params + chunkpos_t initial = { .x = 0, .y = 0, .z = 0 }; + if(lua_isnumber(L, 2)) { + initial.x = (chunkunit_t)luaL_checkinteger(L, 2); + } + if(lua_isnumber(L, 3)) { + initial.y = (chunkunit_t)luaL_checkinteger(L, 3); + } + if(lua_isnumber(L, 4)) { + initial.z = (chunkunit_t)luaL_checkinteger(L, 4); + } + + // Load the map. + errorret_t ret = mapLoad(luaL_checkstring(L, 1), initial); + if(ret.code != ERROR_OK) { + luaL_error(L, "Failed to load map"); + errorCatch(errorPrint(ret)); + return 0; + } + + return 0; +} + +void moduleMapSystem(scriptcontext_t *context) { + assertNotNull(context, "Script context cannot be NULL"); + + scriptContextRegFunc(context, "mapLoad", moduleMapLoad); +} \ No newline at end of file diff --git a/src/script/scriptmodule.c b/src/script/scriptmodule.c index 3ff670f..aaf204d 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/modulemap.h" const scriptmodule_t SCRIPT_MODULE_LIST[] = { { .name = "system", .callback = moduleSystem }, { .name = "input", .callback = moduleInput }, { .name = "platform", .callback = modulePlatform }, { .name = "scene", .callback = moduleScene }, + { .name = "map", .callback = moduleMapSystem }, }; #define SCRIPT_MODULE_COUNT ( \ diff --git a/src/util/string.c b/src/util/string.c index 319cc5f..42c0a9c 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -57,6 +57,17 @@ char_t * stringToken(char_t *str, const char_t *delim) { return strtok(str, delim); } +char_t * stringFindLastChar(const char_t *str, const char_t c) { + assertNotNull(str, "str must not be NULL"); + char_t *last = NULL; + for(const char_t *p = str; *p != '\0'; p++) { + if(*p == c) { + last = (char_t *)p; + } + } + return last; +} + int32_t stringFormat( char_t *dest, const size_t destSize, diff --git a/src/util/string.h b/src/util/string.h index f269573..3c55a0f 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -64,6 +64,16 @@ void stringTrim(char_t *str); */ char_t * stringToken(char_t *str, const char_t *delim); +/** + * Finds the last occurrence of a character in a string. + * + * @param str The string to search. + * @param c The character to find. + * @return A pointer to the last occurrence of the character in the string, or + * NULL if not found. + */ +char_t * stringFindLastChar(const char_t *str, const char_t c); + /** * Formats a string. * diff --git a/tools/assetstool/processmap.py b/tools/assetstool/processmap.py index 7472487..833a0db 100644 --- a/tools/assetstool/processmap.py +++ b/tools/assetstool/processmap.py @@ -111,11 +111,11 @@ def processMap(asset): map = Map(None) map.load(asset['path']) - dir = map.getMapDirectory() + chunksDir = map.getChunkDirectory() - files = os.listdir(dir) + files = os.listdir(chunksDir) if len(files) == 0: - print(f"Error: No chunk files found in map directory {dir}.") + print(f"Error: No chunk files found in {chunksDir}.") sys.exit(1) chunkFiles = [] @@ -133,21 +133,22 @@ def processMap(asset): result = processChunk(chunk) chunkFiles.extend(result['files']) - outMap = { - 'files': chunkFiles - } - return assetCache(asset['path'], outMap) - - # List files - chunkFiles = [] - for fileName in os.listdir(asset['path']): - if not fileName.endswith('.json'): - continue - result = processChunk(os.path.join(asset['path'], fileName)) - chunkFiles.extend(result['files']) + # Map file + outBuffer = bytearray() + outBuffer.extend(b'DMF') + outBuffer.extend(len(chunkFiles).to_bytes(4, 'little')) + + # DMF (Dusk Map file) + fileRelative = getAssetRelativePath(asset['path']) + fileNameWithoutExt = os.path.splitext(os.path.basename(fileRelative))[0] + outputMapRelative = os.path.join(os.path.dirname(fileRelative), f"{fileNameWithoutExt}.dmf") + outputMapPath = os.path.join(args.output_assets, outputMapRelative) + os.makedirs(os.path.dirname(outputMapPath), exist_ok=True) + with open(outputMapPath, "wb") as f: + f.write(outBuffer) outMap = { 'files': chunkFiles } - + outMap['files'].append(outputMapPath) return assetCache(asset['path'], outMap) \ No newline at end of file diff --git a/tools/dusk/chunk.py b/tools/dusk/chunk.py index 16298ed..e5d416f 100644 --- a/tools/dusk/chunk.py +++ b/tools/dusk/chunk.py @@ -131,12 +131,12 @@ class Chunk: return self.dirty def getFilename(self): - if not self.map or not hasattr(self.map, 'getMapDirectory'): + if not self.map or not hasattr(self.map, 'getChunkDirectory'): return None - dir_path = self.map.getMapDirectory() - if dir_path is None: + dirPath = self.map.getChunkDirectory() + if dirPath is None: return None - return f"{dir_path}/{self.x}_{self.y}_{self.z}.json" + return f"{dirPath}/{self.x}_{self.y}_{self.z}.json" def draw(self): self.vertexBuffer.draw() diff --git a/tools/dusk/map.py b/tools/dusk/map.py index 14c20c8..5c44c4a 100644 --- a/tools/dusk/map.py +++ b/tools/dusk/map.py @@ -1,4 +1,5 @@ import json +import sys from dusk.event import Event from PyQt5.QtWidgets import QFileDialog, QMessageBox from PyQt5.QtCore import QTimer @@ -129,11 +130,17 @@ class Map: return self.mapFileName if self.mapFileName and os.path.exists(self.mapFileName) else None def getMapDirectory(self): - fname = self.getMapFilename() - if not fname or not fname.endswith('.json'): + if self.mapFileName is None: return None - return fname[:-5] # Remove '.json' extension - + dirname = os.path.dirname(self.mapFileName) + return dirname + + def getChunkDirectory(self): + dirName = self.getMapDirectory() + if dirName is None: + return None + return os.path.join(dirName, 'chunks') + def anyChunksDirty(self): for chunk in self.chunks.values(): if chunk.isDirty():