Chunk streaming concurrency, entity slot fix, and map-data-driven spawns
- Allow 2 chunks to be mid-load concurrently instead of 1 (MAP_CHUNK_LOAD_CONCURRENCY). - Fix entitySetChunk silently losing track of an entity when its target chunk's entity slots are full - it now stays detached (and retries later) instead of claiming a chunk that never actually registered it. - DCF format bumped to v5: chunks can now declare entity spawns (global/NPC via the existing entityglobal registry, or one-shot item pickups) and map area triggers, resolved via a new callback-ID registry (mapareagloballist.h) mirroring the entity one. rpg.c's hardcoded TEST entity/item/area spawns are gone - chunk_0_0_0.json now carries that data instead. The player is still bootstrapped in code since it isn't map-authored content.
This commit is contained in:
@@ -14,6 +14,16 @@ JSON input (assetsraw/chunks/chunk_X_Y_Z.json):
|
||||
],
|
||||
"meshes": [
|
||||
{ "file": "house_5_3.dmf", "pos": [x, y, z] }
|
||||
],
|
||||
"entities": [
|
||||
{ "type": "global", "globalId": <int>, "pos": [x, y, z] },
|
||||
{ "type": "item", "itemId": <int>, "quantity": <int>, "pos": [x, y, z] }
|
||||
],
|
||||
"areas": [
|
||||
{
|
||||
"min": [x, y, z], "max": [x, y, z],
|
||||
"callbackId": <int>, "notify": <int>, "trigger": <int>
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -25,10 +35,27 @@ JSON input (assetsraw/chunks/chunk_X_Y_Z.json):
|
||||
Mesh files are located by searching under assets/meshes/ and referenced by
|
||||
path from the assets root in the DCF.
|
||||
|
||||
"entities" spawns things into the world when this chunk loads. A "global"
|
||||
entity is spawned via mapSpawnEntity() - globalId indexes
|
||||
ENTITY_GLOBAL_LIST (src/dusk/rpg/entity/global/entitygloballist.h) and is
|
||||
deduped automatically if already spawned, so it's safe to declare on a
|
||||
chunk that streams in more than once. An "item" entity has no persistent
|
||||
identity - it respawns fresh every time this chunk (re)loads, including
|
||||
after being picked up, since nothing tracks "already collected" yet.
|
||||
itemId is a raw ITEM_ID_* value (see src/dusk/rpg/item/item.json for the
|
||||
name -> id mapping, same convention as the tile "type" ints above).
|
||||
|
||||
"areas" declares map trigger regions (see rpg/overworld/maparea.h) owned
|
||||
by this chunk - they're removed when the chunk unloads and re-added if it
|
||||
streams back in. callbackId indexes MAP_AREA_CALLBACK_LIST
|
||||
(src/dusk/rpg/overworld/global/mapareagloballist.h; 0 is reserved and
|
||||
invalid). notify is bitwise MAP_AREA_NOTIFY_PLAYER(1)|NOTIFY_NPC(2).
|
||||
trigger is bitwise MAP_TRIGGER_STEP(1)|ENTER(2)|EXIT(4).
|
||||
|
||||
Output DCF is derived automatically:
|
||||
assetsraw/chunks/chunk_X_Y_Z.json -> assets/chunks/X_Y_Z.dcf
|
||||
|
||||
Version 4 DCF format (after 8-byte header):
|
||||
Version 5 DCF format (after 8-byte header):
|
||||
tile_t tiles[CHUNK_WIDTH * CHUNK_HEIGHT] (one per x/y column)
|
||||
each tile: uint32_t shape, uint8_t z, 3 padding bytes (8 bytes total,
|
||||
matching the C tile_t struct's layout: { tileshape_t shape; uint8_t z; })
|
||||
@@ -36,6 +63,19 @@ Version 4 DCF format (after 8-byte header):
|
||||
for each model:
|
||||
null-terminated string (relative asset path to .json model)
|
||||
float32[3] (x, y, z offset)
|
||||
uint8_t entitySpawnCount
|
||||
for each entity spawn:
|
||||
uint8_t kind (0 = global entity, 1 = item entity)
|
||||
uint16_t a (globalId if kind 0, itemId if kind 1)
|
||||
uint8_t b (unused if kind 0, quantity if kind 1)
|
||||
int16_t x, y, z (world position, 3 fields)
|
||||
uint8_t areaSpawnCount
|
||||
for each area spawn:
|
||||
int16_t minX, minY, minZ (3 fields)
|
||||
int16_t maxX, maxY, maxZ (3 fields)
|
||||
uint16_t callbackId
|
||||
uint8_t notify
|
||||
uint8_t trigger
|
||||
|
||||
DMF format:
|
||||
Bytes 0-3: DMF\\x00
|
||||
@@ -74,6 +114,11 @@ WORLD_LAYER_HEIGHT = 1.0 / math.sqrt(2)
|
||||
|
||||
CHUNK_MESH_COUNT_MAX = 10
|
||||
CHUNK_MESH_NAME_MAX = 64
|
||||
CHUNK_ENTITY_SPAWN_COUNT_MAX = 8
|
||||
CHUNK_AREA_COUNT_MAX = 4
|
||||
|
||||
ENTITY_SPAWN_KIND_GLOBAL = 0
|
||||
ENTITY_SPAWN_KIND_ITEM = 1
|
||||
|
||||
# Matches sizeof(tile_t) on the C side: uint32_t shape + uint8_t z, padded
|
||||
# to 8 bytes ({ tileshape_t shape; uint8_t z; } with 4-byte enum alignment).
|
||||
@@ -106,7 +151,7 @@ TILE_SHAPE_RAMP_SOUTHWEST_INNER = 13
|
||||
|
||||
FILE_MAGIC = b'DCF'
|
||||
DMF_MAGIC = b'DMF\x00'
|
||||
VERSION_OUT = 4
|
||||
VERSION_OUT = 5
|
||||
DMF_VERSION = 1
|
||||
|
||||
|
||||
@@ -163,11 +208,29 @@ def write_dmf(path, vertex_bytes):
|
||||
print(f' Wrote DMF {path}: {vert_count} vertices, {len(buf)} bytes')
|
||||
|
||||
|
||||
def write_dcf(dcf_path, tiles, mesh_names, mesh_offsets=None):
|
||||
def write_dcf(
|
||||
dcf_path, tiles, mesh_names, mesh_offsets=None,
|
||||
entity_spawns=None, area_spawns=None
|
||||
):
|
||||
"""Write a current-version 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
|
||||
if entity_spawns is None:
|
||||
entity_spawns = []
|
||||
if area_spawns is None:
|
||||
area_spawns = []
|
||||
|
||||
if len(entity_spawns) > CHUNK_ENTITY_SPAWN_COUNT_MAX:
|
||||
raise ValueError(
|
||||
f"Too many entity spawns ({len(entity_spawns)}) - max "
|
||||
f"{CHUNK_ENTITY_SPAWN_COUNT_MAX}"
|
||||
)
|
||||
if len(area_spawns) > CHUNK_AREA_COUNT_MAX:
|
||||
raise ValueError(
|
||||
f"Too many area spawns ({len(area_spawns)}) - max "
|
||||
f"{CHUNK_AREA_COUNT_MAX}"
|
||||
)
|
||||
|
||||
buf = bytearray()
|
||||
buf += FILE_MAGIC
|
||||
@@ -183,11 +246,34 @@ def write_dcf(dcf_path, tiles, mesh_names, mesh_offsets=None):
|
||||
)
|
||||
buf += encoded + b'\x00'
|
||||
buf += struct.pack('<3f', offset[0], offset[1], offset[2])
|
||||
|
||||
buf += struct.pack('<B', len(entity_spawns))
|
||||
for spawn in entity_spawns:
|
||||
kind = spawn['kind']
|
||||
x, y, z = spawn['pos']
|
||||
if kind == ENTITY_SPAWN_KIND_GLOBAL:
|
||||
a, b = spawn['globalId'], 0
|
||||
else:
|
||||
a, b = spawn['itemId'], spawn['quantity']
|
||||
buf += struct.pack('<BHB3h', kind, a, b, x, y, z)
|
||||
|
||||
buf += struct.pack('<B', len(area_spawns))
|
||||
for area in area_spawns:
|
||||
minX, minY, minZ = area['min']
|
||||
maxX, maxY, maxZ = area['max']
|
||||
buf += struct.pack(
|
||||
'<6hHBB',
|
||||
minX, minY, minZ, maxX, maxY, maxZ,
|
||||
area['callbackId'], area['notify'], area['trigger']
|
||||
)
|
||||
|
||||
with open(dcf_path, 'wb') as f:
|
||||
f.write(buf)
|
||||
print(
|
||||
f' Wrote DCF {dcf_path}: '
|
||||
f'version {VERSION_OUT}, {mesh_count} mesh(es), {len(buf)} bytes'
|
||||
f'version {VERSION_OUT}, {mesh_count} mesh(es), '
|
||||
f'{len(entity_spawns)} entity spawn(s), {len(area_spawns)} '
|
||||
f'area(s), {len(buf)} bytes'
|
||||
)
|
||||
|
||||
|
||||
@@ -323,7 +409,39 @@ def from_json(json_path, dcf_path):
|
||||
mesh_offsets.append((float(pos[0]), float(pos[1]), float(pos[2])))
|
||||
print(f' Resolved {filename} -> {rel}')
|
||||
|
||||
write_dcf(dcf_path, bytes(tiles), model_names, mesh_offsets)
|
||||
entity_spawns = []
|
||||
for spawn in data.get('entities', []):
|
||||
pos = tuple(int(v) for v in spawn['pos'])
|
||||
if spawn['type'] == 'global':
|
||||
entity_spawns.append({
|
||||
'kind': ENTITY_SPAWN_KIND_GLOBAL,
|
||||
'globalId': int(spawn['globalId']),
|
||||
'pos': pos,
|
||||
})
|
||||
elif spawn['type'] == 'item':
|
||||
entity_spawns.append({
|
||||
'kind': ENTITY_SPAWN_KIND_ITEM,
|
||||
'itemId': int(spawn['itemId']),
|
||||
'quantity': int(spawn['quantity']),
|
||||
'pos': pos,
|
||||
})
|
||||
else:
|
||||
raise ValueError(f"Unknown entity spawn type: {spawn['type']}")
|
||||
|
||||
area_spawns = []
|
||||
for area in data.get('areas', []):
|
||||
area_spawns.append({
|
||||
'min': tuple(int(v) for v in area['min']),
|
||||
'max': tuple(int(v) for v in area['max']),
|
||||
'callbackId': int(area['callbackId']),
|
||||
'notify': int(area['notify']),
|
||||
'trigger': int(area['trigger']),
|
||||
})
|
||||
|
||||
write_dcf(
|
||||
dcf_path, bytes(tiles), model_names, mesh_offsets,
|
||||
entity_spawns, area_spawns
|
||||
)
|
||||
|
||||
|
||||
def process_json(json_path):
|
||||
|
||||
Reference in New Issue
Block a user