diff --git a/src/extract.zig b/src/extract.zig index a62307a..d93cc35 100644 --- a/src/extract.zig +++ b/src/extract.zig @@ -26,6 +26,7 @@ fn is_solid(tile: u7) bool { /// Extracts a compressed level into the map and circuit buffers pub fn extractLevel(opt: Options) !void { + w4.tracef("extract begin"); const map = opt.map; const circuit = opt.circuit; const alloc = opt.alloc; @@ -35,18 +36,21 @@ pub fn extractLevel(opt: Options) !void { const tiles = level.tiles orelse return error.NullTiles; const width = level.width; + w4.tracef("div exact %d, %d", tiles.len, level.width); const height = @divExact(@intCast(u16, tiles.len), level.width); const size = tiles.len; map.map_size = .{ level.width, height }; circuit.map_size = .{ level.width, height }; + w4.tracef("%d", @src().line); var auto_map = try alloc.alloc(bool, size); defer alloc.free(auto_map); var circuit_map = try alloc.alloc(CircuitType, size); defer alloc.free(circuit_map); + w4.tracef("reading tiles"); for (tiles) |data, i| { switch (data) { .tile => |tile| { @@ -64,6 +68,7 @@ pub fn extractLevel(opt: Options) !void { var autotiles = try alloc.alloc(?AutoTile, size); defer alloc.free(autotiles); + w4.tracef("autotile walls"); // Auto generate walls { var i: usize = 0; @@ -123,6 +128,7 @@ pub fn extractLevel(opt: Options) !void { } } + w4.tracef("autotile circuit"); // Auto generate circuit // Re-use autotiles to save memory { @@ -195,4 +201,5 @@ pub fn extractLevel(opt: Options) !void { circuit.map[i] = tile; } } + w4.tracef("extract end"); } diff --git a/src/game.zig b/src/game.zig index 1ade13e..bba2b55 100644 --- a/src/game.zig +++ b/src/game.zig @@ -140,7 +140,7 @@ fn randRangeF(min: f32, max: f32) f32 { } // Allocators -var fba_buf: [4096]u8 = undefined; +var fba_buf: [8192]u8 = undefined; var fba = std.heap.FixedBufferAllocator.init(&fba_buf); var alloc = fba.allocator(); @@ -217,11 +217,34 @@ pub fn start() !void { .pos = 0, .buffer = world_data, }; + w4.tracef("[world]: size=%d", world_data.len); const world_reader = stream.reader(); + var levels = try world.read(alloc, world_reader); + var level_data_offset = try stream.getPos(); + w4.tracef("header_size=%d", level_data_offset); + + try stream.seekTo(level_data_offset + levels[1].offset); + w4.tracef("seek_to=%d", try stream.getPos()); + + for (levels) |lvl, i| { + w4.tracef("[%d]: offset=%d, x=%d, y=%d", i, lvl.offset, @intCast(isize, lvl.x), @intCast(isize, lvl.y)); + } + level = try world.Level.read(world_reader); + w4.tracef("level_read_end=%d", try stream.getPos()); + // w4.tracef( + // "[level 0]: x=%d, y=%d, width=%d, size=%d, entity_count=%d, seek_head=%d", + // level.world_x, + // level.world_y, + // level.width, + // level.size, + // level.entity_count, + // ); + level_buf = try alloc.alloc(world.TileData, level.size); try level.readTiles(world_reader, level_buf); + w4.tracef("level_read_end=%d", try stream.getPos()); try extract.extractLevel(.{ .alloc = frame_alloc, @@ -235,10 +258,13 @@ pub fn start() !void { .switch_on = world.Tiles.SwitchesOn, }); + w4.tracef("entity_count=%d", level.entity_count); + w4.tracef("entity_size=%d", level.entity_count * world.Entity.calculateSize()); var entity_buf = try alloc.alloc(world.Entity, level.entity_count); try level.readEntities(world_reader, entity_buf); + w4.tracef("entity_read_end=%d", try stream.getPos()); - const spawnArr = level.getSpawn().?; + const spawnArr = level.getSpawn() orelse return error.NoPlayerSpawn; const spawn = Vec2{ spawnArr[0], spawnArr[1] }; camera = @divTrunc(spawn, @splat(2, @as(i32, 20))) * @splat(2, @as(i32, 20)); diff --git a/src/world.zig b/src/world.zig index 3b51e11..4f04e61 100644 --- a/src/world.zig +++ b/src/world.zig @@ -139,8 +139,8 @@ pub const TileData = union(enum) { }; pub const Level = struct { - world_x: u8, - world_y: u8, + world_x: i8, + world_y: i8, width: u16, size: u16, entity_count: u16, @@ -159,11 +159,23 @@ pub const Level = struct { }; } + pub fn calculateSize(level: Level) !usize { + const tiles = level.tiles orelse return error.NullTiles; + const entities = level.entities orelse return error.NullEntities; + return @sizeOf(i8) + // world_x + @sizeOf(i8) + // world_y + @sizeOf(u16) + // width + @sizeOf(u16) + // size + @sizeOf(u16) + // entity_count + tiles.len + // + entities.len * 5; + } + pub fn write(level: Level, writer: anytype) !void { var tiles = level.tiles orelse return error.NullTiles; var entities = level.entities orelse return error.NullEntities; - try writer.writeInt(u8, level.world_x, .Little); - try writer.writeInt(u8, level.world_y, .Little); + try writer.writeInt(i8, level.world_x, .Little); + try writer.writeInt(i8, level.world_y, .Little); try writer.writeInt(u16, level.width, .Little); try writer.writeInt(u16, level.size, .Little); try writer.writeInt(u16, level.entity_count, .Little); @@ -179,8 +191,8 @@ pub const Level = struct { pub fn read(reader: anytype) !Level { return Level{ - .world_x = try reader.readInt(u8, .Little), - .world_y = try reader.readInt(u8, .Little), + .world_x = try reader.readInt(i8, .Little), + .world_y = try reader.readInt(i8, .Little), .width = try reader.readInt(u16, .Little), .size = try reader.readInt(u16, .Little), .entity_count = try reader.readInt(u16, .Little), @@ -374,6 +386,12 @@ pub const Entity = struct { x: i16, y: i16, + pub fn calculateSize() usize { + return @sizeOf(u8) + // kind + @sizeOf(i16) + // x + @sizeOf(i16); // y + } + pub fn write(entity: Entity, writer: anytype) !void { try writer.writeInt(u8, @enumToInt(entity.kind), .Little); try writer.writeInt(i16, entity.x, .Little); @@ -396,35 +414,65 @@ pub const Entity = struct { // | node data... | // | level data... | -const LevelHeader = struct { x: u8, y: u8, offset: u16 }; +pub const LevelHeader = struct { + x: i8, + y: i8, + offset: u16, + pub fn write(header: LevelHeader, writer: anytype) !void { + try writer.writeInt(i8, header.x, .Little); + try writer.writeInt(i8, header.y, .Little); + try writer.writeInt(u16, header.offset, .Little); + } -pub const World = struct { - /// All levels in the game. If two rooms are next to each other, they - /// are assumed to be neighbors. Leaving the screen will load in the next - /// level in that direction. The player is placed at the same position - /// vertically, but on the opposite side of the screen horizontally. Vice versa - /// for vertically moving between screens. The player will retain their momentum. - /// - /// When a wire crosses a screen boundary, it will coil up at the player's feet - /// automatically. If one side of the wire is pinned, the wire will be let go of. - /// - /// Alternatively, the wire will be pinned outside of the level. If it isn't pinned, - /// I will need to freeze it and move it in a snake like fashion. Or just leave the - /// other level loaded. - levels: []Level, - /// An abstract representation of all circuits in game. - abstract_circuit: []CircuitNode, - - // pub fn write(world: Entity, writer: anytype) !void { - // try writer.writeInt(u16, circuit_nodes.len, .Little); - // try writer.writeInt(u16, levels.len, .Little); - // for (circuit_nodes) |node| { - // try node.write_header(writer); - // } - // } + pub fn read(reader: anytype) !LevelHeader { + return LevelHeader{ + .x = try reader.readInt(i8, .Little), + .y = try reader.readInt(i8, .Little), + .offset = try reader.readInt(u16, .Little), + }; + } }; +pub fn write(level_headers: []LevelHeader, writer: anytype) !void { + // Write number of levels + try writer.writeInt(u16, @intCast(u16, level_headers.len), .Little); + + // Write headers + for (level_headers) |lvl_header| { + try lvl_header.write(writer); + } +} + +pub fn read(alloc: std.mem.Allocator, reader: anytype) ![]LevelHeader { + // read number of levels + const level_count = try reader.readInt(u16, .Little); + + var level_headers = try alloc.alloc(LevelHeader, level_count); + // read headers + for (level_headers) |_, i| { + level_headers[i] = try LevelHeader.read(reader); + } + + return level_headers; +} + +// All levels in the game. If two rooms are next to each other, they +// are assumed to be neighbors. Leaving the screen will load in the next +// level in that direction. The player is placed at the same position +// vertically, but on the opposite side of the screen horizontally. Vice versa +// for vertically moving between screens. The player will retain their momentum. +// +// When a wire crosses a screen boundary, it will coil up at the player's feet +// automatically. If one side of the wire is pinned, the wire will be let go of. +// +// Alternatively, the wire will be pinned outside of the level. If it isn't pinned, +// I will need to freeze it and move it in a snake like fashion. Or just leave the +// other level loaded. +// levels: []Level, +// An abstract representation of all circuits in game. +// abstract_circuit: []CircuitNode, + const NodeID = u16; pub const CircuitNode = struct { diff --git a/tools/LDtkImport.zig b/tools/LDtkImport.zig index 305f1de..286a4c3 100644 --- a/tools/LDtkImport.zig +++ b/tools/LDtkImport.zig @@ -46,19 +46,16 @@ fn make(step: *std.build.Step) !void { const source = try source_file.readToEndAlloc(allocator, 10 * MB); defer allocator.free(source); - // Create output array to write to - var data = std.ArrayList(u8).init(allocator); - defer data.deinit(); - const writer = data.writer(); - var ldtk_parser = try LDtk.parse(allocator, source); defer ldtk_parser.deinit(); const ldtk = ldtk_parser.root; - if (ldtk.levels.len > 0) { - const level = ldtk.levels[0]; + // Store levels + var levels = std.ArrayList(world.Level).init(allocator); + defer levels.deinit(); + for (ldtk.levels) |level| { var entity_array = std.ArrayList(world.Entity).init(allocator); defer entity_array.deinit(); @@ -68,13 +65,88 @@ fn make(step: *std.build.Step) !void { .level = level, .entity_array = &entity_array, }); - defer allocator.free(parsed_level.tiles.?); - // Save the level! - try parsed_level.write(writer); + try levels.append(parsed_level); + } + defer for (levels.items) |level| { + allocator.free(level.tiles.?); + allocator.free(level.entities.?); + }; + + // Calculate the offset of each level and store it in the headers. + // Offset is relative to the beginning of level.data + var level_headers = std.ArrayList(world.LevelHeader).init(allocator); + defer level_headers.deinit(); + + for (levels.items) |level, i| { + if (level_headers.items.len == 0) { + try level_headers.append(.{ + .x = level.world_x, + .y = level.world_y, + .offset = 0, + }); + continue; + } + const last_offset = level_headers.items[i - 1].offset; + const last_size = try levels.items[i - 1].calculateSize(); + const offset = @intCast(u16, last_offset + last_size); + try level_headers.append(.{ + .x = level.world_x, + .y = level.world_y, + .offset = offset, + }); + std.log.warn("[{}] x={} y={}; {} + {} = {}", .{ i, level.world_x, level.world_y, last_offset, last_size, offset }); } - // Open output file and write data + // Create array to write data to + var data = std.ArrayList(u8).init(allocator); + defer data.deinit(); + const writer = data.writer(); + + try world.write(level_headers.items, writer); + + // Write levels + for (levels.items) |level| { + std.log.warn("{} + {} + {} + {} + {} + {} + {}", .{ + @sizeOf(i8), + @sizeOf(i8), + @sizeOf(u16), + @sizeOf(u16), + @sizeOf(u16), + level.tiles.?.len, + level.entities.?.len * world.Entity.calculateSize(), + }); + + std.log.warn("x={} y={} w={} s={} ec={} t={} e={}", .{ + level.world_x, + level.world_y, + level.width, + level.size, + level.entity_count, + level.tiles.?.len, + level.entities.?.len, + }); + try level.write(writer); + } + + { + var stream = std.io.FixedBufferStream([]const u8){ + .pos = 0, + .buffer = data.items, + }; + const world_reader = stream.reader(); + var lvls = try world.read(allocator, world_reader); + var level_data_offset = try stream.getPos(); + std.log.warn("level_data_offset {}", .{level_data_offset}); + + try stream.seekTo(level_data_offset + lvls[1].offset); + std.log.warn("seek to 1 {}", .{try stream.getPos()}); + + var level = try world.Level.read(world_reader); + std.log.warn("level x={}, y={}", .{ level.world_x, level.world_y }); + } + + // Open output file and write data into it cwd.makePath(this.builder.getInstallPath(.lib, "")) catch |e| switch (e) { error.PathAlreadyExists => {}, else => return e, @@ -84,7 +156,7 @@ fn make(step: *std.build.Step) !void { this.world_data.path = output; } -/// Returns parsed layers of the +/// Returns parsed level. User owns level.tiles fn parseLevel(opt: struct { allocator: std.mem.Allocator, ldtk: LDtk.Root, @@ -98,8 +170,8 @@ fn parseLevel(opt: struct { const layers = level.layerInstances orelse return error.NoLayers; - const world_x: u8 = @intCast(u8, @divExact(level.worldX, (ldtk.worldGridWidth orelse 160))); - const world_y: u8 = @intCast(u8, @divExact(level.worldY, (ldtk.worldGridHeight orelse 160))); + const world_x: i8 = @intCast(i8, @divExact(level.worldX, (ldtk.worldGridWidth orelse 160))); + const world_y: i8 = @intCast(i8, @divExact(level.worldY, (ldtk.worldGridHeight orelse 160))); var circuit_layer: ?LDtk.LayerInstance = null; var collision_layer: ?LDtk.LayerInstance = null; @@ -202,12 +274,9 @@ fn parseLevel(opt: struct { .width = @intCast(u16, width), .size = @intCast(u16, size), .entity_count = @intCast(u16, entity_array.items.len), - .tiles = null, - .entities = entity_array.items, + .tiles = try allocator.alloc(world.TileData, size), + .entities = try allocator.dupe(world.Entity, entity_array.items), }; - parsed_level.tiles = try allocator.alloc(world.TileData, size); - // NOTE: - // defer allocator.free(level.tiles.?); const tiles = parsed_level.tiles.?;