load map first pass

This commit is contained in:
2025-09-19 12:43:57 -05:00
parent 2f40724258
commit 061352bcff
14 changed files with 256 additions and 52 deletions

View File

@@ -20,7 +20,10 @@ assetdef_t ASSET_DEFINITIONS[ASSET_TYPE_COUNT] = {
}, },
[ASSET_TYPE_CONFIG] = { [ASSET_TYPE_CONFIG] = {
"DCF", assetConfigLoad, assetConfigDispose "DCF", assetConfigLoad, assetConfigDispose
} },
[ASSET_TYPE_RPG_MAP] = {
"DRM", assetRPGMapLoad, assetRPGMapDispose
},
}; };
errorret_t assetInit(asset_t *asset, const char_t *filename) { errorret_t assetInit(asset_t *asset, const char_t *filename) {

View File

@@ -13,6 +13,7 @@
#include "asset/type/assetpaletteimage.h" #include "asset/type/assetpaletteimage.h"
#include "asset/type/assetalphaimage.h" #include "asset/type/assetalphaimage.h"
#include "asset/type/assetconfig.h" #include "asset/type/assetconfig.h"
#include "asset/type/assetrpgmap.h"
#define ASSET_HEADER_SIZE 3 #define ASSET_HEADER_SIZE 3
#define ASSET_REFERENCE_COUNT_MAX 8 #define ASSET_REFERENCE_COUNT_MAX 8
@@ -33,6 +34,7 @@ typedef enum {
ASSET_TYPE_PALETTE_IMAGE, ASSET_TYPE_PALETTE_IMAGE,
ASSET_TYPE_ALPHA_IMAGE, ASSET_TYPE_ALPHA_IMAGE,
ASSET_TYPE_CONFIG, ASSET_TYPE_CONFIG,
ASSET_TYPE_RPG_MAP,
ASSET_TYPE_COUNT ASSET_TYPE_COUNT
} assettype_t; } assettype_t;
@@ -47,8 +49,9 @@ typedef struct asset_s {
union { union {
assetpaletteimage_t paletteImage; assetpaletteimage_t paletteImage;
assetalphaimager_t alphaImage; assetalphaimage_t alphaImage;
assetconfig_t config; assetconfig_t config;
assetrpgmap_t rpgMap;
}; };
} asset_t; } asset_t;

View File

@@ -9,4 +9,5 @@ target_sources(${DUSK_TARGET_NAME}
assetalphaimage.c assetalphaimage.c
assetconfig.c assetconfig.c
assetpaletteimage.c assetpaletteimage.c
assetrpgmap.c
) )

View File

@@ -27,7 +27,7 @@ typedef struct {
typedef struct { typedef struct {
texture_t texture; texture_t texture;
} assetalphaimager_t; } assetalphaimage_t;
/** /**
* Loads an alpha image asset from the given asset structure. The asset must * Loads an alpha image asset from the given asset structure. The asset must

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetrpgmap.h"
#include "asset/asset.h"
#include "assert/assert.h"
#include "display/tileset/tilesetlist.h"
errorret_t assetRPGMapLoad(asset_t *asset) {
assertNotNull(asset, "Asset cannot be NULL.");
assertTrue(
asset->type == ASSET_TYPE_RPG_MAP,
"Asset is not of type ASSET_TYPE_RPG_MAP."
);
assetrpgmapraw_t raw;
// Read map header info
zip_int64_t bytesRead = zip_fread(
asset->file, &raw.header, sizeof(assetrpgmapheader_t)
);
if(bytesRead != sizeof(raw.header)) {
errorThrow("Failed to read RPG map header.");
}
if(raw.header.mapWidth == 0 || raw.header.mapWidth > MAP_WIDTH_MAX) {
errorThrow("Invalid RPG map width.");
}
if(raw.header.mapHeight == 0 || raw.header.mapHeight > MAP_HEIGHT_MAX) {
errorThrow("Invalid RPG map height.");
}
if(raw.header.tilesetCount == 0) {
errorThrow("Invalid RPG map tileset count.");
}
if(raw.header.tilesetCount > ASSET_RPG_MAP_TILESET_COUNT_MAX) {
errorThrow("Invalid RPG map tileset count.");
}
if(raw.header.tileLayerCount == 0) {
errorThrow("Invalid RPG map layer count.");
}
if(raw.header.tileLayerCount > MAP_LAYER_COUNT_MAX) {
errorThrow("Invalid RPG map layer count.");
}
// Read layers
for(uint32_t layer = 0; layer < raw.header.tileLayerCount; layer++) {
// Read width * height tiles
bytesRead = zip_fread(
asset->file,
&raw.layers[layer].tiles,
sizeof(assetrpgmaptile_t) * raw.header.mapWidth * raw.header.mapHeight
);
if(bytesRead != (
sizeof(assetrpgmaptile_t) * raw.header.mapWidth * raw.header.mapHeight
)) errorThrow("Failed to read RPG map layer %u.", layer);
}
// For each tileset
for(uint32_t tileset = 0; tileset < raw.header.tilesetCount; tileset++) {
// Read tileset
bytesRead = zip_fread(
asset->file,
&raw.tilesets[tileset],
sizeof(assetrpgmaptileset_t)
);
if(bytesRead != sizeof(assetrpgmaptileset_t)) {
errorThrow("Failed to read RPG map tileset %u.", tileset);
}
}
// Map data is loaded, we can load it into the map structure.
mapInit(&asset->rpgMap.map);
entity_t *ent;
ent = mapEntityAdd(&asset->rpgMap.map);
entityInit(ent, ENTITY_TYPE_PLAYER, &asset->rpgMap.map);
errorOk();
}
errorret_t assetRPGMapDispose(asset_t *asset) {
assertNotNull(asset, "Asset cannot be NULL.");
assertTrue(
asset->type == ASSET_TYPE_RPG_MAP,
"Asset is not of type ASSET_TYPE_RPG_MAP."
);
errorOk();
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include "rpg/world/map.h"
typedef struct asset_s asset_t;
#define ASSET_RPG_MAP_TILESET_COUNT_MAX 16
#pragma pack(push, 1)
typedef struct {
uint32_t mapWidth;
uint32_t mapHeight;
uint32_t tilesetCount;
uint32_t tileLayerCount;
} assetrpgmapheader_t;
typedef struct {
uint32_t firstGid;
uint32_t tilesetIndex;
} assetrpgmaptileset_t;
typedef struct {
uint32_t tilesetIndex;
} assetrpgmaptile_t;
typedef struct {
assetrpgmaptile_t tiles[MAP_TILE_COUNT_MAX];
} assetrpgmaplayer_t;
typedef struct {
assetrpgmapheader_t header;
assetrpgmaplayer_t layers[MAP_LAYER_COUNT_MAX];
assetrpgmaptileset_t tilesets[ASSET_RPG_MAP_TILESET_COUNT_MAX];
} assetrpgmapraw_t;
#pragma pack(pop)
typedef struct {
map_t map;
} assetrpgmap_t;
/**
* Loads an RPG map asset from the given asset structure. The asset must be of
* type ASSET_TYPE_RPG_MAP and must be loaded.
*
* @param asset The asset to load the RPG map from.
* @return An error code.
*/
errorret_t assetRPGMapLoad(asset_t *asset);
/**
* Disposes of an RPG map asset, freeing any allocated resources.
*
* @param asset The asset to dispose of.
* @return An error code.
*/
errorret_t assetRPGMapDispose(asset_t *asset);

View File

@@ -37,9 +37,11 @@ errorret_t sceneOverworldInit(void) {
} }
void sceneOverworldUpdate(void) { void sceneOverworldUpdate(void) {
if(RPG.map == NULL) return;
// Move camera to player. // Move camera to player.
const entity_t *start = &testMap.entities[0]; const entity_t *start = &RPG.map->entities[0];
const entity_t *end = &testMap.entities[testMap.entityCount]; const entity_t *end = &RPG.map->entities[RPG.map->entityCount];
while(start < end) { while(start < end) {
if(start->type == ENTITY_TYPE_PLAYER) { if(start->type == ENTITY_TYPE_PLAYER) {
SCENE_OVERWORLD.camera.lookat.target[0] = start->position[0]; SCENE_OVERWORLD.camera.lookat.target[0] = start->position[0];
@@ -70,7 +72,7 @@ void sceneOverworldRender(void) {
cameraPushMatrix(&SCENE_OVERWORLD.camera); cameraPushMatrix(&SCENE_OVERWORLD.camera);
sceneOverworldRenderMap(&testMap); if(RPG.map != NULL) sceneOverworldRenderMap(RPG.map);
spriteBatchFlush(); spriteBatchFlush();
cameraPopMatrix(); cameraPopMatrix();

View File

@@ -7,7 +7,7 @@
#pragma once #pragma once
#include "display/camera.h" #include "display/camera.h"
#include "rpg/world/map.h" #include "rpg/rpg.h"
#include "error/error.h" #include "error/error.h"
typedef struct { typedef struct {

View File

@@ -6,28 +6,44 @@
*/ */
#include "rpg.h" #include "rpg.h"
#include "rpg/world/map.h" #include "rpg/world/map.h"
#include "util/memory.h"
#include "asset/assetmanager.h"
map_t testMap; rpg_t RPG;
asset_t *asset;
ref_t assetRef;
void rpgInit() { void rpgInit() {
mapInit(&testMap); memoryZero(&RPG, sizeof(RPG));
testMap.width = 2;
testMap.height = 2; errorret_t ret = assetManagerLoadAsset("map/untitled.drm", &asset, &assetRef);
for(uint32_t i = 0; i < testMap.width * testMap.height; i++) { if(ret.code != ERROR_OK) {
testMap.base.tiles[i].id = 1; errorPrint(ret);
errorCatch(ret);
return;
} }
RPG.map = &asset->rpgMap.map;
entity_t *ent = mapEntityAdd(&testMap); // mapInit(&testMap);
entityInit(ent, ENTITY_TYPE_PLAYER, &testMap); // testMap.width = 2;
// testMap.height = 2;
// for(uint32_t i = 0; i < testMap.width * testMap.height; i++) {
// testMap.base.tiles[i].id = 1;
// }
entity_t *npc = mapEntityAdd(&testMap); // entity_t *ent = mapEntityAdd(&testMap);
entityInit(npc, ENTITY_TYPE_NPC, &testMap); // entityInit(ent, ENTITY_TYPE_PLAYER, &testMap);
npc->position[0] = 32.0f;
npc->position[1] = 32.0f; // entity_t *npc = mapEntityAdd(&testMap);
// entityInit(npc, ENTITY_TYPE_NPC, &testMap);
// npc->position[0] = 32.0f;
// npc->position[1] = 32.0f;
} }
void rpgUpdate() { void rpgUpdate() {
mapUpdate(&testMap); if(RPG.map != NULL) {
mapUpdate(RPG.map);
}
} }

View File

@@ -6,6 +6,13 @@
*/ */
#pragma once #pragma once
#include "rpg/world/map.h"
typedef struct {
map_t *map;
} rpg_t;
extern rpg_t RPG;
/** /**
* Initializes the RPG subsystem. * Initializes the RPG subsystem.

View File

@@ -13,6 +13,7 @@
#define MAP_WIDTH_MAX 64 #define MAP_WIDTH_MAX 64
#define MAP_HEIGHT_MAX 64 #define MAP_HEIGHT_MAX 64
#define MAP_TILE_COUNT_MAX (MAP_WIDTH_MAX * MAP_HEIGHT_MAX) #define MAP_TILE_COUNT_MAX (MAP_WIDTH_MAX * MAP_HEIGHT_MAX)
#define MAP_LAYER_COUNT_MAX 2
typedef struct { typedef struct {
uint8_t id; uint8_t id;

View File

@@ -2,6 +2,7 @@ import sys, os
from args import inputAssets, args from args import inputAssets, args
from processasset import processAsset from processasset import processAsset
from processpalette import processPaletteList from processpalette import processPaletteList
from processtileset import processTilesetList
from assethelpers import getBuiltAssetsRelativePath from assethelpers import getBuiltAssetsRelativePath
import zipfile import zipfile
@@ -29,6 +30,7 @@ with zipfile.ZipFile(outputFileName, 'w') as zipf:
# Generate additional headers. # Generate additional headers.
processPaletteList() processPaletteList()
processTilesetList()
# Finalize build # Finalize build
if args.build_type == 'header': if args.build_type == 'header':

View File

@@ -109,42 +109,21 @@ def processMap(asset):
# Now we have our layers all parsed out. # Now we have our layers all parsed out.
data = bytearray() data = bytearray()
data += b'drm' # Dusk RPG Map data += b'DRM' # Dusk RPG Map
data += mapWidth.to_bytes(4, 'little') # Map width in tiles data += mapWidth.to_bytes(4, 'little') # Map width in tiles
data += mapHeight.to_bytes(4, 'little') # Map height in tiles data += mapHeight.to_bytes(4, 'little') # Map height in tiles
data += len(tilesets).to_bytes(4, 'little') # Number of tilesets data += len(tilesets).to_bytes(4, 'little') # Number of tilesets
data += len(tileLayers).to_bytes(4, 'little') # Number of layers data += len(tileLayers).to_bytes(4, 'little') # Number of layers
# For each tileset
for tileset in tilesets:
data += tileset['firstGid'].to_bytes(4, 'little') # First GID
# For each layer... # For each layer...
for layer in tileLayers: for layer in tileLayers:
for gid in layer['data']: for gid in layer['data']:
# Get tileset for this gid, since the tilesets are already sorted we can data += gid.to_bytes(4, 'little') # Tileset index
# simply find the first one that has firstGid <= gid
if gid == 0:
# Empty tile
localIndex = 0xFF
tilesetIndex = 0xFFFFFFFF
else:
for tileset in tilesets:
if gid >= tileset['firstGid']:
tilesetIndex = tilesets.index(tileset)
localIndex = gid - tileset['firstGid']
break
else:
# If no tileset was found, this is an invalid gid
print(f"Error: Invalid tile GID {gid} in {asset['path']}")
sys.exit(1)
if localIndex > 255: # For each tileset
print(f"Error: Local tile index {localIndex} exceeds 255 in {asset['path']}") for tileset in tilesets:
sys.exit(1) data += tileset['firstGid'].to_bytes(4, 'little') # First GID
data += tileset['tileset']['tilesetIndex'].to_bytes(4, 'little') # Tileset index
data += tilesetIndex.to_bytes(4, 'little') # Tileset index
data += localIndex.to_bytes(1, 'little') # Local tile index
relative = getAssetRelativePath(asset['path']) relative = getAssetRelativePath(asset['path'])
fileNameWithoutExt = os.path.splitext(os.path.basename(asset['path']))[0] fileNameWithoutExt = os.path.splitext(os.path.basename(asset['path']))[0]

View File

@@ -8,6 +8,8 @@ from args import args
from xml.etree import ElementTree from xml.etree import ElementTree
from assetcache import assetGetCache, assetCache from assetcache import assetGetCache, assetCache
tilesets = []
def loadTilesetFromTSX(asset): def loadTilesetFromTSX(asset):
# Load the TSX file # Load the TSX file
tree = ElementTree.parse(asset['path']) tree = ElementTree.parse(asset['path'])
@@ -138,10 +140,36 @@ def processTileset(asset):
os.makedirs(os.path.dirname(outputFile), exist_ok=True) os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, 'w') as f: with open(outputFile, 'w') as f:
f.write(data) f.write(data)
return assetCache(asset['path'], {
tileset = {
"files": [], "files": [],
"image": tilesetData['image'], "image": tilesetData['image'],
"headerFile": outputFile, "headerFile": os.path.relpath(outputFile, args.headers_dir),
"tilesetName": tilesetName,
"tilesetNameUpper": tilesetNameUpper,
"tilesetIndex": len(tilesets),
"tilesetData": tilesetData,
"files": tilesetData['image']['files'], "files": tilesetData['image']['files'],
}) }
tilesets.append(tileset)
return assetCache(asset['path'], tileset)
def processTilesetList():
data = f"// Tileset List Generated at {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
data += f"#pragma once\n"
for tileset in tilesets:
data += f"#include \"{tileset['headerFile']}\"\n"
data += f"\n"
data += f"#define TILESET_LIST_COUNT {len(tilesets)}\n\n"
data += f"static const tileset_t* TILESET_LIST[TILESET_LIST_COUNT] = {{\n"
for tileset in tilesets:
data += f" &TILESET_{tileset['tilesetNameUpper']},\n"
data += f"}};\n"
# Write header.
outputFile = os.path.join(args.headers_dir, "display", "tileset", f"tilesetlist.h")
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
with open(outputFile, 'w') as f:
f.write(data)