227 lines
6.1 KiB
Zig
227 lines
6.1 KiB
Zig
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();
|
|
}
|