Import multiple levels
parent
3fcd7e0942
commit
f5691e45f1
|
@ -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");
|
||||
}
|
||||
|
|
30
src/game.zig
30
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));
|
||||
|
|
110
src/world.zig
110
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,34 +414,64 @@ 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;
|
||||
|
||||
|
|
|
@ -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.?;
|
||||
|
||||
|
|
Loading…
Reference in New Issue