sokoban-v1/libs/tiled/src/layer.zig
2025-12-31 02:36:29 +02:00

284 lines
8.2 KiB
Zig

const std = @import("std");
const Property = @import("./property.zig");
const xml = @import("./xml.zig");
const Color = @import("./color.zig");
const Object = @import("./object.zig");
const Position = @import("./position.zig");
const Layer = @This();
pub const TileVariant = struct {
width: u32,
height: u32,
data: []u32,
fn initDataFromXml(
arena: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer,
) ![]u32 {
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin("data");
const encoding = attrs.get("encoding") orelse "csv";
// TODO: compression
var temp_tiles: std.ArrayList(u32) = .empty;
if (std.mem.eql(u8, encoding, "csv")) {
const text = try lexer.nextExpectText();
var split_iter = std.mem.splitScalar(u8, text, ',');
while (split_iter.next()) |raw_tile_id| {
const tile_id_str = std.mem.trim(u8, raw_tile_id, &std.ascii.whitespace);
const tile_id = try std.fmt.parseInt(u32, tile_id_str, 10);
try temp_tiles.append(scratch.allocator(), tile_id);
}
} else {
return error.UnknownEncodingType;
}
try iter.finish("data");
return try arena.dupe(u32, temp_tiles.items);
}
pub fn get(self: TileVariant, x: usize, y: usize) ?u32 {
if ((0 <= x and x < self.width) and (0 <= y and y < self.height)) {
return self.data[y * self.width + x];
}
return null;
}
};
pub const ImageVariant = struct {
pub const Image = struct {
// TODO: format
source: []const u8,
transparent_color: ?Color,
width: ?u32,
height: ?u32,
fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Image {
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin("image");
// TODO: format
const source = try attrs.getDupe(arena, "source") orelse return error.MissingSource;
const width = try attrs.getNumber(u32, "width") orelse null;
const height = try attrs.getNumber(u32, "height") orelse null;
const transparent_color = try attrs.getColor("trans", false);
try iter.finish("image");
return Image{
.source = source,
.transparent_color = transparent_color,
.width = width,
.height = height
};
}
};
repeat_x: bool,
repeat_y: bool,
image: ?Image
};
pub const ObjectVariant = struct {
pub const DrawOrder = enum {
top_down,
index,
const map: std.StaticStringMap(DrawOrder) = .initComptime(.{
.{ "topdown", .top_down },
.{ "index", .index },
});
};
color: ?Color,
draw_order: DrawOrder,
items: []Object
};
pub const GroupVariant = struct {
layers: []Layer
};
pub const Type = enum {
tile,
object,
image,
group,
const name_map: std.StaticStringMap(Type) = .initComptime(.{
.{ "layer", .tile },
.{ "objectgroup", .object },
.{ "imagelayer", .image },
.{ "group", .group }
});
fn toXmlName(self: Type) []const u8 {
return switch (self) {
.tile => "layer",
.object => "objectgroup",
.image => "imagelayer",
.group => "group",
};
}
};
pub const Variant = union(Type) {
tile: TileVariant,
object: ObjectVariant,
image: ImageVariant,
group: GroupVariant
};
id: u32,
name: []const u8,
class: []const u8,
opacity: f32,
visible: bool,
tint_color: ?Color,
offset_x: f32,
offset_y: f32,
parallax_x: f32,
parallax_y: f32,
properties: Property.List,
variant: Variant,
pub fn initFromXml(
arena: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer,
) !Layer {
const value = try lexer.peek() orelse return error.MissingStartTag;
if (value != .start_tag) return error.MissingStartTag;
var layer_type = Type.name_map.get(value.start_tag.name) orelse return error.UnknownLayerType;
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin(layer_type.toXmlName());
const id = try attrs.getNumber(u32, "id") orelse return error.MissingId;
const name = try attrs.getDupe(arena, "name") orelse "";
const class = try attrs.getDupe(arena, "class") orelse "";
const opacity = try attrs.getNumber(f32, "opacity") orelse 1;
const visible = try attrs.getBool("visible", "1", "0") orelse true;
const offset_x = try attrs.getNumber(f32, "offsetx") orelse 0;
const offset_y = try attrs.getNumber(f32, "offsety") orelse 0;
const parallax_x = try attrs.getNumber(f32, "parallaxx") orelse 1;
const parallax_y = try attrs.getNumber(f32, "parallaxy") orelse 1;
const tint_color = try attrs.getColor("tintcolor", true);
var variant: Variant = undefined;
switch (layer_type) {
.tile => {
const width = try attrs.getNumber(u32, "width") orelse return error.MissingWidth;
const height = try attrs.getNumber(u32, "height") orelse return error.MissingHeight;
variant = .{
.tile = TileVariant{
.width = width,
.height = height,
.data = &[0]u32{}
}
};
},
.image => {
const repeat_x = try attrs.getBool("repeatx", "1", "0") orelse false;
const repeat_y = try attrs.getBool("repeaty", "1", "0") orelse false;
variant = .{
.image = ImageVariant{
.repeat_x = repeat_x,
.repeat_y = repeat_y,
.image = null
}
};
},
.object => {
const draw_order = try attrs.getEnum(ObjectVariant.DrawOrder, "draworder", ObjectVariant.DrawOrder.map) orelse .top_down;
const color = try attrs.getColor("color", true);
variant = .{
.object = ObjectVariant{
.color = color,
.draw_order = draw_order,
.items = &.{}
}
};
},
.group => {
variant = .{
.group = .{
.layers = &.{}
}
};
},
}
var properties: Property.List = .empty;
var objects: std.ArrayList(Object) = .empty;
var layers: std.ArrayList(Layer) = .empty;
while (try iter.next()) |node| {
if (node.isTag("properties")) {
properties = try Property.List.initFromXml(arena, scratch, lexer);
continue;
}
if (variant == .tile and node.isTag("data")) {
variant.tile.data = try TileVariant.initDataFromXml(arena, scratch, lexer);
continue;
}
if (variant == .image and node.isTag("image")) {
variant.image.image = try ImageVariant.Image.initFromXml(arena, lexer);
continue;
}
if (variant == .object and node.isTag("object")) {
const object = try Object.initFromXml(arena, scratch, lexer);
try objects.append(scratch.allocator(), object);
continue;
}
if (variant == .group and isLayerNode(node)) {
const layer = try initFromXml(arena, scratch, lexer);
try layers.append(scratch.allocator(), layer);
continue;
}
try iter.skip();
}
try iter.finish(layer_type.toXmlName());
if (variant == .object) {
variant.object.items = try arena.dupe(Object, objects.items);
}
if (variant == .group) {
variant.group.layers = try arena.dupe(Layer, layers.items);
}
return Layer{
.id = id,
.name = name,
.class = class,
.opacity = opacity,
.visible = visible,
.tint_color = tint_color,
.offset_x = offset_x,
.offset_y = offset_y,
.parallax_x = parallax_x,
.parallax_y = parallax_y,
.properties = properties,
.variant = variant,
};
}
pub fn isLayerNode(node: xml.TagParser.Node) bool {
return node.isTag("layer") or node.isTag("objectgroup") or node.isTag("imagelayer") or node.isTag("group");
}