const std = @import("std"); const Io = std.Io; const Allocator = std.mem.Allocator; const xml = @import("./xml.zig"); const Property = @import("./property.zig"); const Position = @import("./position.zig"); const Tileset = @This(); pub const Image = struct { source: []const u8, width: u32, height: u32, pub fn intFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Image { var iter = xml.TagParser.init(lexer); const attrs = try iter.begin("image"); const source = try attrs.getDupe(arena, "width") orelse return error.MissingSource; const width = try attrs.getNumber(u32, "width") orelse return error.MissingWidth; const height = try attrs.getNumber(u32, "height") orelse return error.MissingHeight; try iter.finish("image"); return Image{ .source = source, .width = width, .height = height }; } }; pub const Tile = struct { id: u32, properties: Property.List, pub fn initFromXml( arena: std.mem.Allocator, scratch: *std.heap.ArenaAllocator, lexer: *xml.Lexer, ) !Tile { var iter = xml.TagParser.init(lexer); const tile_attrs = try iter.begin("tile"); const id = try tile_attrs.getNumber(u32, "id") orelse return error.MissingId; var properties: Property.List = .empty; while (try iter.next()) |node| { if (node.isTag("properties")) { properties = try Property.List.initFromXml(arena, scratch, lexer); continue; } try iter.skip(); } try iter.finish("tile"); return Tile{ .id = id, .properties = properties }; } }; pub const List = struct { const Entry = struct { name: []const u8, tileset: Tileset }; list: std.ArrayList(Entry), pub const empty = List{ .list = .empty }; pub fn add(self: *List, gpa: Allocator, name: []const u8, tileset: Tileset) !void { if (self.get(name) != null) { return error.DuplicateName; } const name_dupe = try gpa.dupe(u8, name); errdefer gpa.free(name_dupe); try self.list.append(gpa, .{ .name = name_dupe, .tileset = tileset }); } pub fn get(self: *const List, name: []const u8) ?*const Tileset { for (self.list.items) |*entry| { if (std.mem.eql(u8, entry.name, name)) { return &entry.tileset; } } return null; } pub fn deinit(self: *List, gpa: Allocator) void { for (self.list.items) |entry| { gpa.free(entry.name); entry.tileset.deinit(); } self.list.deinit(gpa); } }; arena: std.heap.ArenaAllocator, version: []const u8, tiled_version: []const u8, name: []const u8, tile_width: u32, tile_height: u32, tile_count: u32, columns: u32, image: Image, tiles: []Tile, pub fn initFromBuffer( gpa: std.mem.Allocator, scratch: *std.heap.ArenaAllocator, xml_buffers: *xml.Lexer.Buffers, buffer: []const u8 ) !Tileset { var reader = Io.Reader.fixed(buffer); return initFromReader(gpa, scratch, xml_buffers, &reader); } pub fn initFromReader( gpa: std.mem.Allocator, scratch: *std.heap.ArenaAllocator, xml_buffers: *xml.Lexer.Buffers, reader: *Io.Reader, ) !Tileset { var lexer = xml.Lexer.init(reader, xml_buffers); return initFromXml(gpa, scratch, &lexer); } pub fn initFromXml( gpa: std.mem.Allocator, scratch: *std.heap.ArenaAllocator, lexer: *xml.Lexer ) !Tileset { var arena_state = std.heap.ArenaAllocator.init(gpa); const arena = arena_state.allocator(); var iter = xml.TagParser.init(lexer); const tileset_attrs = try iter.begin("tileset"); const version = try tileset_attrs.getDupe(arena, "version") orelse return error.MissingTilesetTag; const tiled_version = try tileset_attrs.getDupe(arena, "tiledversion") orelse return error.MissingTilesetTag; const name = try tileset_attrs.getDupe(arena, "name") orelse return error.MissingTilesetTag; const tile_width = try tileset_attrs.getNumber(u32, "tilewidth") orelse return error.MissingTilesetTag; const tile_height = try tileset_attrs.getNumber(u32, "tileheight") orelse return error.MissingTilesetTag; const tile_count = try tileset_attrs.getNumber(u32, "tilecount") orelse return error.MissingTilesetTag; const columns = try tileset_attrs.getNumber(u32, "columns") orelse return error.MissingTilesetTag; var image: ?Image = null; var tiles_list: std.ArrayList(Tile) = .empty; while (try iter.next()) |node| { if (node.isTag("image")) { image = try Image.intFromXml(arena, lexer); continue; } else if (node.isTag("tile")) { const tile = try Tile.initFromXml(arena, scratch, lexer); try tiles_list.append(scratch.allocator(), tile); continue; } try iter.skip(); } try iter.finish("tileset"); const tiles = try arena.dupe(Tile, tiles_list.items); return Tileset{ .arena = arena_state, .version = version, .tiled_version = tiled_version, .name = name, .tile_width = tile_width, .tile_height = tile_height, .tile_count = tile_count, .columns = columns, .image = image orelse return error.MissingImageTag, .tiles = tiles }; } pub fn getTileProperties(self: *const Tileset, id: u32) ?Property.List { for (self.tiles) |tile| { if (tile.id == id) { return tile.properties; } } return null; } pub fn getTilePositionInImage(self: *const Tileset, id: u32) ?Position { if (id >= self.tile_count) { return null; } const tileset_width = @divExact(self.image.width, self.tile_width); const tile_x = @mod(id, tileset_width); const tile_y = @divFloor(id, tileset_width); return Position{ .x = @floatFromInt(tile_x * self.tile_width), .y = @floatFromInt(tile_y * self.tile_height), }; } pub fn deinit(self: *const Tileset) void { self.arena.deinit(); }