diff --git a/src/LDtk.zig b/src/LDtk.zig index 29daf2e..8fbe9f6 100644 --- a/src/LDtk.zig +++ b/src/LDtk.zig @@ -1,71 +1,85 @@ const std = @import("std"); -const json = struct { - pub fn object(value_opt: ?std.json.Value) ?std.json.ObjectMap { - const value = value_opt orelse return null; - return switch (value) { - .Object => |obj| obj, - else => null, - }; - } +// Utility functions +pub fn object(value_opt: ?std.json.Value) ?std.json.ObjectMap { + const value = value_opt orelse return null; + return switch (value) { + .Object => |obj| obj, + else => null, + }; +} - pub fn array(value_opt: ?std.json.Value) ?std.json.Array { - const value = value_opt orelse return null; - return switch (value) { - .Array => |arr| arr, - else => null, - }; - } +pub fn array(value_opt: ?std.json.Value) ?std.json.Array { + const value = value_opt orelse return null; + return switch (value) { + .Array => |arr| arr, + else => null, + }; +} - pub fn string(value_opt: ?std.json.Value) ?[]const u8 { - const value = value_opt orelse return null; - return switch (value) { - .String => |str| str, - else => null, - }; - } +pub fn string(value_opt: ?std.json.Value) ?[]const u8 { + const value = value_opt orelse return null; + return switch (value) { + .String => |str| str, + else => null, + }; +} - pub fn boolean(value_opt: ?std.json.Value) ?bool { - const value = value_opt orelse return null; - return switch (value) { - .Bool => |b| b, - else => null, - }; +pub fn string_list(alloc: std.mem.Allocator, array_opt: ?std.json.Value) ![][]const u8 { + const arr = array(array_opt) orelse return &[_][]const u8{}; + var list = try std.ArrayList([]const u8).initCapacity(alloc, arr.items.len); + defer list.deinit(); + for (arr.items) |value| { + list.appendAssumeCapacity(string(value) orelse return error.InvalidString); } + return list.toOwnedSlice(); +} - pub fn integer(value_opt: ?std.json.Value) ?i64 { - const value = value_opt orelse return null; - return switch (value) { - .Integer => |int| int, - else => null, - }; - } +pub fn boolean(value_opt: ?std.json.Value) ?bool { + const value = value_opt orelse return null; + return switch (value) { + .Bool => |b| b, + else => null, + }; +} - pub fn enum_from_value(comptime T: type, value_opt: ?std.json.Value) ?T { - const value = value_opt orelse return null; - return switch (value) { - .String => |str| std.meta.stringToEnum(T, str), - else => null, - }; - } +pub fn integer(value_opt: ?std.json.Value) ?i64 { + const value = value_opt orelse return null; + return switch (value) { + .Integer => |int| int, + else => null, + }; +} - pub fn pos_from_value(value_opt: ?std.json.Value) ?[2]i64 { - const value = array(value_opt) orelse return null; - return .{ - integer(value[0]) orelse return null, - integer(value[1]) orelse return null, - }; - } +fn float(value_opt: ?std.json.Value) ?f64 { + const value = value_opt orelse return null; + return switch (value) { + .Float => |float| float, + else => null, + }; +} - pub fn posf_from_value(value_opt: ?std.json.Value) ?[2]i64 { - const value = array(value_opt) orelse return null; - return .{ - float(value[0]) orelse return null, - float(value[1]) orelse return null, - }; - } -}; +pub fn enum_from_value(comptime T: type, value_opt: ?std.json.Value) ?T { + const value = value_opt orelse return null; + return switch (value) { + .String => |str| std.meta.stringToEnum(T, str), + else => null, + }; +} +pub fn pos_from_value(value_opt: ?std.json.Value) ?[2]i64 { + const value = array(value_opt) orelse return null; + const x = integer(value.items[0]) orelse return null; + const y = integer(value.items[1]) orelse return null; + return [2]i64{ x, y }; +} + +pub fn posf_from_value(value_opt: ?std.json.Value) ?[2]f64 { + const value = array(value_opt) orelse return null; + const x = float(value.items[0]) orelse return null; + const y = float(value.items[1]) orelse return null; + return [2]f64{ x, y }; +} /// 1. LDtk Json root pub const Root = struct { @@ -167,21 +181,23 @@ pub const LayerInstance = struct { pub fn fromJSON(alloc: std.mem.Allocator, layer_value: std.json.Value) !LayerInstance { const layer_obj = object(layer_value) orelse return error.InvalidLayer; - const __type = enum_from_value(LayerType, layer.get("__type")) orelse return error.InvalidType; + const __type = enum_from_value(LayerType, layer_obj.get("__type")) orelse return error.InvalidType; const grid = grid: { - if (__type == .IntGrid and array(layer.get("intGridCsv"))) |intGridCsv| grid: { - const grid = std.ArrayList(i64).initCapacity(alloc, intGridCsv.items.len); - defer grid.deinit(); + if (__type == .IntGrid) { + if (array(layer_obj.get("intGridCsv"))) |intGridCsv| { + var grid_list = try std.ArrayList(i64).initCapacity(alloc, intGridCsv.items.len); + defer grid_list.deinit(); - for (grid) |int| { - grid.appendAssumeCapacity(integer(int) orelse return error.InvalidInt); + for (intGridCsv.items) |int| { + grid_list.appendAssumeCapacity(integer(int) orelse return error.InvalidInt); + } + + break :grid grid_list.toOwnedSlice(); } - - break :grid grid.toOwnedSlice(); } break :grid &[0]i64{}; }; - return LayerInstance { + return LayerInstance{ .__cHei = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, .__cWid = integer(layer_obj.get("__cWid")) orelse return error.InvalidCWid, .__gridSize = integer(layer_obj.get("__gridSize")) orelse return error.InvalidGridSize, @@ -190,13 +206,14 @@ pub const LayerInstance = struct { .__pxTotalOffsetX = integer(layer_obj.get("__pxTotalOffsetX")) orelse return error.InvalidTotalOffsetX, .__pxTotalOffsetY = integer(layer_obj.get("__pxTotalOffsetY")) orelse return error.InvalidTotalOffsetY, .__tilesetDefUid = integer(layer_obj.get("__tilesetDefUid")) orelse return error.InvalidTilesetDefUid, - .__tilesetRelPath = integer(layer_obj.get("__tilesetRelPath")) orelse return error.InvalidTilesetRelPath, + .__tilesetRelPath = string(layer_obj.get("__tilesetRelPath")) orelse return error.InvalidTilesetRelPath, .__type = __type, - .autoLayerTiles = try TileInstance.fromJSON(alloc, layer_obj.get("autoLayerTiles")), - .entityInstances = try EntityInstance.fromJSON(alloc, layer_obj.get("entityInstances")), - .gridTiles = try TileInstance.fromJSON(alloc, layer_obj.get("gridTiles")), + .autoLayerTiles = try TileInstance.fromJSONMany(alloc, layer_obj.get("autoLayerTiles")), + .entityInstances = try EntityInstance.fromJSONMany(alloc, layer_obj.get("entityInstances")), + .gridTiles = try TileInstance.fromJSONMany(alloc, layer_obj.get("gridTiles")), .iid = string(layer_obj.get("iid")) orelse return error.InvalidIID, .intGridCsv = grid, + .layerDefUid = integer(layer_obj.get("layerDefUid")) orelse return error.InvalidLayerDefUid, .levelId = integer(layer_obj.get("levelId")) orelse return error.InvalidLevelId, .overrideTilesetUid = integer(layer_obj.get("overrideTilesetUid")) orelse return error.InvalidOverrideTilesetUid, .pxOffsetX = integer(layer_obj.get("pxOffsetX")) orelse return error.InvalidPxOffsetX, @@ -205,11 +222,12 @@ pub const LayerInstance = struct { }; } - pub fn fromJSONMany(alloc: std.mem.Allocator, layers: std.json.Array) ![]LayerInstance { + pub fn fromJSONMany(alloc: std.mem.Allocator, layers_opt: ?std.json.Value) ![]LayerInstance { + const layers = array(layers_opt) orelse return &[_]LayerInstance{}; var ldtk_layers = try std.ArrayList(LayerInstance).initCapacity(alloc, layers.items.len); defer ldtk_layers.deinit(); // levels will be returned using toOwnedSlice for (layers.items) |layer_value| { - ldtk_layers.appendAssumeCapacity(try fromJSON(layer_value)); + ldtk_layers.appendAssumeCapacity(try fromJSON(alloc, layer_value)); } return ldtk_layers.toOwnedSlice(); } @@ -235,7 +253,8 @@ const TileInstance = struct { /// The Tile ID in the corresponding tileset t: i64, - pub fn fromJSON(tile: std.json.Value) !TileInstance { + pub fn fromJSON(tile_opt: ?std.json.Value) !TileInstance { + const tile = object(tile_opt) orelse return error.InvalidTileInstance; const f = @intToEnum(FlipBits, integer(tile.get("f")) orelse return error.InvalidFlipBits); const px = pos_from_value(tile.get("px")) orelse return error.InvalidPx; const src = pos_from_value(tile.get("src")) orelse return error.InvalidSrc; @@ -247,6 +266,16 @@ const TileInstance = struct { .t = t, }; } + + pub fn fromJSONMany(alloc: std.mem.Allocator, tiles_opt: ?std.json.Value) ![]TileInstance { + const tiles = array(tiles_opt) orelse return &[_]TileInstance{}; + var ldtk_tiles = try std.ArrayList(TileInstance).initCapacity(alloc, tiles.items.len); + defer ldtk_tiles.deinit(); // levels will be returned using toOwnedSlice + for (tiles.items) |tile_value| { + ldtk_tiles.appendAssumeCapacity(try fromJSON(tile_value)); + } + return ldtk_tiles.toOwnedSlice(); + } }; const FlipBits = enum(u4) { @@ -260,7 +289,7 @@ const FlipBits = enum(u4) { const EntityInstance = struct { __grid: [2]i64, __identifier: []const u8, - __pivot: [2]f32, + __pivot: [2]f64, __smartColor: []const u8, __tags: [][]const u8, __tile: ?TilesetRectangle, @@ -271,19 +300,20 @@ const EntityInstance = struct { px: [2]i64, width: i64, - pub fn fromJSON(alloc: std.mem.Allocator, entity: std.json.Value) !TileInstance { - const __grid = pos_from_value(tile.get("__grid")) orelse return error.InvalidGrid; - const __identifier = string(tile.get("__identifier")) orelse return error.InvalidIdentifier; - const __pivot = posf_from_value(tile.get("__pivot")) orelse return error.InvalidPivot; - const __smartColor = string(tile.get("__smartColor")) orelse return error.InvalidSmartColor; - const __tags = pos_from_value(tile.get("__tags")) orelse return error.InvalidTags; - const __tile: ?[]TilesetRectangle = null; // TilesetRectangle.fromJSON(); - const defUid = integer(tile.get("defUid")) orelse return error.InvalidDefUid; - const fieldInstances = try FieldInstance.fromJSON(alloc); - const height = integer(tile.get("height")) orelse return error.InvalidHeight; - const iid = string(tile.get("iid")) orelse return error.InvalidIid; - const px = pos_from_value(tile.get("px")) orelse return error.InvalidPx; - const width = integer(tile.get("width")) orelse return error.InvalidWidth; + pub fn fromJSON(alloc: std.mem.Allocator, entity_opt: std.json.Value) !EntityInstance { + const entity = object(entity_opt) orelse return error.InvalidEntityInstance; + const __grid = pos_from_value(entity.get("__grid")) orelse return error.InvalidGrid; + const __identifier = string(entity.get("__identifier")) orelse return error.InvalidIdentifier; + const __pivot = posf_from_value(entity.get("__pivot")) orelse return error.InvalidPivot; + const __smartColor = string(entity.get("__smartColor")) orelse return error.InvalidSmartColor; + const __tags = try string_list(alloc, entity.get("__tags")); + const __tile = try TilesetRectangle.fromJSON(entity.get("__tile")); + const defUid = integer(entity.get("defUid")) orelse return error.InvalidDefUid; + const fieldInstances = try FieldInstance.fromJSONMany(alloc, entity.get("fieldInstances")); + const height = integer(entity.get("height")) orelse return error.InvalidHeight; + const iid = string(entity.get("iid")) orelse return error.InvalidIid; + const px = pos_from_value(entity.get("px")) orelse return error.InvalidPx; + const width = integer(entity.get("width")) orelse return error.InvalidWidth; return EntityInstance{ .__grid = __grid, @@ -300,6 +330,16 @@ const EntityInstance = struct { .width = width, }; } + + pub fn fromJSONMany(alloc: std.mem.Allocator, entities_opt: ?std.json.Value) ![]EntityInstance { + const entities = array(entities_opt) orelse return &[_]EntityInstance{}; + var ldtk_entities = try std.ArrayList(EntityInstance).initCapacity(alloc, entities.items.len); + defer ldtk_entities.deinit(); // levels will be returned using toOwnedSlice + for (entities.items) |entity_value| { + ldtk_entities.appendAssumeCapacity(try fromJSON(alloc, entity_value)); + } + return ldtk_entities.toOwnedSlice(); + } }; /// 2.4. Field Instance @@ -312,19 +352,33 @@ pub const FieldInstance = struct { __value: []const u8, defUid: i64, - pub fn fromJSON(alloc: std.mem.Allocator, field: std.json.Value) !FieldInstance { + pub fn fromJSON(field_value: ?std.json.Value) !?FieldInstance { + const field = object(field_value) orelse return error.InvalidFieldInstance; const __identifier = string(field.get("__identifier")) orelse return error.InvalidIdentifier; - const __tile = string(field.get("__tile")) orelse return error.InvalidTile; + const __tile = try TilesetRectangle.fromJSON(field.get("__tile")); const __type = string(field.get("__type")) orelse return error.InvalidType; const __value = string(field.get("__value")) orelse return error.InvalidValue; const defUid = integer(field.get("defUid")) orelse return error.InvalidIDefUid; - return FieldInstance { + return FieldInstance{ .__identifier = __identifier, .__tile = __tile, .__type = __type, .__value = __value, + .defUid = defUid, }; } + + pub fn fromJSONMany(alloc: std.mem.Allocator, fields_opt: ?std.json.Value) ![]FieldInstance { + const fields = array(fields_opt) orelse return &[_]FieldInstance{}; + var ldtk_fields = try std.ArrayList(FieldInstance).initCapacity(alloc, fields.items.len); + defer ldtk_fields.deinit(); // levels will be returned using toOwnedSlice + for (fields.items) |field_value| { + if (try fromJSON(field_value)) |field| { + ldtk_fields.appendAssumeCapacity(field); + } + } + return ldtk_fields.toOwnedSlice(); + } }; const FieldType = union(enum) { @@ -418,12 +472,23 @@ const EntityDefinition = struct { const FieldDefinition = []const u8; /// 3.2.2. Tileset rectangle -const TilesetRectangle = struct { - h: i64, +pub const TilesetRectangle = struct { tilesetUid: i64, w: i64, + h: i64, x: i64, y: i64, + + pub fn fromJSON(tile_rect_opt: ?std.json.Value) !TilesetRectangle { + const tile_rect = object(tile_rect_opt) orelse return error.InvalidTileRect; + return TilesetRectangle{ + .tilesetUid = integer(tile_rect.get("tilesetUid")) orelse return error.InvalidTilesetUid, + .w = integer(tile_rect.get("w")) orelse return error.InvalidW, + .h = integer(tile_rect.get("h")) orelse return error.InvalidH, + .x = integer(tile_rect.get("x")) orelse return error.InvalidX, + .y = integer(tile_rect.get("y")) orelse return error.InvalidY, + }; + } }; /// 3.3. Tileset definition diff --git a/src/main.zig b/src/main.zig index 615f3e1..d43bec8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -25,7 +25,7 @@ test "load default/empty ldtk file" { const ldtk_levels = try extract_levels(testing.allocator, levels); defer testing.allocator.free(ldtk_levels); - var ldtk_root = LDtk.Root { + var ldtk_root = LDtk.Root{ .bgColor = string(root.get("bgColor")) orelse return error.InvalidBGColor, // .defs = ldtk_defs, .externalLevels = boolean(root.get("externalLevels")) orelse return error.InvalidExternalLevels, @@ -76,6 +76,7 @@ pub fn extract_levels(alloc: std.mem.Allocator, levels: std.json.Array) ![]LDtk. defer ldtk_levels.deinit(); // levels will be returned using toOwnedSlice for (levels.items) |level_value| { const level_obj = object(level_value) orelse return error.InvalidLevel; + const layer_instances = if (level_obj.get("layerInstances")) |layerInstances| try LDtk.LayerInstance.fromJSONMany(alloc, layerInstances) else null; ldtk_levels.appendAssumeCapacity(.{ .__bgColor = string(level_obj.get("__bgColor")), // TODO @@ -88,8 +89,7 @@ pub fn extract_levels(alloc: std.mem.Allocator, levels: std.json.Array) ![]LDtk. .fieldInstances = &[_]LDtk.FieldInstance{}, .identifier = string(level_obj.get("identifier")) orelse return error.InvalidIdentifier, .iid = string(level_obj.get("iid")) orelse return error.InvalidIID, - // TODO - .layerInstances = null, + .layerInstances = layer_instances, .pxHei = integer(level_obj.get("pxHei")) orelse return error.InvalidPxHei, .pxWid = integer(level_obj.get("pxWid")) orelse return error.InvalidPxWid, .uid = integer(level_obj.get("uid")) orelse return error.InvalidUID, @@ -101,41 +101,41 @@ pub fn extract_levels(alloc: std.mem.Allocator, levels: std.json.Array) ![]LDtk. return ldtk_levels.toOwnedSlice(); } -pub fn extract_layers(alloc: std.mem.Allocator, layers: std.json.Array) ![]LDtk.LayerInstance { - var ldtk_layers = try std.ArrayList(LDtk.LayerInstance).initCapacity(alloc, layers.items.len); - defer ldtk_layers.deinit(); // levels will be returned using toOwnedSlice - for (layers.items) |layer_value| { - const layer_obj = object(layer_value) orelse return error.InvalidLayer; - const __type = enum_from_value(LDtk.LayerType, layer_obj.get("__type")) orelse return error.InvalidType; - const autoLayerTiles = if (__type == .AutoLayer) {} else null; - const entityInstances = if (__type == .Entities) {} else null; - const gridTiles = if (__type == .Tiles) {} else null; - const intGridCsv = if (__type == .IntGrid) {} else null; - ldtk_layers.appendAssumeCapacity(.{ - .__cHei = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, - .__cWid = integer(layer_obj.get("__cWid")) orelse return error.InvalidCWid, - .__gridSize = integer(layer_obj.get("__gridSize")) orelse return error.InvalidGridSize, - .__identifier = string(layer_obj.get("__identifier")) orelse return error.InvalidIdentifier, - .__opacity = float(layer_obj.get("__opacity")) orelse return error.InvalidOpacity, - .__pxTotalOffsetX = integer(layer_obj.get("__pxTotalOffsetX")) orelse return error.InvalidTotalOffsetX, - .__pxTotalOffsetY = integer(layer_obj.get("__pxTotalOffsetY")) orelse return error.InvalidTotalOffsetY, - .__tilesetDefUid = integer(layer_obj.get("__tilesetDefUid")) orelse return error.InvalidTilesetDefUid, - .__tilesetRelPath = integer(layer_obj.get("__tilesetRelPath")) orelse return error.InvalidTilesetRelPath, - .__type = __type, - .autoLayerTiles = autoLayerTiles, - .entityInstances = entityInstances, - .gridTiles = gridTiles, - .iid = string(layer_obj.get("iid")) orelse return error.InvalidIID, - .intGridCsv = integer(layer_obj.get("intGridCsv")) orelse return error.InvalidGridCsv, - .levelId = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, - .overrideTilesetUid = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, - .pxOffsetX = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, - .pxOffsetY = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, - .visible = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, - }); - } - return ldtk_layers.toOwnedSlice(); -} +// pub fn extract_layers(alloc: std.mem.Allocator, layers: std.json.Array) ![]LDtk.LayerInstance { +// var ldtk_layers = try std.ArrayList(LDtk.LayerInstance).initCapacity(alloc, layers.items.len); +// defer ldtk_layers.deinit(); // levels will be returned using toOwnedSlice +// for (layers.items) |layer_value| { +// const layer_obj = object(layer_value) orelse return error.InvalidLayer; +// const __type = enum_from_value(LDtk.LayerType, layer_obj.get("__type")) orelse return error.InvalidType; +// const autoLayerTiles = if (__type == .AutoLayer) {} else null; +// const entityInstances = if (__type == .Entities) {} else null; +// const gridTiles = if (__type == .Tiles) {} else null; +// const intGridCsv = if (__type == .IntGrid) {} else null; +// ldtk_layers.appendAssumeCapacity(.{ +// .__cHei = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, +// .__cWid = integer(layer_obj.get("__cWid")) orelse return error.InvalidCWid, +// .__gridSize = integer(layer_obj.get("__gridSize")) orelse return error.InvalidGridSize, +// .__identifier = string(layer_obj.get("__identifier")) orelse return error.InvalidIdentifier, +// .__opacity = float(layer_obj.get("__opacity")) orelse return error.InvalidOpacity, +// .__pxTotalOffsetX = integer(layer_obj.get("__pxTotalOffsetX")) orelse return error.InvalidTotalOffsetX, +// .__pxTotalOffsetY = integer(layer_obj.get("__pxTotalOffsetY")) orelse return error.InvalidTotalOffsetY, +// .__tilesetDefUid = integer(layer_obj.get("__tilesetDefUid")) orelse return error.InvalidTilesetDefUid, +// .__tilesetRelPath = integer(layer_obj.get("__tilesetRelPath")) orelse return error.InvalidTilesetRelPath, +// .__type = __type, +// .autoLayerTiles = autoLayerTiles, +// .entityInstances = entityInstances, +// .gridTiles = gridTiles, +// .iid = string(layer_obj.get("iid")) orelse return error.InvalidIID, +// .intGridCsv = integer(layer_obj.get("intGridCsv")) orelse return error.InvalidGridCsv, +// .levelId = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, +// .overrideTilesetUid = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, +// .pxOffsetX = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, +// .pxOffsetY = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, +// .visible = integer(layer_obj.get("__cHei")) orelse return error.InvalidCHei, +// }); +// } +// return ldtk_layers.toOwnedSlice(); +// } fn object(value_opt: ?std.json.Value) ?std.json.ObjectMap { const value = value_opt orelse return null;