Import multiple levels

master
Louis Pearson 2022-08-06 01:15:38 -06:00
parent 3fcd7e0942
commit f5691e45f1
4 changed files with 202 additions and 52 deletions

View File

@ -26,6 +26,7 @@ fn is_solid(tile: u7) bool {
/// Extracts a compressed level into the map and circuit buffers /// Extracts a compressed level into the map and circuit buffers
pub fn extractLevel(opt: Options) !void { pub fn extractLevel(opt: Options) !void {
w4.tracef("extract begin");
const map = opt.map; const map = opt.map;
const circuit = opt.circuit; const circuit = opt.circuit;
const alloc = opt.alloc; const alloc = opt.alloc;
@ -35,18 +36,21 @@ pub fn extractLevel(opt: Options) !void {
const tiles = level.tiles orelse return error.NullTiles; const tiles = level.tiles orelse return error.NullTiles;
const width = level.width; const width = level.width;
w4.tracef("div exact %d, %d", tiles.len, level.width);
const height = @divExact(@intCast(u16, tiles.len), level.width); const height = @divExact(@intCast(u16, tiles.len), level.width);
const size = tiles.len; const size = tiles.len;
map.map_size = .{ level.width, height }; map.map_size = .{ level.width, height };
circuit.map_size = .{ level.width, height }; circuit.map_size = .{ level.width, height };
w4.tracef("%d", @src().line);
var auto_map = try alloc.alloc(bool, size); var auto_map = try alloc.alloc(bool, size);
defer alloc.free(auto_map); defer alloc.free(auto_map);
var circuit_map = try alloc.alloc(CircuitType, size); var circuit_map = try alloc.alloc(CircuitType, size);
defer alloc.free(circuit_map); defer alloc.free(circuit_map);
w4.tracef("reading tiles");
for (tiles) |data, i| { for (tiles) |data, i| {
switch (data) { switch (data) {
.tile => |tile| { .tile => |tile| {
@ -64,6 +68,7 @@ pub fn extractLevel(opt: Options) !void {
var autotiles = try alloc.alloc(?AutoTile, size); var autotiles = try alloc.alloc(?AutoTile, size);
defer alloc.free(autotiles); defer alloc.free(autotiles);
w4.tracef("autotile walls");
// Auto generate walls // Auto generate walls
{ {
var i: usize = 0; var i: usize = 0;
@ -123,6 +128,7 @@ pub fn extractLevel(opt: Options) !void {
} }
} }
w4.tracef("autotile circuit");
// Auto generate circuit // Auto generate circuit
// Re-use autotiles to save memory // Re-use autotiles to save memory
{ {
@ -195,4 +201,5 @@ pub fn extractLevel(opt: Options) !void {
circuit.map[i] = tile; circuit.map[i] = tile;
} }
} }
w4.tracef("extract end");
} }

View File

@ -140,7 +140,7 @@ fn randRangeF(min: f32, max: f32) f32 {
} }
// Allocators // Allocators
var fba_buf: [4096]u8 = undefined; var fba_buf: [8192]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&fba_buf); var fba = std.heap.FixedBufferAllocator.init(&fba_buf);
var alloc = fba.allocator(); var alloc = fba.allocator();
@ -217,11 +217,34 @@ pub fn start() !void {
.pos = 0, .pos = 0,
.buffer = world_data, .buffer = world_data,
}; };
w4.tracef("[world]: size=%d", world_data.len);
const world_reader = stream.reader(); 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); 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); level_buf = try alloc.alloc(world.TileData, level.size);
try level.readTiles(world_reader, level_buf); try level.readTiles(world_reader, level_buf);
w4.tracef("level_read_end=%d", try stream.getPos());
try extract.extractLevel(.{ try extract.extractLevel(.{
.alloc = frame_alloc, .alloc = frame_alloc,
@ -235,10 +258,13 @@ pub fn start() !void {
.switch_on = world.Tiles.SwitchesOn, .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); var entity_buf = try alloc.alloc(world.Entity, level.entity_count);
try level.readEntities(world_reader, entity_buf); 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] }; const spawn = Vec2{ spawnArr[0], spawnArr[1] };
camera = @divTrunc(spawn, @splat(2, @as(i32, 20))) * @splat(2, @as(i32, 20)); camera = @divTrunc(spawn, @splat(2, @as(i32, 20))) * @splat(2, @as(i32, 20));

View File

@ -139,8 +139,8 @@ pub const TileData = union(enum) {
}; };
pub const Level = struct { pub const Level = struct {
world_x: u8, world_x: i8,
world_y: u8, world_y: i8,
width: u16, width: u16,
size: u16, size: u16,
entity_count: 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 { pub fn write(level: Level, writer: anytype) !void {
var tiles = level.tiles orelse return error.NullTiles; var tiles = level.tiles orelse return error.NullTiles;
var entities = level.entities orelse return error.NullEntities; var entities = level.entities orelse return error.NullEntities;
try writer.writeInt(u8, level.world_x, .Little); try writer.writeInt(i8, level.world_x, .Little);
try writer.writeInt(u8, level.world_y, .Little); try writer.writeInt(i8, level.world_y, .Little);
try writer.writeInt(u16, level.width, .Little); try writer.writeInt(u16, level.width, .Little);
try writer.writeInt(u16, level.size, .Little); try writer.writeInt(u16, level.size, .Little);
try writer.writeInt(u16, level.entity_count, .Little); try writer.writeInt(u16, level.entity_count, .Little);
@ -179,8 +191,8 @@ pub const Level = struct {
pub fn read(reader: anytype) !Level { pub fn read(reader: anytype) !Level {
return Level{ return Level{
.world_x = try reader.readInt(u8, .Little), .world_x = try reader.readInt(i8, .Little),
.world_y = try reader.readInt(u8, .Little), .world_y = try reader.readInt(i8, .Little),
.width = try reader.readInt(u16, .Little), .width = try reader.readInt(u16, .Little),
.size = try reader.readInt(u16, .Little), .size = try reader.readInt(u16, .Little),
.entity_count = try reader.readInt(u16, .Little), .entity_count = try reader.readInt(u16, .Little),
@ -374,6 +386,12 @@ pub const Entity = struct {
x: i16, x: i16,
y: 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 { pub fn write(entity: Entity, writer: anytype) !void {
try writer.writeInt(u8, @enumToInt(entity.kind), .Little); try writer.writeInt(u8, @enumToInt(entity.kind), .Little);
try writer.writeInt(i16, entity.x, .Little); try writer.writeInt(i16, entity.x, .Little);
@ -396,35 +414,65 @@ pub const Entity = struct {
// | node data... | // | node data... |
// | level 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 { pub fn read(reader: anytype) !LevelHeader {
/// All levels in the game. If two rooms are next to each other, they return LevelHeader{
/// are assumed to be neighbors. Leaving the screen will load in the next .x = try reader.readInt(i8, .Little),
/// level in that direction. The player is placed at the same position .y = try reader.readInt(i8, .Little),
/// vertically, but on the opposite side of the screen horizontally. Vice versa .offset = try reader.readInt(u16, .Little),
/// 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 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; const NodeID = u16;
pub const CircuitNode = struct { pub const CircuitNode = struct {

View File

@ -46,19 +46,16 @@ fn make(step: *std.build.Step) !void {
const source = try source_file.readToEndAlloc(allocator, 10 * MB); const source = try source_file.readToEndAlloc(allocator, 10 * MB);
defer allocator.free(source); 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); var ldtk_parser = try LDtk.parse(allocator, source);
defer ldtk_parser.deinit(); defer ldtk_parser.deinit();
const ldtk = ldtk_parser.root; const ldtk = ldtk_parser.root;
if (ldtk.levels.len > 0) { // Store levels
const level = ldtk.levels[0]; var levels = std.ArrayList(world.Level).init(allocator);
defer levels.deinit();
for (ldtk.levels) |level| {
var entity_array = std.ArrayList(world.Entity).init(allocator); var entity_array = std.ArrayList(world.Entity).init(allocator);
defer entity_array.deinit(); defer entity_array.deinit();
@ -68,13 +65,88 @@ fn make(step: *std.build.Step) !void {
.level = level, .level = level,
.entity_array = &entity_array, .entity_array = &entity_array,
}); });
defer allocator.free(parsed_level.tiles.?);
// Save the level! try levels.append(parsed_level);
try parsed_level.write(writer); }
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) { cwd.makePath(this.builder.getInstallPath(.lib, "")) catch |e| switch (e) {
error.PathAlreadyExists => {}, error.PathAlreadyExists => {},
else => return e, else => return e,
@ -84,7 +156,7 @@ fn make(step: *std.build.Step) !void {
this.world_data.path = output; this.world_data.path = output;
} }
/// Returns parsed layers of the /// Returns parsed level. User owns level.tiles
fn parseLevel(opt: struct { fn parseLevel(opt: struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
ldtk: LDtk.Root, ldtk: LDtk.Root,
@ -98,8 +170,8 @@ fn parseLevel(opt: struct {
const layers = level.layerInstances orelse return error.NoLayers; const layers = level.layerInstances orelse return error.NoLayers;
const world_x: u8 = @intCast(u8, @divExact(level.worldX, (ldtk.worldGridWidth orelse 160))); const world_x: i8 = @intCast(i8, @divExact(level.worldX, (ldtk.worldGridWidth orelse 160)));
const world_y: u8 = @intCast(u8, @divExact(level.worldY, (ldtk.worldGridHeight orelse 160))); const world_y: i8 = @intCast(i8, @divExact(level.worldY, (ldtk.worldGridHeight orelse 160)));
var circuit_layer: ?LDtk.LayerInstance = null; var circuit_layer: ?LDtk.LayerInstance = null;
var collision_layer: ?LDtk.LayerInstance = null; var collision_layer: ?LDtk.LayerInstance = null;
@ -202,12 +274,9 @@ fn parseLevel(opt: struct {
.width = @intCast(u16, width), .width = @intCast(u16, width),
.size = @intCast(u16, size), .size = @intCast(u16, size),
.entity_count = @intCast(u16, entity_array.items.len), .entity_count = @intCast(u16, entity_array.items.len),
.tiles = null, .tiles = try allocator.alloc(world.TileData, size),
.entities = entity_array.items, .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.?; const tiles = parsed_level.tiles.?;