diff options
-rw-r--r-- | .envrc | 7 | ||||
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | build.zig | 95 | ||||
-rw-r--r-- | build.zig.zon | 61 | ||||
-rw-r--r-- | examples/game.zig | 24 | ||||
-rw-r--r-- | examples/map.tmj | 66 | ||||
-rw-r--r-- | flake.lock | 168 | ||||
-rw-r--r-- | flake.nix | 53 | ||||
-rw-r--r-- | src/test/map.tmj | 66 | ||||
-rw-r--r-- | src/tmz.zig | 377 |
11 files changed, 926 insertions, 0 deletions
@@ -0,0 +1,7 @@ +dotenv + +if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" +fi + +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c706ca6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# direnv +.direnv + +# Zig +zig-cache +zig-out diff --git a/README.md b/README.md new file mode 100644 index 0000000..2574b2a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# tmz + +A library for loading Tiled Maps and Tilesets diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..a3b1b10 --- /dev/null +++ b/build.zig @@ -0,0 +1,95 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + // const tmz_module = b.createModule(.{ .root_source_file = .{ .path = "src/tmz.zig" } }); + + const lib = b.addStaticLibrary(.{ + .name = "tmz", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = .{ .path = "src/tmz.zig" }, + .target = target, + .optimize = optimize, + }); + + // This declares intent for the library to be installed into the standard + // location when the user invokes the "install" step (the default step when + // running `zig build`). + b.installArtifact(lib); + + const game = b.addExecutable(.{ + .name = "tmz", + .root_source_file = .{ .path = "examples/game.zig" }, + .target = target, + .optimize = optimize, + }); + + game.root_module.addImport("tmz", &lib.root_module); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(game); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(game); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const lib_unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/tmz.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const game_unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "examples/game.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_game_unit_tests = b.addRunArtifact(game_unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); + test_step.dependOn(&run_game_unit_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..4638b33 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,61 @@ +.{ + .name = "tmz", + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.1.0", + + // This field is optional. + // This is currently advisory only; Zig does not yet do anything + // with this value. + .minimum_zig_version = "0.12.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save <url>` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + //"LICENSE", + //"README.md", + }, +} diff --git a/examples/game.zig b/examples/game.zig new file mode 100644 index 0000000..e86eede --- /dev/null +++ b/examples/game.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const tmz = @import("tmz"); + +pub fn main() !void { + var buffer: [10_000]u8 = undefined; + const file = try std.fs.cwd().readFile("examples/map.tmj", &buffer); + + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + defer _ = gpa.deinit(); + + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + const map = try tmz.parse(file, arena.allocator()); + + const stdout_file = std.io.getStdOut().writer(); + var bw = std.io.bufferedWriter(stdout_file); + const stdout = bw.writer(); + + try stdout.print("Map: {any}\n", .{map}); + + try bw.flush(); // don't forget to flush! +} diff --git a/examples/map.tmj b/examples/map.tmj new file mode 100644 index 0000000..ddfa9f3 --- /dev/null +++ b/examples/map.tmj @@ -0,0 +1,66 @@ +{ + "compressionlevel": -1, + "height": 30, + "class": "bar", + "infinite": false, + "layers": [ + { + "class": "bar", + "data": [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 19, 15, 19, 15, 19, 15, 19, 15, 19, 15, + 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 15, 14, 15, 15, 15, 14, 15, 15, 15, 14, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 14, 15, 19, 15, 19, 15, 19, 15, 19, 15, + 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 15, 14, 15, 14, 15, 15, 15, 15, 15, 14, + 4, 4, 4, 4, 4, 4, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 3, 3, 14, 14, 14, 15, 19, 15, 19, 15, 19, 15, + 5, 5, 5, 5, 5, 5, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 3, 14, 14, 15, 14, 15, 14, 15, 14, 15, 14, + 6, 6, 6, 6, 6, 6, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 16, 16, 16, 16, 14, 15, 19, 15, 19, 15, 19, 15, + 6, 6, 6, 6, 6, 6, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 16, 16, 16, 16, 15, 14, 15, 15, 15, 14, 15, 14, + 6, 6, 6, 6, 6, 6, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 3, 16, 16, 16, 16, 14, 14, 19, 15, 19, 15, 19, 15, + 7, 7, 7, 7, 7, 7, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 13, 13, 13, 13, 8, 9, 8, 14, 15, 14, 15, 14, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 10, 8, 19, 15, 19, 15, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 10, 10, 8, 14, 15, 14, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 2147483659, 10, 17, 15, 19, 15, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 1073741835, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 3221225483, 10, 8, 14, 15, 14, + 4, 4, 4, 4, 4, 4, 10, 10, 10, 10, 1073741835, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 3221225483, 10, 10, 17, 15, 19, 15, + 5, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 1073741835, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 3221225483, 10, 10, 17, 15, 15, 15, 14, + 6, 6, 6, 6, 6, 6, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 15, 19, 15, 19, 15, + 6, 6, 6, 6, 6, 6, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 15, 15, 15, 14, 15, 14, + 6, 6, 6, 6, 6, 6, 1, 3, 1, 3, 1, 3, 1, 2, 1, 2, 1, 3, 1, 3, 1, 2, 1, 3, 1, 3, 1, 2, 19, 15, 19, 15, + 7, 7, 7, 7, 7, 7, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 15, 14, 15, 14, + 1, 2, 1, 3, 1, 3, 1, 3, 1, 2, 1, 2, 1, 3, 1, 2, 1, 2, 1, 2, 1, 3, 1, 2, 1, 2, 1, 2, 1, 2, 19, 15, + 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 15, 14, + 1, 3, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 19, 15, + 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 15, 14, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1], + "height": 30, + "id": 1, + "name": "Tile Layer 1", + "opacity": 1, + "type": "tilelayer", + "visible": true, + "width": 32, + "x": 0, + "y": 0 + } + ], + "nextlayerid": 2, + "nextobjectid": 1, + "orientation": "orthogonal", + "renderorder": "right-down", + "tiledversion": "1.10.0", + "tileheight": 8, + "tilesets": [ + { + "firstgid": 1, + "source": "tiles.tsj" + } + ], + "tilewidth": 8, + "type": "map", + "version": "1.10", + "width": 32 +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..9697d8d --- /dev/null +++ b/flake.lock @@ -0,0 +1,168 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1713532798, + "narHash": "sha256-wtBhsdMJA3Wa32Wtm1eeo84GejtI43pMrFrmwLXrsEc=", + "owner": "numtide", + "repo": "devshell", + "rev": "12e914740a25ea1891ec619bb53cf5e6ca922e40", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1704161960, + "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "63143ac2c9186be6d9da6035fa22620018c85932", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1711703276, + "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1713714899, + "narHash": "sha256-+z/XjO3QJs5rLE5UOf015gdVauVRQd2vZtsFkaXBq2Y=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6143fc5eeb9c4f00163267708e26191d1e918932", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1708475490, + "narHash": "sha256-g1v0TsWBQPX97ziznfJdWhgMyMGtoBFs102xSYO4syU=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "0e74ca98a74bc7270d28838369593635a5db3260", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devshell": "devshell", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs_2", + "treefmt-nix": "treefmt-nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1711963903, + "narHash": "sha256-N3QDhoaX+paWXHbEXZapqd1r95mdshxToGowtjtYkGI=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "49dc4a92b02b8e68798abd99184f228243b6e3ac", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..53dec5f --- /dev/null +++ b/flake.nix @@ -0,0 +1,53 @@ +{ + description = "tmz - a library for loading Tiled Maps and Tilesets"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + flake-parts.url = "github:hercules-ci/flake-parts"; + + devshell.url = "github:numtide/devshell"; + treefmt-nix.url = "github:numtide/treefmt-nix"; + }; + + outputs = inputs @ {flake-parts, ...}: + flake-parts.lib.mkFlake {inherit inputs;} { + imports = [ + inputs.devshell.flakeModule + inputs.treefmt-nix.flakeModule + ]; + systems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin"]; + perSystem = { + config, + self', + inputs', + pkgs, + system, + ... + }: { + treefmt.config = { + projectRootFile = "flake.nix"; + + flakeCheck = true; + flakeFormatter = true; + + programs.zig.enable = true; + programs.alejandra.enable = true; + }; + + devshells.default = { + commands = [ + { + name = "fmt"; + help = "format the repo"; + command = "nix fmt"; + } + ]; + + packages = [ + pkgs.zig_0_12 + ]; + }; + }; + }; +} diff --git a/src/test/map.tmj b/src/test/map.tmj new file mode 100644 index 0000000..ddfa9f3 --- /dev/null +++ b/src/test/map.tmj @@ -0,0 +1,66 @@ +{ + "compressionlevel": -1, + "height": 30, + "class": "bar", + "infinite": false, + "layers": [ + { + "class": "bar", + "data": [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 19, 15, 19, 15, 19, 15, 19, 15, 19, 15, + 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 15, 14, 15, 15, 15, 14, 15, 15, 15, 14, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 14, 15, 19, 15, 19, 15, 19, 15, 19, 15, + 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 15, 14, 15, 14, 15, 15, 15, 15, 15, 14, + 4, 4, 4, 4, 4, 4, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 3, 3, 14, 14, 14, 15, 19, 15, 19, 15, 19, 15, + 5, 5, 5, 5, 5, 5, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 3, 14, 14, 15, 14, 15, 14, 15, 14, 15, 14, + 6, 6, 6, 6, 6, 6, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 16, 16, 16, 16, 14, 15, 19, 15, 19, 15, 19, 15, + 6, 6, 6, 6, 6, 6, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 16, 16, 16, 16, 15, 14, 15, 15, 15, 14, 15, 14, + 6, 6, 6, 6, 6, 6, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 3, 16, 16, 16, 16, 14, 14, 19, 15, 19, 15, 19, 15, + 7, 7, 7, 7, 7, 7, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 13, 13, 13, 13, 8, 9, 8, 14, 15, 14, 15, 14, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 10, 8, 19, 15, 19, 15, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 10, 10, 8, 14, 15, 14, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 2147483659, 10, 17, 15, 19, 15, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 1073741835, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 3221225483, 10, 8, 14, 15, 14, + 4, 4, 4, 4, 4, 4, 10, 10, 10, 10, 1073741835, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 3221225483, 10, 10, 17, 15, 19, 15, + 5, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 1073741835, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 3221225483, 10, 10, 17, 15, 15, 15, 14, + 6, 6, 6, 6, 6, 6, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 18, 17, 15, 19, 15, 19, 15, + 6, 6, 6, 6, 6, 6, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 15, 15, 15, 14, 15, 14, + 6, 6, 6, 6, 6, 6, 1, 3, 1, 3, 1, 3, 1, 2, 1, 2, 1, 3, 1, 3, 1, 2, 1, 3, 1, 3, 1, 2, 19, 15, 19, 15, + 7, 7, 7, 7, 7, 7, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 15, 14, 15, 14, + 1, 2, 1, 3, 1, 3, 1, 3, 1, 2, 1, 2, 1, 3, 1, 2, 1, 2, 1, 2, 1, 3, 1, 2, 1, 2, 1, 2, 1, 2, 19, 15, + 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 15, 14, + 1, 3, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 19, 15, + 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 15, 14, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 3, 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1], + "height": 30, + "id": 1, + "name": "Tile Layer 1", + "opacity": 1, + "type": "tilelayer", + "visible": true, + "width": 32, + "x": 0, + "y": 0 + } + ], + "nextlayerid": 2, + "nextobjectid": 1, + "orientation": "orthogonal", + "renderorder": "right-down", + "tiledversion": "1.10.0", + "tileheight": 8, + "tilesets": [ + { + "firstgid": 1, + "source": "tiles.tsj" + } + ], + "tilewidth": 8, + "type": "map", + "version": "1.10", + "width": 32 +} diff --git a/src/tmz.zig b/src/tmz.zig new file mode 100644 index 0000000..ac16d75 --- /dev/null +++ b/src/tmz.zig @@ -0,0 +1,377 @@ +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)); +} |