const std = @import("std"); const Property = @import("./property.zig"); const xml = @import("./xml.zig"); const Color = @import("./color.zig"); const Object = @import("./object.zig"); const Position = @import("./position.zig"); const Layer = @This(); pub const TileVariant = struct { width: u32, height: u32, data: []u32, fn initDataFromXml( arena: std.mem.Allocator, scratch: *std.heap.ArenaAllocator, lexer: *xml.Lexer, ) ![]u32 { var iter = xml.TagParser.init(lexer); const attrs = try iter.begin("data"); const encoding = attrs.get("encoding") orelse "csv"; // TODO: compression var temp_tiles: std.ArrayList(u32) = .empty; if (std.mem.eql(u8, encoding, "csv")) { const text = try lexer.nextExpectText(); var split_iter = std.mem.splitScalar(u8, text, ','); while (split_iter.next()) |raw_tile_id| { const tile_id_str = std.mem.trim(u8, raw_tile_id, &std.ascii.whitespace); const tile_id = try std.fmt.parseInt(u32, tile_id_str, 10); try temp_tiles.append(scratch.allocator(), tile_id); } } else { return error.UnknownEncodingType; } try iter.finish("data"); return try arena.dupe(u32, temp_tiles.items); } pub fn get(self: TileVariant, x: usize, y: usize) ?u32 { if ((0 <= x and x < self.width) and (0 <= y and y < self.height)) { return self.data[y * self.width + x]; } return null; } }; pub const ImageVariant = struct { pub const Image = struct { // TODO: format source: []const u8, transparent_color: ?Color, width: ?u32, height: ?u32, fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Image { var iter = xml.TagParser.init(lexer); const attrs = try iter.begin("image"); // TODO: format const source = try attrs.getDupe(arena, "source") orelse return error.MissingSource; const width = try attrs.getNumber(u32, "width") orelse null; const height = try attrs.getNumber(u32, "height") orelse null; const transparent_color = try attrs.getColor("trans", false); try iter.finish("image"); return Image{ .source = source, .transparent_color = transparent_color, .width = width, .height = height }; } }; repeat_x: bool, repeat_y: bool, image: ?Image }; pub const ObjectVariant = struct { pub const DrawOrder = enum { top_down, index, const map: std.StaticStringMap(DrawOrder) = .initComptime(.{ .{ "topdown", .top_down }, .{ "index", .index }, }); }; color: ?Color, draw_order: DrawOrder, items: []Object }; pub const GroupVariant = struct { layers: []Layer }; pub const Type = enum { tile, object, image, group, const name_map: std.StaticStringMap(Type) = .initComptime(.{ .{ "layer", .tile }, .{ "objectgroup", .object }, .{ "imagelayer", .image }, .{ "group", .group } }); fn toXmlName(self: Type) []const u8 { return switch (self) { .tile => "layer", .object => "objectgroup", .image => "imagelayer", .group => "group", }; } }; pub const Variant = union(Type) { tile: TileVariant, object: ObjectVariant, image: ImageVariant, group: GroupVariant }; id: u32, name: []const u8, class: []const u8, opacity: f32, visible: bool, tint_color: ?Color, offset_x: f32, offset_y: f32, parallax_x: f32, parallax_y: f32, properties: Property.List, variant: Variant, pub fn initFromXml( arena: std.mem.Allocator, scratch: *std.heap.ArenaAllocator, lexer: *xml.Lexer, ) !Layer { const value = try lexer.peek() orelse return error.MissingStartTag; if (value != .start_tag) return error.MissingStartTag; var layer_type = Type.name_map.get(value.start_tag.name) orelse return error.UnknownLayerType; var iter = xml.TagParser.init(lexer); const attrs = try iter.begin(layer_type.toXmlName()); const id = try attrs.getNumber(u32, "id") orelse return error.MissingId; const name = try attrs.getDupe(arena, "name") orelse ""; const class = try attrs.getDupe(arena, "class") orelse ""; const opacity = try attrs.getNumber(f32, "opacity") orelse 1; const visible = try attrs.getBool("visible", "1", "0") orelse true; const offset_x = try attrs.getNumber(f32, "offsetx") orelse 0; const offset_y = try attrs.getNumber(f32, "offsety") orelse 0; const parallax_x = try attrs.getNumber(f32, "parallaxx") orelse 1; const parallax_y = try attrs.getNumber(f32, "parallaxy") orelse 1; const tint_color = try attrs.getColor("tintcolor", true); var variant: Variant = undefined; switch (layer_type) { .tile => { const width = try attrs.getNumber(u32, "width") orelse return error.MissingWidth; const height = try attrs.getNumber(u32, "height") orelse return error.MissingHeight; variant = .{ .tile = TileVariant{ .width = width, .height = height, .data = &[0]u32{} } }; }, .image => { const repeat_x = try attrs.getBool("repeatx", "1", "0") orelse false; const repeat_y = try attrs.getBool("repeaty", "1", "0") orelse false; variant = .{ .image = ImageVariant{ .repeat_x = repeat_x, .repeat_y = repeat_y, .image = null } }; }, .object => { const draw_order = try attrs.getEnum(ObjectVariant.DrawOrder, "draworder", ObjectVariant.DrawOrder.map) orelse .top_down; const color = try attrs.getColor("color", true); variant = .{ .object = ObjectVariant{ .color = color, .draw_order = draw_order, .items = &.{} } }; }, .group => { variant = .{ .group = .{ .layers = &.{} } }; }, } var properties: Property.List = .empty; var objects: std.ArrayList(Object) = .empty; var layers: std.ArrayList(Layer) = .empty; while (try iter.next()) |node| { if (node.isTag("properties")) { properties = try Property.List.initFromXml(arena, scratch, lexer); continue; } if (variant == .tile and node.isTag("data")) { variant.tile.data = try TileVariant.initDataFromXml(arena, scratch, lexer); continue; } if (variant == .image and node.isTag("image")) { variant.image.image = try ImageVariant.Image.initFromXml(arena, lexer); continue; } if (variant == .object and node.isTag("object")) { const object = try Object.initFromXml(arena, scratch, lexer); try objects.append(scratch.allocator(), object); continue; } if (variant == .group and isLayerNode(node)) { const layer = try initFromXml(arena, scratch, lexer); try layers.append(scratch.allocator(), layer); continue; } try iter.skip(); } try iter.finish(layer_type.toXmlName()); if (variant == .object) { variant.object.items = try arena.dupe(Object, objects.items); } if (variant == .group) { variant.group.layers = try arena.dupe(Layer, layers.items); } return Layer{ .id = id, .name = name, .class = class, .opacity = opacity, .visible = visible, .tint_color = tint_color, .offset_x = offset_x, .offset_y = offset_y, .parallax_x = parallax_x, .parallax_y = parallax_y, .properties = properties, .variant = variant, }; } pub fn isLayerNode(node: xml.TagParser.Node) bool { return node.isTag("layer") or node.isTag("objectgroup") or node.isTag("imagelayer") or node.isTag("group"); }