const std = @import("std"); pub const flipped_horizontally: u32 = 0x80000000; pub const flipped_vertically: u32 = 0x40000000; pub const flipped_diagonally: u32 = 0x20000000; pub const rotated_hexagonal_120: u32 = 0x10000000; /// [https://doc.mapeditor.org/en/stable/reference/json-map-format/#map] pub const Map = struct { backgroundcolor: ?[]const u8 = null, class: ?[]const u8 = null, compressionlevel: i32 = -1, height: u32, hexsidelength: ?u32 = null, infinite: bool, layers: []Layer, nextlayerid: u32, nextobjectid: u32, orientation: Orientation, parallaxoriginx: f32 = 0, parallaxoriginy: f32 = 0, properties: ?[]const Property = null, renderorder: RenderOrder, staggeraxis: ?StaggerAxis = null, staggerindex: ?StaggerIndex = null, tiledversion: []const u8, tileheight: u32, tilesets: []Tileset, tilewidth: u32, type: Type, version: []const u8, width: u32, pub fn globalTileId(self: @This(), index: u32) !Gid { const is_flipped_horizontally = (index & flipped_horizontally > 0); const is_flipped_vertically = (index & flipped_vertically > 0); const is_flipped_diagonally = (index & flipped_diagonally > 0); const is_rotated = (index & rotated_hexagonal_120 > 0); // clear flags const gtid = index & ~flipped_horizontally | flipped_vertically | flipped_diagonally | rotated_hexagonal_120; for (self.tilesets) |tileset| { if (tileset.firstgid <= gtid) { return .{ .gid = gtid - tileset.firstgid, .flipped_horizontally = is_flipped_horizontally, .flipped_vertically = is_flipped_vertically, .flipped_diagonally = is_flipped_diagonally, .rotated = is_rotated }; } } return error.GidNotFound; } pub const Gid = struct { gid: u32, flipped_horizontally: bool, flipped_vertically: bool, flipped_diagonally: bool, rotated: bool, }; pub const Orientation = enum { orthogonal, isometric, staggered, hexagonal }; 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 Type = enum { map }; }; inline fn get(source: std.json.Value, name: []const u8) ?[]const u8 { if (source.object.get(name)) |o| { return o.string; } return null; } // pub const DataEncoding = union(enum) { // csv: ?[]const u32, // base64: ?[]const u8, // pub fn jsonParse(allocator: Allocator, source: std.json.Value, options: std.json.ParseOptions) !@This() { // _ = source; // _ = options; // _ = allocator; // // if (source == .string) { // // // const beep = source.string; // // // return .{ .base64 = beep }; // // return @This(){ .base64 = "hi" }; // // } else if (source == .array) { // // // return .{ .csv = &[_]u32{ 2, 3 } }; // // // return .{ .base64 = "fff" }; // // return error.UnexpectedToken; // // } else { // // return error.UnexpectedToken; // // } // return .{ .base64 = "foo" }; // } // }; pub const Layer = struct { // chunks: ?[]const ChunkEncoding = null, class: ?[]const u8 = null, compression: ?enum { zlib, gzip, zstd } = null, data: ?[]const u32 = null, draworder: ?enum { topdown, index } = .topdown, encoding: ?Encoding = .csv, // height: ?u32 = null, // id: u32, // image: ?[]const u8 = null, // layers: ?[]Layer = null, // locked: bool = false, // name: []const u8, // objects: ?[]Object = null, // offsetx: f32 = 0, // offsety: f32 = 0, // opacity: f32, // parallaxx: f32 = 1, // parallaxy: f32 = 1, // properties: ?[]Property = null, // repeatx: ?bool = null, // repeaty: ?bool = null, // startx: ?i32 = null, // starty: ?i32 = null, // tintcolor: ?[]const u8 = null, // transparentcolor: ?[]const u8 = null, type: Type, // visible: bool, // width: ?u32 = null, // x: i32, // y: i32, pub fn jsonParseFromValue(allocator: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) !@This() { _ = allocator; // autofix _ = options; // autofix if (source.object.get("type")) |layer_type| { if (std.mem.eql(u8, layer_type.string, "tilelayer")) { if (source.object.get("encoding")) |encoding| { if (std.mem.eql(u8, encoding.string, "csv")) { return .{ .type = Type.fromString(layer_type.string) }; } } else { return .{ .type = Type.fromString(layer_type.string) }; } } } else { return error.MissingField; } return error.MissingField; } pub const Type = enum { tilelayer, objectgroup, imagelayer, group, pub fn fromString(string: []const u8) Type { if (std.mem.eql(u8, string, "tilelayer")) { return .tilelayer; } return .tilelayer; } }; pub const Encoding = enum { csv, base64 }; // pub fn jsonParseFromValue(allocator: Allocator, source: std.json.Value, options: std.json.ParseOptions) !@This() { // if (source != .object) return error.UnexpectedToken; // if (source.object.get("data")) |source_data| { // std.log.info("yayyyyyyyyyyyy : {s}\n\n\n", .{source.object.keys()}); // const encoding_value: std.json.Value = source.object.get("encoding") orelse .{ .string = "csv" }; // if (encoding_value != .string) return error.UnexpectedToken; // const encoding = encoding_value.string; // var data: []const u32 = undefined; // if (std.mem.eql(u8, encoding, "base64")) { // var base64 = try std.json.parseFromValueLeaky([]const u8, allocator, source_data, options); // // var buffer = allocator.alloc(u8, std.base64.standard.Decoder.calcSizeForSlice(base64) catch return error.UnexpectedToken); // var buffer: [0x10000]u8 = undefined; // var decoded = buffer[0 .. std.base64.standard.Decoder.calcSizeForSlice(base64) catch return error.UnexpectedToken]; // std.base64.standard.Decoder.decode(decoded, base64) catch return error.UnexpectedToken; // data = &[_]u32{ 1, 4 }; // } else { // data = try std.json.parseFromValueLeaky([]const u32, allocator, source_data, options); // } // return .{ // .id = @intCast(source.object.get("id").?.integer), // .name = source.object.get("name").?.string, // .opacity = 0, // .type = .tilelayer, // .visible = true, // .x = 0, // .y = 0, // .data = data, // }; // } else { // std.log.err("brooooo : {s}\n\n\n", .{source.object.keys()}); // return error.UnexpectedToken; // } // } }; pub const Data = union(enum) { csv: []const u32, base64: []const u8, }; pub const Chunk = struct { data: []const u32, height: u32, width: u32, x: u32, y: u32, }; pub const Object = struct { ellipse: ?bool = null, gid: ?u32 = null, height: f32, id: u32, name: []const u8, point: ?bool = null, polygon: ?[]Point = null, polyline: ?[]Point = null, properties: ?[]Property = null, rotation: f32, template: ?[]const u8 = null, text: ?Text = null, type: ?[]const u8 = null, visible: bool, width: f32, x: f32, y: f32, }; pub const Point = struct { x: f32, y: f32, }; pub const Text = struct { bold: bool, color: []const u8, fontfamily: []const u8 = "sans-serif", halign: enum { center, right, justify, left } = .left, italic: bool = false, kerning: bool = true, pixelsize: usize = 16, strikeout: bool = false, text: []const u8, underline: bool = false, valign: enum { center, bottom, top } = .top, wrap: bool = false, }; pub const Property = struct { name: []const u8, type: enum { string, int, float, bool, color, file, object, class, } = .string, propertytype: []const u8 = "", }; pub const Tileset = struct { backgroundcolor: ?[]const u8 = null, class: ?[]const u8 = null, columns: ?u32 = null, fillmode: ?enum { stretch, @"preserve-aspect-fit" } = .stretch, firstgid: u32, grid: ?Grid = null, image: ?[]const u8 = null, imageheight: ?u32 = null, imagewidth: ?u32 = null, margin: ?u32 = null, name: ?[]const u8 = null, objectalignment: ?enum { unspecified, topleft, top, topright, left, center, right, bottomleft, bottom, bottomright } = .unspecified, properties: ?[]Property = null, source: []const u8, spacing: ?i32 = null, terrains: ?[]Terrain = null, tilecount: ?u32 = null, tileversion: ?[]const u8 = null, tileheight: ?u32 = null, tileoffset: ?Point = null, tilerendersize: ?enum { tile, grid } = .tile, tiles: ?[]Tile = null, tilewidth: ?u32 = null, transformations: ?Transformations = null, transparentcolor: ?[]const u8 = null, type: ?enum { tileset } = .tileset, version: ?[]const u8 = null, wangsets: ?[]Wangset = null, }; pub const Grid = struct { orientation: enum { orthogonal, isometric } = .orthogonal, height: i32, width: i32, }; pub const Terrain = struct { name: []const u8, properties: []Property, tile: i32, }; pub const Tile = struct { animiation: []Frame, id: i32, name: ?[]const u8, imageheight: u32, imagewidth: u32, x: i32, y: i32, width: u32, height: u32, objectgroup: ?Layer, probability: f32, properties: []Property, terrain: []i32, type: []const u8, }; pub const Frame = struct { duration: i32, tileid: i32, }; pub const Transformations = struct { hflip: bool, vflip: bool, rotate: bool, preferuntransformed: bool, }; pub const Wangset = struct { class: ?[]const u8, colors: []Wangcolor, name: []const u8, properties: []Property, tile: i32, type: []const u8, wangtiles: []Wangtile, }; pub const Wangcolor = struct { class: ?[]const u8, color: []const u8, name: []const u8, probability: f32, properties: []Property, tile: i32, }; pub const Wangtile = struct { tileid: i32, wangid: []i32, }; pub fn parse(bytes: []const u8, allocator: std.mem.Allocator) !Map { const map = try std.json.parseFromSliceLeaky(std.json.Value, allocator, bytes, .{}); return try std.json.parseFromValueLeaky(Map, allocator, map, .{ .ignore_unknown_fields = true }); } test "parse works" { const json_map = @embedFile("test/map.tmj"); var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const map = try parse(json_map, arena.allocator()); try std.testing.expectEqualStrings("1.10", map.version); // try std.testing.expectEqual(0, map.globalTileId(0)); }