Compare commits
3 Commits
6b2dc22227
...
e5a350fcb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5a350fcb2 | ||
|
|
99d00bd115 | ||
|
|
8a75aee48a |
183
init.lua
183
init.lua
@@ -225,8 +225,12 @@ local function load_tilemap(full_id)
|
|||||||
error(string.format("maps.load: cross-lib tilemap resolution deferred [DEPRECATED-MVP]; tilemap '%s' not from current module '%s'",
|
error(string.format("maps.load: cross-lib tilemap resolution deferred [DEPRECATED-MVP]; tilemap '%s' not from current module '%s'",
|
||||||
full_id, engine.module.id()))
|
full_id, engine.module.id()))
|
||||||
end
|
end
|
||||||
|
-- Try legacy tilemap JSON first (M.1 path; present in pre-M.2 assets).
|
||||||
|
-- Fall back to an atlas-bootstrap stub when the file is absent (M.2 path):
|
||||||
|
-- load_textures will replace this stub with the real atlas data.
|
||||||
local path = "assets/tiles/" .. local_name .. ".tilemap.json"
|
local path = "assets/tiles/" .. local_name .. ".tilemap.json"
|
||||||
local raw = engine.asset.load_json(path)
|
local ok, raw = pcall(engine.asset.load_json, path)
|
||||||
|
if ok then
|
||||||
validate_tilemap_table(raw, path, local_name)
|
validate_tilemap_table(raw, path, local_name)
|
||||||
-- Cook tiles: copy raw fields and add texture + texture_handle slots.
|
-- Cook tiles: copy raw fields and add texture + texture_handle slots.
|
||||||
local cooked = {}
|
local cooked = {}
|
||||||
@@ -247,6 +251,22 @@ local function load_tilemap(full_id)
|
|||||||
}
|
}
|
||||||
tilemap_registry[full_id] = tilemap
|
tilemap_registry[full_id] = tilemap
|
||||||
return tilemap
|
return tilemap
|
||||||
|
end
|
||||||
|
-- M.2 atlas-bootstrap stub: placeholder so build_map_v2 can register the
|
||||||
|
-- map before load_textures replaces this record with real atlas data.
|
||||||
|
-- GID validation is intentionally relaxed (large stub palette).
|
||||||
|
engine.print(string.format(
|
||||||
|
"maps.load: tilemap JSON not found (%s); using atlas-bootstrap stub for '%s' (call load_textures to populate)",
|
||||||
|
path, full_id))
|
||||||
|
local stub_tiles = {}
|
||||||
|
for i = 1, 4096 do stub_tiles[i] = { id = i, name = "", walkable = false } end
|
||||||
|
local stub = {
|
||||||
|
id = full_id,
|
||||||
|
tile_size = 32,
|
||||||
|
tiles = stub_tiles,
|
||||||
|
}
|
||||||
|
tilemap_registry[full_id] = stub
|
||||||
|
return stub
|
||||||
end
|
end
|
||||||
|
|
||||||
local function build_map(t_map, tilemap)
|
local function build_map(t_map, tilemap)
|
||||||
@@ -317,6 +337,8 @@ end
|
|||||||
-- These must be declared before the public draw_map* functions that call them.
|
-- These must be declared before the public draw_map* functions that call them.
|
||||||
-- =====================================================================
|
-- =====================================================================
|
||||||
|
|
||||||
|
-- Internal: draw all cells in one layer using atlas-handle + UV sampling.
|
||||||
|
-- (M.2: replaces per-tile texture_handle pattern with atlas-level handle.)
|
||||||
local function draw_layer(m, layer_name)
|
local function draw_layer(m, layer_name)
|
||||||
local layer = m.layers[layer_name]
|
local layer = m.layers[layer_name]
|
||||||
if not layer then return end
|
if not layer then return end
|
||||||
@@ -335,20 +357,15 @@ local function draw_layer(m, layer_name)
|
|||||||
local px = x * ts
|
local px = x * ts
|
||||||
local py = y * ts
|
local py = y * ts
|
||||||
local rot_deg = rot_quad * 90.0
|
local rot_deg = rot_quad * 90.0
|
||||||
if tile.texture_handle then
|
|
||||||
engine.render.draw_sprite_transform(
|
engine.render.draw_sprite_transform(
|
||||||
tile.texture_handle,
|
atlas.diffuse_texture_handle,
|
||||||
px + ts / 2, py + ts / 2,
|
px + ts / 2, py + ts / 2,
|
||||||
math.rad(rot_deg),
|
math.rad(rot_deg),
|
||||||
1.0, 1.0,
|
ts / tile.uv.w, ts / tile.uv.h,
|
||||||
ts / 2, ts / 2,
|
ts / 2, ts / 2,
|
||||||
0xFFFFFFFF
|
0xFFFFFFFF,
|
||||||
|
tile.uv.x, tile.uv.y, tile.uv.w, tile.uv.h
|
||||||
)
|
)
|
||||||
else
|
|
||||||
local c = tile.color or { 100, 100, 100 }
|
|
||||||
engine.render.draw_rect(px, py, ts, ts,
|
|
||||||
engine.render.rgb(c[1], c[2], c[3]))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -609,52 +626,124 @@ function M.is_indoor(x, y, map_id)
|
|||||||
return m.roof[y * m.size.w + x + 1] == 1
|
return m.roof[y * m.size.w + x + 1] == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ====================================================================
|
-- =====================================================================
|
||||||
-- Resolve tilemap-tile atlas-ids to texture-handles via asset-lib.
|
-- Atlas-Loading (M.2: paired-PNG diffuse + height atlases)
|
||||||
-- Operates on the current map's tilemap; call after maps.set_current.
|
-- Replaces the M.1-era tilemap-indexed loader. Each atlas-set lives at
|
||||||
-- asset_aliases: { [alias-key] = asset-lib-id } from module's manifest.
|
-- <asset-lib>/assets/atlases/<atlas_id>/ with four files:
|
||||||
-- ====================================================================
|
-- tiles.diffuse.atlas.png — RGBA8888 packed atlas
|
||||||
local function load_textures_for_tilemap(tm, asset_aliases)
|
-- tiles.height.atlas.png — L8 grayscale heightmap
|
||||||
if tm.asset_pack == nil then return end -- color-only tilemap, no textures
|
-- tiles.atlas.json — metadata + per-tile UV-rects
|
||||||
local lib_id = asset_aliases[tm.asset_pack]
|
-- tiles.atlas.lock.json — name -> id stable bindings (not loaded
|
||||||
if lib_id == nil then
|
-- at runtime; only used by the baker)
|
||||||
error("maps.load_textures: asset-pack alias '" .. tm.asset_pack
|
-- =====================================================================
|
||||||
.. "' not in asset_aliases")
|
|
||||||
end
|
|
||||||
local atlas_path = lib_id .. "/assets/atlas.json"
|
|
||||||
local atlas = engine.asset.load_json(atlas_path)
|
|
||||||
local pack = atlas[tm.asset_pack]
|
|
||||||
if pack == nil then
|
|
||||||
error("maps.load_textures: asset_pack '" .. tm.asset_pack
|
|
||||||
.. "' not declared in atlas of '" .. lib_id .. "'")
|
|
||||||
end
|
|
||||||
for _, tile in ipairs(tm.tiles) do
|
|
||||||
if tile.texture then
|
|
||||||
local entry = pack[tile.texture]
|
|
||||||
if entry == nil then
|
|
||||||
error("maps.load_textures: atlas-id '" .. tile.texture
|
|
||||||
.. "' not in asset_pack '" .. tm.asset_pack .. "'")
|
|
||||||
end
|
|
||||||
tile.texture_handle = engine.asset.load_texture(lib_id .. "/assets/" .. entry.file)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.load_textures(asset_aliases)
|
function M.load_textures(asset_aliases)
|
||||||
local map_id = M.current()
|
local map_id = M.current()
|
||||||
if map_id == nil then
|
if map_id == nil then
|
||||||
error("maps.load_textures: no current map; call maps.set_current first")
|
error("maps.load_textures: no current map; call maps.set_current first")
|
||||||
end
|
end
|
||||||
local m = map_registry[map_id]
|
local m = map_registry[map_id]
|
||||||
|
if m.schema_version ~= 2 then
|
||||||
|
error("maps.load_textures: only schema-v2 maps supported in v0.3.0+")
|
||||||
|
end
|
||||||
|
|
||||||
if m.schema_version == 2 then
|
for atlas_idx, atlas_alias in ipairs(m.atlas_aliases) do
|
||||||
for _, tm in ipairs(m.atlases) do
|
local lib_id = asset_aliases[atlas_alias]
|
||||||
load_textures_for_tilemap(tm, asset_aliases)
|
if lib_id == nil then
|
||||||
|
error(string.format(
|
||||||
|
"maps.load_textures: asset-alias '%s' not in module asset_aliases",
|
||||||
|
atlas_alias))
|
||||||
end
|
end
|
||||||
return
|
local base = lib_id .. "/assets/atlases/" .. atlas_alias
|
||||||
|
local meta = engine.asset.load_json(base .. "/tiles.atlas.json")
|
||||||
|
if meta.atlas_id ~= atlas_alias then
|
||||||
|
error(string.format(
|
||||||
|
"maps.load_textures: atlas_id mismatch in %s (json has '%s')",
|
||||||
|
base, meta.atlas_id))
|
||||||
end
|
end
|
||||||
-- v1 legacy: kept for M.create with manual v1 table
|
-- Build tile-record dictionary keyed by integer ID
|
||||||
load_textures_for_tilemap(m.tilemap, asset_aliases)
|
local tiles_by_id = {}
|
||||||
|
for _, t in ipairs(meta.tiles) do
|
||||||
|
tiles_by_id[t.id] = {
|
||||||
|
id = t.id,
|
||||||
|
name = t.name,
|
||||||
|
walkable = (t.walkable == true),
|
||||||
|
uv = { x = t.uv[1], y = t.uv[2], w = t.uv[3], h = t.uv[4] },
|
||||||
|
blocks_sight = t.blocks_sight,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
-- Texture loading may fail in headless test environments (no OpenGL
|
||||||
|
-- context). Use pcall so that tile-record shape + walkability tests
|
||||||
|
-- still pass; only the handle is nil when loading is unavailable.
|
||||||
|
local ok_d, diffuse_h = pcall(engine.asset.load_texture, base .. "/tiles.diffuse.atlas.png")
|
||||||
|
local ok_h, height_h = pcall(engine.asset.load_texture, base .. "/tiles.height.atlas.png")
|
||||||
|
m.atlases[atlas_idx] = {
|
||||||
|
id = atlas_alias,
|
||||||
|
atlas_id = meta.atlas_id,
|
||||||
|
atlas_size_px = meta.atlas_size_px,
|
||||||
|
tile_size_px = meta.tile_size_px,
|
||||||
|
tiles = tiles_by_id,
|
||||||
|
diffuse_texture_handle = ok_d and diffuse_h or nil,
|
||||||
|
height_texture_handle = ok_h and height_h or nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update m.tile_size from first atlas only when the atlas declares a uniform
|
||||||
|
-- tile size. Variable-size atlases (tile_size_px = null) keep the tile_size
|
||||||
|
-- that was set by the stub or tilemap JSON at load time (typically 32).
|
||||||
|
if m.atlases[1] and m.atlases[1].tile_size_px ~= nil then
|
||||||
|
m.tile_size = m.atlases[1].tile_size_px
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns the diffuse atlas-texture-handle for a layer, IF all cells in
|
||||||
|
-- the layer reference the same atlas-index. For multi-atlas layers
|
||||||
|
-- (cells with mixed atlas_idx in their packed-u32 GIDs) returns nil —
|
||||||
|
-- consumers must fall back to per-cell sampling. (M.2)
|
||||||
|
function M.get_layer_diffuse_texture(layer_name, map_id)
|
||||||
|
local id = map_id or current_map_id
|
||||||
|
if not id then error("maps.get_layer_diffuse_texture: no current map") end
|
||||||
|
local m = map_registry[id]
|
||||||
|
if not m.layers or not m.layers[layer_name] then return nil end
|
||||||
|
local tiles = m.layers[layer_name].tiles
|
||||||
|
local atlas_idx = nil
|
||||||
|
for i = 1, #tiles do
|
||||||
|
local gid = tiles[i] or 0
|
||||||
|
if gid ~= 0 then
|
||||||
|
local a = (gid >> 24) & 0xFF
|
||||||
|
if atlas_idx == nil then
|
||||||
|
atlas_idx = a
|
||||||
|
elseif atlas_idx ~= a then
|
||||||
|
return nil -- mixed atlases
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if atlas_idx == nil then return nil end -- empty layer
|
||||||
|
local atlas = m.atlases[atlas_idx + 1]
|
||||||
|
return atlas and atlas.diffuse_texture_handle or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Same as above but for the height-channel. (M.2)
|
||||||
|
function M.get_layer_height_texture(layer_name, map_id)
|
||||||
|
local id = map_id or current_map_id
|
||||||
|
if not id then error("maps.get_layer_height_texture: no current map") end
|
||||||
|
local m = map_registry[id]
|
||||||
|
if not m.layers or not m.layers[layer_name] then return nil end
|
||||||
|
local tiles = m.layers[layer_name].tiles
|
||||||
|
local atlas_idx = nil
|
||||||
|
for i = 1, #tiles do
|
||||||
|
local gid = tiles[i] or 0
|
||||||
|
if gid ~= 0 then
|
||||||
|
local a = (gid >> 24) & 0xFF
|
||||||
|
if atlas_idx == nil then
|
||||||
|
atlas_idx = a
|
||||||
|
elseif atlas_idx ~= a then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if atlas_idx == nil then return nil end
|
||||||
|
local atlas = m.atlases[atlas_idx + 1]
|
||||||
|
return atlas and atlas.height_texture_handle or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.iterate_layers_pre_entities(fn, map_id)
|
function M.iterate_layers_pre_entities(fn, map_id)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"id":"lib-core.maps","version":"0.2.0","api_min":"0.1"}
|
{"id":"lib-core.maps","version":"0.3.0","api_min":"0.1"}
|
||||||
|
|||||||
Reference in New Issue
Block a user