Something went wrong. Try again.
A library for parsing Tiled maps.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263height: u32,width: u32,tile_width: u32,tile_height: u32,infinite: bool,orientation: Orientation,
layers: std.StringHashMapUnmanaged(Layer),properties: std.StringHashMapUnmanaged(Property),tilesets: std.ArrayList(Tileset),
background_color: ?Color = null,class: ?[]const u8,
pub const Orientation = enum { orthogonal, isometric, staggered, hexagonal };
pub fn initFromSlice(io: Io, alloc: Allocator, json: []const u8) !Map { var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); const arena_allocator = arena.allocator();
const parsed_value = try std.json.parseFromSliceLeaky(std.json.Value, arena_allocator, json, .{ .ignore_unknown_fields = true });
const json_map = try std.json.parseFromValueLeaky(JsonMap, arena_allocator, parsed_value, .{ .ignore_unknown_fields = true });
var map: Map = .{ .height = json_map.height, .width = json_map.width, .tile_width = json_map.tilewidth, .tile_height = json_map.tileheight, .infinite = json_map.infinite, .orientation = json_map.orientation,
.background_color = if (json_map.backgroundcolor) |color| color else null, .class = if (json_map.class) |c| try alloc.dupe(u8, c) else null, .tilesets = .empty, .layers = .empty, .properties = .empty, };
if (json_map.tilesets) |json_tilesets| { for (json_tilesets) |json_tileset| { const tileset = try Tileset.fromJson(io, alloc, json_tileset); try map.tilesets.append(alloc, tileset); } }
if (json_map.layers) |json_layers| { for (json_layers) |json_layer| { const layer = try Layer.fromJson(io, alloc, json_layer); try map.layers.put(alloc, layer.name, layer); } }
if (json_map.properties) |properties| { for (properties) |property| { const prop = try Property.fromJson(alloc, property); try map.properties.put(alloc, prop.name, prop); } }
return map;}
pub fn initFromFile(io: Io, allocator: Allocator, path: []const u8) !Map { const file = try Io.Dir.cwd().openFile(io, path, .{}); defer file.close(io);
var reader = file.readerStreaming(io, &.{});
var out: Io.Writer.Allocating = .init(allocator); defer out.deinit();
_ = try reader.interface.streamRemaining(&out.writer);
const json = try out.toOwnedSlice(); defer allocator.free(json);
return initFromSlice(io, allocator, json);}
pub fn deinit(self: *Map, allocator: Allocator) void { if (self.class) |class| allocator.free(class);
var layers_it = self.layers.valueIterator(); while (layers_it.next()) |layer| { layer.*.deinit(allocator); } self.layers.deinit(allocator);
for (self.tilesets.items) |*tileset| { tileset.deinit(allocator); } self.tilesets.deinit(allocator);
var properties_it = self.properties.valueIterator(); while (properties_it.next()) |prop| { prop.*.deinit(allocator); } self.properties.deinit(allocator);}
pub const TileResult = struct { tile: Tile, tileset: Tileset,};
pub fn getTile(self: Map, gid: u32) ?TileResult { if (gid == 0) return null;
var i = self.tilesets.items.len; while (i > 0) { i -= 1; const tileset = self.tilesets.items[i]; if (tileset.first_gid <= gid) { const tile = tileset.tiles.get(gid - tileset.first_gid) orelse return null; return .{ .tile = tile, .tileset = tileset }; } } return null;}
/// Finds first objectpub fn findObjectByClass(self: Map, class: []const u8) ?Object { var layer_it = self.layers.valueIterator(); while (layer_it.next()) |layer| { if (layer.content == .object_group) { if (layer.content.object_group.getByClass(class)) |object| { return object; } } } return null;}
/// Finds first object by namepub fn findObject(self: Map, name: []const u8) ?Object { var it = self.layers.valueIterator(); while (it.next()) |layer| { if (layer.content == .object_group) { if (layer.content.object_group.get(name)) |object| { return object; } } } return null;}
/// Finds first layer by namepub fn findLayer(self: Map, name: []const u8) ?Layer { return self.layers.get(name);}
/// Finds first layer by classpub fn findLayerByClass(self: Map, class: []const u8) ?Layer { var it = self.layers.valueIterator(); while (it.next()) |layer| { if (layer.class) |layer_class| { if (std.mem.eql(u8, class, layer_class)) return layer.*; } } return null;}
/// Finds all layers by class, appending them to the provided listpub fn findLayersByClass(self: Map, allocator: Allocator, class: []const u8, out: *std.ArrayList(Layer)) !void { var it = self.layers.valueIterator(); while (it.next()) |layer| { if (layer.class) |layer_class| { if (std.mem.eql(u8, class, layer_class)) try out.append(allocator, layer.*); } }}
pub fn pixelWidth(self: Map) u32 { return self.width * self.tile_width;}
pub fn pixelHeight(self: Map) u32 { return self.height * self.tile_height;}
/// https://doc.mapeditor.org/en/stable/reference/json-map-format/#mapconst JsonMap = struct { /// Hex-formatted color (#RRGGBB or #AARRGGBB) backgroundcolor: ?tmz.Color = null, class: ?[]const u8 = null, /// The compression level to use for tile layer data (defaults to -1, which means to use the algorithm default) // TODO: actually support this? compression_level: i32 = -1, /// Number of tile rows height: u32, /// Length of the side of a hex tile in pixels (hexagonal maps only) hexsidelength: ?u32 = null, infinite: bool, layers: ?[]Layer.JsonLayer = null, /// Auto-increments for each layer nextlayerid: u32, /// Auto-increments for each placed object // next_object_id: u32, orientation: Orientation, parallaxoriginx: ?f32 = 0, parallaxoriginy: ?f32 = 0, properties: ?[]Property = null, /// currently only supported for orthogonal maps renderorder: ?RenderOrder = null, /// staggered / hexagonal maps only staggeraxis: ?StaggerAxis = null, /// staggered / hexagonal maps only staggerindex: ?StaggerIndex = null, tiledversion: []const u8, /// Map grid height tileheight: u32, tilesets: ?[]Tileset.JsonTileset = null, /// Map grid width tilewidth: u32, version: []const u8, /// Number of tile columns width: u32,
pub const RenderOrder = enum { @"right-down", @"right-up", @"left-down", @"left-up" }; pub const StaggerAxis = enum { x, y }; pub const StaggerIndex = enum { odd, even };};
pub const Gid = packed struct(u32) { flags: Flags, gid: u28,
pub fn fromInt(item: u32) Gid { return @bitCast(item); }};
pub const Flags = packed struct(u4) { flipped_horizontally: bool, flipped_vertically: bool, flipped_diagonally: bool, rotated_120: bool,
pub const clear: Flags = .{ .flipped_horizontally = false, .flipped_vertically = false, .flipped_diagonally = false, .rotated_120 = false, };};
const Map = @This();
const tmz = @import("root.zig");const Layer = tmz.Layer;const Object = tmz.Object;const Color = tmz.Color;const Tileset = tmz.Tileset;const Tile = tmz.Tile;const Property = tmz.Property;
const std = @import("std");const Io = std.Io;const innerParseFromValue = std.json.innerParseFromValue;const Allocator = std.mem.Allocator;