2 Commits

Author SHA1 Message Date
YourWishes a6f449bb93 example building 2 2026-06-30 16:21:47 -05:00
YourWishes 0614bfc446 example building 2026-06-30 16:21:46 -05:00
16 changed files with 2430 additions and 118 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -21,7 +21,7 @@ add_subdirectory(texture)
# Color definitions
dusk_run_python(
dusk_color_defs
tools.color.csv
tools.color
--csv ${CMAKE_CURRENT_SOURCE_DIR}/color.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/display/color.h
)
+1 -1
View File
@@ -14,7 +14,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Input Action Definitions
dusk_run_python(
dusk_input_csv_defs
tools.input.csv
tools.input
--csv ${CMAKE_CURRENT_SOURCE_DIR}/input.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/input/inputactiondefs.h
)
+1 -1
View File
@@ -13,7 +13,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Item Definitions
dusk_run_python(
dusk_item_csv_defs
tools.item.csv
tools.item
--csv ${CMAKE_CURRENT_SOURCE_DIR}/item.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/item/item.h
)
+8 -7
View File
@@ -35,6 +35,7 @@ errorret_t rpgInit(void) {
assertTrue(entIndex != 0xFF, "No available entity slots!.");
entity_t *ent = &ENTITIES[entIndex];
entityInit(ent, ENTITY_TYPE_PLAYER);
ent->position = (worldpos_t){ 10, 2, 0 };
RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
RPG_CAMERA.followEntity.followEntityId = ent->id;
{
@@ -48,7 +49,7 @@ errorret_t rpgInit(void) {
entity_t *npc = &ENTITIES[npcIndex];
entityInit(npc, ENTITY_TYPE_NPC);
npcSetMoveType(npc, NPC_MOVE_TYPE_PATH);
npc->position = (worldpos_t){ 3, 3, 0 };
npc->position = (worldpos_t){ 8, 8, 0 };
npc->interact.type = ENTITY_INTERACT_PRINT;
npc->interact.data.message = "hello world";
{
@@ -58,12 +59,12 @@ errorret_t rpgInit(void) {
if(ci != -1) entitySetChunk(npc, (uint8_t)ci);
}
npcpath_t *path = &npc->data.npc.moveData.path;
path->positions[0] = (worldpos_t){ 3, 3, 0 };
path->positions[1] = (worldpos_t){ 10, 3, 0 };
path->positions[2] = (worldpos_t){ 10, 10, 0 };
path->positions[3] = (worldpos_t){ 3, 10, 0 };
path->count = 4;
// npcpath_t *path = &npc->data.npc.moveData.path;
// path->positions[0] = (worldpos_t){ 3, 3, 0 };
// path->positions[1] = (worldpos_t){ 10, 3, 0 };
// path->positions[2] = (worldpos_t){ 10, 10, 0 };
// path->positions[3] = (worldpos_t){ 3, 10, 0 };
// path->count = 4;
// All Good!
errorOk();
+1 -1
View File
@@ -12,7 +12,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Story Flag Definitions
dusk_run_python(
dusk_story_defs
tools.story.csv
tools.story
--csv ${CMAKE_CURRENT_SOURCE_DIR}/storyflag.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/story/storyflagvalue.h
)
@@ -9,6 +9,8 @@
#include "console/console.h"
#include "assert/assert.h"
#include "display/display.h"
#include "display/displaystate.h"
#include "display/shader/shader.h"
#include "display/screen/screen.h"
#include "display/shader/shaderunlit.h"
@@ -57,6 +59,10 @@ errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
errorret_t sceneOverworldRender(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null");
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
}));
mat4 proj, model, eye;
+229 -107
View File
@@ -4,39 +4,54 @@
# https://opensource.org/licenses/MIT
"""
Converts DCF chunk files (version 1 or 2) to version 3 and generates the
companion DMF (Dusk Mesh Format) files that version 3 references.
Generates DCF + companion DMF files from raw chunk JSON files, or upgrades
legacy DCF files (version 1 or 2) to version 3.
Version 1 format (after 8-byte header + tiles):
uint32_t vertCount
meshvertex_t vertices[vertCount]
JSON input (assetsraw/chunks/chunk_X_Y_Z.json):
{
"tiles": [
{ "pos": [x, y, z], "type": <tile_shape_int>, "tile": <uv_tile_int> }
],
"meshes": [
{ "file": "house_5_3.dmf", "pos": [x, y, z] }
]
}
Version 2 format (after 8-byte header + tiles):
Tiles absent from the array default to TILE_SHAPE_NULL.
Mesh files are located by searching under assets/meshes/ and referenced by
path from the assets root in the DCF.
Output DCF is derived automatically:
assetsraw/chunks/chunk_X_Y_Z.json -> assets/chunks/X_Y_Z.dcf
Version 3 DCF format (after 8-byte header + tiles):
uint8_t meshCount
for each mesh:
uint32_t vertCount
meshvertex_t vertices[vertCount]
Version 3 format (after 8-byte header + tiles):
uint8_t meshCount
for each mesh:
null-terminated string (path to companion .dmf asset)
null-terminated string (relative asset path to .dmf)
float32[3] (x, y, z offset)
DMF format:
Bytes 0-3: DMF\x00
Bytes 0-3: DMF\\x00
Bytes 4-7: uint32_t version = 1 (little-endian)
Bytes 8-11: uint32_t vertCount (little-endian)
Bytes 12+: meshvertex_t vertices[vertCount]
Each vertex: uv[2] + pos[3] = 5 x float32 LE = 20 bytes
Usage:
python3 -m tools.asset.chunk <input.dcf> [output.dcf]
If output is omitted the input file is updated in place.
DMF files are written to assets/meshes/ beside the chunks/ directory.
python3 -m tools.asset.chunk # process all assetsraw/chunks/*.json
python3 -m tools.asset.chunk <file.json> # process one JSON file
python3 -m tools.asset.chunk <file.dcf> # upgrade legacy DCF in-place
"""
import json
import os
import struct
import sys
import os
_HERE = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.normpath(os.path.join(_HERE, '..', '..', '..'))
ASSETSRAW_DIR = os.path.join(PROJECT_ROOT, 'assetsraw')
ASSETS_DIR = os.path.join(PROJECT_ROOT, 'assets')
CHUNK_WIDTH = 16
CHUNK_HEIGHT = 16
@@ -44,62 +59,48 @@ CHUNK_DEPTH = 32
CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH # 8192
CHUNK_MESH_COUNT_MAX = 10
CHUNK_VERTEX_COUNT = 8192
CHUNK_MESH_NAME_MAX = 64
TILE_SIZE = 4
VERTEX_SIZE = 20
FILE_MAGIC = b'DCF'
DMF_MAGIC = b'DMF\x00'
VERSION_OUT = 3
DMF_VERSION = 1
TILE_SHAPE_NULL = 0
TILE_SHAPE_GROUND = 1
FILE_MAGIC = b'DCF'
DMF_MAGIC = b'DMF\x00'
VERSION_OUT = 3
DMF_VERSION = 1
def read_dcf(path):
"""Read a v1 or v2 DCF file. Returns (tiles, meshes) where meshes is a
list of raw vertex byte strings, one per mesh."""
with open(path, 'rb') as f:
data = f.read()
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
if data[:3] != FILE_MAGIC:
raise ValueError(f"{path}: not a DCF file")
def tile_index(x, y, z):
return x + y * CHUNK_WIDTH + z * CHUNK_WIDTH * CHUNK_HEIGHT
version = struct.unpack_from('<I', data, 4)[0]
if version not in (1, 2):
raise ValueError(
f"{path}: expected version 1 or 2, got {version}"
)
offset = 8
tiles_size = CHUNK_TILE_COUNT * TILE_SIZE
tiles = data[offset:offset + tiles_size]
offset += tiles_size
def find_mesh(filename):
"""Search ASSETS_DIR/meshes/ recursively for filename.
Returns the path relative to ASSETS_DIR (forward slashes), or None."""
meshes_root = os.path.join(ASSETS_DIR, 'meshes')
for dirpath, _, filenames in os.walk(meshes_root):
if filename in filenames:
rel = os.path.relpath(
os.path.join(dirpath, filename), ASSETS_DIR
)
return rel.replace('\\', '/')
return None
meshes = []
if version == 1:
vert_count = struct.unpack_from('<I', data, offset)[0]
offset += 4
verts = data[offset:offset + vert_count * VERTEX_SIZE]
if len(verts) != vert_count * VERTEX_SIZE:
raise ValueError(f"{path}: truncated vertex data")
if vert_count > 0:
meshes.append(verts)
else:
mesh_count = data[offset]
offset += 1
for _ in range(mesh_count):
vert_count = struct.unpack_from('<I', data, offset)[0]
offset += 4
verts = data[offset:offset + vert_count * VERTEX_SIZE]
if len(verts) != vert_count * VERTEX_SIZE:
raise ValueError(f"{path}: truncated vertex data")
offset += vert_count * VERTEX_SIZE
if vert_count > 0:
meshes.append(verts)
return tiles, meshes
def derive_dcf_path(json_path):
"""assetsraw/chunks/chunk_X_Y_Z.json -> assets/chunks/X_Y_Z.dcf"""
base = os.path.splitext(os.path.basename(json_path))[0]
if base.startswith('chunk_'):
base = base[len('chunk_'):]
rel_dir = os.path.relpath(os.path.dirname(os.path.abspath(json_path)), ASSETSRAW_DIR)
return os.path.join(ASSETS_DIR, rel_dir, base + '.dcf')
def write_dmf(path, vertex_bytes):
@@ -111,18 +112,11 @@ def write_dmf(path, vertex_bytes):
buf += vertex_bytes
with open(path, 'wb') as f:
f.write(buf)
print(
f' Wrote DMF {path}: '
f'{vert_count} vertices, {len(buf)} bytes'
)
print(f' Wrote DMF {path}: {vert_count} vertices, {len(buf)} bytes')
def write_v3(dcf_path, tiles, mesh_names, mesh_offsets=None):
"""Write a v3 DCF that references the given DMF asset paths.
mesh_offsets is an optional list of (x, y, z) tuples, one per mesh.
Defaults to (0, 0, 0) for each mesh when omitted.
"""
"""Write a v3 DCF referencing the given DMF asset paths."""
mesh_count = len(mesh_names)
if mesh_offsets is None:
mesh_offsets = [(0.0, 0.0, 0.0)] * mesh_count
@@ -145,51 +139,179 @@ def write_v3(dcf_path, tiles, mesh_names, mesh_offsets=None):
f.write(buf)
print(
f' Wrote DCF {dcf_path}: '
f'version {VERSION_OUT}, {mesh_count} mesh(es)'
f'version {VERSION_OUT}, {mesh_count} mesh(es), {len(buf)} bytes'
)
# ---------------------------------------------------------------------------
# JSON -> DCF + DMF
# ---------------------------------------------------------------------------
def build_terrain_verts(tiles_bytes):
"""Generate top-face quads for every TILE_SHAPE_GROUND tile."""
buf = bytearray()
for z in range(CHUNK_DEPTH):
for y in range(CHUNK_HEIGHT):
for x in range(CHUNK_WIDTH):
idx = tile_index(x, y, z)
tile_type = struct.unpack_from(
'<I', tiles_bytes, idx * TILE_SIZE
)[0]
if tile_type != TILE_SHAPE_GROUND:
continue
fx, fy, fz = float(x), float(y), float(z)
uv_u0 = x / CHUNK_WIDTH
uv_u1 = (x + 1) / CHUNK_WIDTH
uv_v0 = y / CHUNK_HEIGHT
uv_v1 = (y + 1) / CHUNK_HEIGHT
for v in [
(uv_u0, uv_v0, fx, fy, fz),
(uv_u1, uv_v0, fx+1, fy, fz),
(uv_u1, uv_v1, fx+1, fy+1, fz),
(uv_u0, uv_v0, fx, fy, fz),
(uv_u1, uv_v1, fx+1, fy+1, fz),
(uv_u0, uv_v1, fx, fy+1, fz),
]:
buf += struct.pack('<5f', *v)
return bytes(buf)
def from_json(json_path, dcf_path):
with open(json_path) as f:
data = json.load(f)
tiles = bytearray(CHUNK_TILE_COUNT * TILE_SIZE)
for tile in data.get('tiles', []):
pos = tile['pos']
x, y, z = int(pos[0]), int(pos[1]), int(pos[2])
struct.pack_into(
'<I', tiles, tile_index(x, y, z) * TILE_SIZE, int(tile['type'])
)
terrain_verts = build_terrain_verts(tiles)
mesh_names = []
mesh_offsets = []
# Terrain mesh (only written if non-empty)
dcf_base = os.path.splitext(os.path.basename(dcf_path))[0]
terrain_dir = os.path.join(ASSETS_DIR, 'meshes', 'chunks')
os.makedirs(terrain_dir, exist_ok=True)
if terrain_verts:
dmf_name = f'chunk_{dcf_base}_0.dmf'
write_dmf(os.path.join(terrain_dir, dmf_name), terrain_verts)
mesh_names.append(f'meshes/chunks/{dmf_name}')
mesh_offsets.append((0.0, 0.0, 0.0))
# External mesh references
for mesh in data.get('meshes', []):
filename = mesh['file']
pos = mesh.get('pos', [0, 0, 0])
rel = find_mesh(filename)
if rel is None:
raise ValueError(f"Mesh not found under assets/meshes/: {filename}")
mesh_names.append(rel)
mesh_offsets.append((float(pos[0]), float(pos[1]), float(pos[2])))
print(f' Resolved {filename} -> {rel}')
write_v3(dcf_path, bytes(tiles), mesh_names, mesh_offsets)
def process_json(json_path):
dcf_path = derive_dcf_path(json_path)
os.makedirs(os.path.dirname(dcf_path), exist_ok=True)
print(f"{json_path} -> {dcf_path}")
from_json(json_path, dcf_path)
# ---------------------------------------------------------------------------
# Legacy DCF (v1/v2) -> v3
# ---------------------------------------------------------------------------
def read_legacy_dcf(path):
with open(path, 'rb') as f:
data = f.read()
if data[:3] != FILE_MAGIC:
raise ValueError(f"{path}: not a DCF file")
version = struct.unpack_from('<I', data, 4)[0]
if version not in (1, 2):
raise ValueError(f"{path}: expected version 1 or 2, got {version}")
offset = 8
tiles_size = CHUNK_TILE_COUNT * TILE_SIZE
tiles = data[offset:offset + tiles_size]
offset += tiles_size
meshes = []
if version == 1:
vert_count = struct.unpack_from('<I', data, offset)[0]
offset += 4
verts = data[offset:offset + vert_count * VERTEX_SIZE]
if len(verts) != vert_count * VERTEX_SIZE:
raise ValueError(f"{path}: truncated vertex data")
if vert_count > 0:
meshes.append(verts)
else:
mesh_count = data[offset]
offset += 1
for _ in range(mesh_count):
vert_count = struct.unpack_from('<I', data, offset)[0]
offset += 4
verts = data[offset:offset + vert_count * VERTEX_SIZE]
if len(verts) != vert_count * VERTEX_SIZE:
raise ValueError(f"{path}: truncated vertex data")
offset += vert_count * VERTEX_SIZE
if vert_count > 0:
meshes.append(verts)
return tiles, meshes
def upgrade_dcf(path):
print(f"Upgrading legacy DCF {path} ...")
tiles, meshes = read_legacy_dcf(path)
print(
f" tiles={CHUNK_TILE_COUNT}, meshes={len(meshes)}, "
f"total_verts={sum(len(m) // VERTEX_SIZE for m in meshes)}"
)
base = os.path.splitext(os.path.basename(path))[0]
terrain_dir = os.path.join(ASSETS_DIR, 'meshes', 'chunks')
os.makedirs(terrain_dir, exist_ok=True)
mesh_names = []
for idx, verts in enumerate(meshes):
dmf_name = f'chunk_{base}_{idx}.dmf'
write_dmf(os.path.join(terrain_dir, dmf_name), verts)
mesh_names.append(f'meshes/chunks/{dmf_name}')
write_v3(path, tiles, mesh_names)
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main():
args = sys.argv[1:]
if not args:
print(
"Usage: python3 -m tools.asset.chunk "
"<input.dcf> [output.dcf]"
chunks_dir = os.path.join(ASSETSRAW_DIR, 'chunks')
if not os.path.isdir(chunks_dir):
print(f"No directory found: {chunks_dir}")
sys.exit(1)
json_files = sorted(
os.path.join(chunks_dir, f)
for f in os.listdir(chunks_dir)
if f.endswith('.json')
)
sys.exit(1)
if not json_files:
print(f"No JSON files found in {chunks_dir}")
sys.exit(0)
for p in json_files:
process_json(p)
return
src = args[0]
dst = args[1] if len(args) > 1 else src
print(f"Reading {src} ...")
tiles, meshes = read_dcf(src)
print(
f" tiles={CHUNK_TILE_COUNT}, "
f"meshes={len(meshes)}, "
f"total_verts="
f"{sum(len(m) // VERTEX_SIZE for m in meshes)}"
)
# Derive chunk base name from the DCF filename (e.g. "0_0_0" from
# "0_0_0.dcf") to name DMF files "chunk_0_0_0_0.dmf" etc.
base = os.path.splitext(os.path.basename(dst))[0]
# assets/meshes/ sits beside assets/chunks/ (one dir up from the DCF).
meshes_dir = os.path.normpath(
os.path.join(os.path.dirname(os.path.abspath(dst)), '..', 'meshes')
)
os.makedirs(meshes_dir, exist_ok=True)
print(f"Writing DMF files to {meshes_dir} ...")
mesh_names = []
for idx, verts in enumerate(meshes):
dmf_filename = f'chunk_{base}_{idx}.dmf'
dmf_path = os.path.join(meshes_dir, dmf_filename)
write_dmf(dmf_path, verts)
mesh_names.append(f'meshes/{dmf_filename}')
print(f"Writing {dst} ...")
write_v3(dst, tiles, mesh_names)
if os.path.splitext(src)[1].lower() == '.json':
process_json(src)
else:
upgrade_dcf(src)
if __name__ == '__main__':