diff --git a/src/game.zig b/src/game.zig index 3ea7fcb..4a9cfb9 100644 --- a/src/game.zig +++ b/src/game.zig @@ -149,7 +149,7 @@ var frame_fba_buf: [8192]u8 = undefined; var frame_fba = std.heap.FixedBufferAllocator.init(&frame_fba_buf); var frame_alloc = frame_fba.allocator(); -var db_fba_buf: [1024]u8 = undefined; +var db_fba_buf: [2046]u8 = undefined; var db_fba = std.heap.FixedBufferAllocator.init(&db_fba_buf); var db_alloc = db_fba.allocator(); @@ -226,9 +226,11 @@ fn loadLevel(lvl: usize) !void { { _ = try wires.resize(0); var a: usize = 0; - while (db.getWire(level, a)) |wire| : (a += 1) { + while (db.getWire(level, a)) |wireSlice| : (a += 1) { + const wire = try world.Wire.getEnds(wireSlice); const coord0 = wire[0].coord.subC(levelc); const coord1 = wire[1].coord.subC(levelc); + w4.tracef("---- Wire (%d, %d), (%d, %d)", coord0.val[0], coord0.val[1], coord1.val[0], coord1.val[1]); const p1 = util.vec2ToVec2f(coord0.toVec2() * tile_size + Vec2{ 4, 4 }); const p2 = util.vec2ToVec2f(coord1.toVec2() * tile_size + Vec2{ 4, 4 }); @@ -243,8 +245,8 @@ fn loadLevel(lvl: usize) !void { w.begin().pos = p1; w.end().pos = p2; - w.begin().pinned = wire[0].kind == world.EntityKind.WireAnchor; - w.end().pinned = wire[1].kind == world.EntityKind.WireEndAnchor; + w.begin().pinned = wire[0].anchored; + w.end().pinned = wire[1].anchored; w.straighten(); } @@ -530,6 +532,7 @@ pub fn update(time: usize) !State { return .Game; } +/// Holds data related to selecting/interacting with the world const Interaction = struct { pos: Vec2, details: union(enum) { diff --git a/src/main.zig b/src/main.zig index 89090a9..50d6d32 100644 --- a/src/main.zig +++ b/src/main.zig @@ -38,6 +38,7 @@ export fn update() void { error.NoLevelUp => showErr(@errorName(e)), error.NoLevelLeft => showErr(@errorName(e)), error.NoLevelRight => showErr(@errorName(e)), + error.MissingEnds => showErr(@errorName(e)), }, }; if (state != newState) { @@ -52,6 +53,7 @@ export fn update() void { error.NullTiles => showErr(@errorName(e)), error.SpawnOutOfBounds => showErr(@errorName(e)), error.InvalidLevel => showErr(@errorName(e)), + error.MissingEnds => showErr(@errorName(e)), }, } } diff --git a/src/world.zig b/src/world.zig index 076ebf4..9d27c36 100644 --- a/src/world.zig +++ b/src/world.zig @@ -190,6 +190,10 @@ pub const Coordinate = struct { return .{ .val = .{ coord.val[0] - other.val[0], coord.val[1] - other.val[1] } }; } + pub fn addOffset(coord: Coordinate, val: [2]i4) Coordinate { + return .{ .val = .{ coord.val[0] + val[0], coord.val[1] + val[1] } }; + } + pub fn eq(coord: Coordinate, other: Coordinate) bool { return coord.val[0] == other.val[0] and coord.val[1] == other.val[1]; } @@ -209,6 +213,10 @@ pub const Coordinate = struct { return .{ coord.val[0], coord.val[1] }; } + pub fn toOffset(coord: Coordinate) [2]i4 { + return .{ @intCast(i4, coord.val[0]), @intCast(i4, coord.val[1]) }; + } + pub fn fromWorld(x: i8, y: i8) Coordinate { return .{ .val = .{ @intCast(i16, x) * 20, @@ -429,10 +437,6 @@ pub const AutoTileset = struct { pub const EntityKind = enum(u8) { Player, Coin, - WireNode, - WireAnchor, - WireEndNode, - WireEndAnchor, Door, Trapdoor, Collected, @@ -461,12 +465,109 @@ pub const Entity = struct { } }; -// Data format: -// | level count | node count | -// | level headers... | -// | node data... | -// | level data... | +const WireKind = enum { + Begin, + BeginPinned, + Point, + PointPinned, + End, +}; +/// A wire is stored as a coordinate and at least one point relative to it, +/// and then an end byte +pub const Wire = union(enum) { + Begin: Coordinate, + BeginPinned: Coordinate, + /// Relative to the last point + Point: [2]i4, + /// Relative to the last point + PointPinned: [2]i4, + End, + + const EndData = struct { coord: Coordinate, anchored: bool }; + + pub fn getEnds(wires: []Wire) ![2]EndData { + std.debug.assert(wires[0] == .Begin or wires[0] == .BeginPinned); + var ends: [2]EndData = undefined; + const w4 = @import("wasm4.zig"); + for (wires) |wire| { + switch (wire) { + .Begin => |coord| { + ends[0] = .{ .coord = coord, .anchored = false }; + ends[1] = ends[0]; + w4.tracef("[getEnds] Begin (%d, %d)", coord.val[0], coord.val[1]); + }, + .BeginPinned => |coord| { + ends[0] = .{ .coord = coord, .anchored = true }; + ends[1] = ends[0]; + w4.tracef("[getEnds] BeginPinned (%d, %d)", coord.val[0], coord.val[1]); + }, + .Point => |offset| { + ends[1] = .{ .coord = ends[1].coord.addOffset(offset), .anchored = false }; + const o1 = @intCast(i32, offset[0]); + const o2 = @intCast(i32, offset[1]); + w4.tracef("[getEnds] Point (%d, %d)", o1, o2); + }, + .PointPinned => |offset| { + ends[1] = .{ .coord = ends[1].coord.addOffset(offset), .anchored = true }; + const o1 = @intCast(i32, offset[0]); + const o2 = @intCast(i32, offset[1]); + w4.tracef("[getEnds] Point Pinned (%d, %d)", o1, o2); + }, + .End => { + return ends; + }, + } + } + return error.MissingEnds; + } + + pub fn write(wire: Wire, writer: anytype) !void { + try writer.writeByte(@enumToInt(wire)); + switch (wire) { + .Begin => |coord| { + try coord.write(writer); + }, + .BeginPinned => |coord| { + try coord.write(writer); + }, + .Point => |point| { + const byte = @bitCast(u8, @intCast(i8, point[0])) | @bitCast(u8, @intCast(i8, point[1])) << 4; + try writer.writeByte(byte); + }, + .PointPinned => |point| { + const byte = @bitCast(u8, @intCast(i8, point[0])) | @bitCast(u8, @intCast(i8, point[1])) << 4; + try writer.writeByte(byte); + }, + .End => {}, + } + } + + pub fn read(reader: anytype) !Wire { + const kind = @intToEnum(WireKind, try reader.readByte()); + switch (kind) { + .Begin => return Wire{ .Begin = try Coord.read(reader) }, + .BeginPinned => return Wire{ .BeginPinned = try Coord.read(reader) }, + .Point => { + const byte = try reader.readByte(); + return Wire{ .Point = .{ + @bitCast(i4, @truncate(u4, 0b0000_1111 & byte)), + @bitCast(i4, @truncate(u4, (0b1111_0000 & byte) >> 4)), + } }; + }, + .PointPinned => { + const byte = try reader.readByte(); + return Wire{ .PointPinned = .{ + @bitCast(i4, @truncate(u4, 0b0000_1111 & byte)), + @bitCast(i4, @truncate(u4, (0b1111_0000 & byte) >> 4)), + } }; + }, + .End => return Wire.End, + } + } +}; + +/// Used to look up level data pub const LevelHeader = struct { x: i8, y: i8, @@ -491,6 +592,7 @@ pub fn write( writer: anytype, level_headers: []LevelHeader, entities: []Entity, + wires: []Wire, circuit_nodes: []CircuitNode, levels: []Level, ) !void { @@ -498,6 +600,8 @@ pub fn write( try writer.writeInt(u16, @intCast(u16, level_headers.len), .Little); // Write number of entities try writer.writeInt(u16, @intCast(u16, entities.len), .Little); + // Write number of entities + try writer.writeInt(u16, @intCast(u16, wires.len), .Little); // Write number of circuit nodes try writer.writeInt(u16, @intCast(u16, circuit_nodes.len), .Little); @@ -511,6 +615,11 @@ pub fn write( try entity.write(writer); } + // Write wire data + for (wires) |wire| { + try wire.write(writer); + } + // Write node data for (circuit_nodes) |node| { try node.write(writer); @@ -527,6 +636,8 @@ pub const Database = struct { cursor: Cursor, level_info: []LevelHeader, entities: []Entity, + wires: []Wire, + wire_count: usize, circuit_info: []CircuitNode, level_data_begin: usize, @@ -544,6 +655,8 @@ pub const Database = struct { const level_count = try reader.readInt(u16, .Little); // read number of entities const entity_count = try reader.readInt(u16, .Little); + // read number of wires + const wire_count = try reader.readInt(u16, .Little); // read number of nodes const node_count = try reader.readInt(u16, .Little); @@ -561,6 +674,16 @@ pub const Database = struct { entities[i] = try Entity.read(reader); } + // read wires + // Allocate a fixed amount of space since wires are likely to be shuffled around + var wires = try alloc.alloc(Wire, 255); + { + var i: usize = 0; + while (i < wire_count) : (i += 1) { + wires[i] = try Wire.read(reader); + } + } + // read circuits var circuit_nodes = try alloc.alloc(CircuitNode, node_count); @@ -575,6 +698,8 @@ pub const Database = struct { .cursor = cursor, .level_info = level_headers, .entities = entities, + .wires = wires, + .wire_count = wire_count, .circuit_info = circuit_nodes, .level_data_begin = level_data_begin, }; @@ -732,20 +857,33 @@ pub const Database = struct { db.entities[coin].kind = .Collected; } - pub fn getWire(database: *Database, level: Level, num: usize) ?[2]Entity { + pub fn getWire(database: *Database, level: Level, num: usize) ?[]Wire { const nw = Coord.fromWorld(level.world_x, level.world_y); const se = nw.add(.{ 20, 20 }); - var node_begin: ?Entity = null; + var node_begin: ?usize = null; var wire_count: usize = 0; - for (database.entities) |entity| { - if (!entity.coord.within(nw, se)) continue; - if (entity.kind == .WireNode or entity.kind == .WireAnchor) { - node_begin = entity; - } else if (entity.kind == .WireEndNode or entity.kind == .WireEndAnchor) { - if (node_begin) |begin| { - if (wire_count == num) return [2]Entity{ begin, entity }; - } - wire_count += 1; + var i: usize = 0; + while (i < database.wire_count) : (i += 1) { + const wire = database.wires[i]; + switch (wire) { + .Begin => |coord| { + if (!coord.within(nw, se)) continue; + node_begin = i; + }, + .BeginPinned => |coord| { + if (!coord.within(nw, se)) continue; + node_begin = i; + }, + .Point, .PointPinned => continue, + .End => { + if (node_begin) |node| { + if (wire_count == num) { + return database.wires[node..i + 1]; + } + wire_count += 1; + node_begin = null; + } + }, } } return null; diff --git a/tools/LDtkImport.zig b/tools/LDtkImport.zig index 47abcf2..6dd5eca 100644 --- a/tools/LDtkImport.zig +++ b/tools/LDtkImport.zig @@ -58,6 +58,9 @@ fn make(step: *std.build.Step) !void { var entity_array = std.ArrayList(world.Entity).init(allocator); defer entity_array.deinit(); + var wires = std.ArrayList(world.Wire).init(allocator); + defer wires.deinit(); + for (ldtk.levels) |level| { std.log.warn("Level: {}", .{levels.items.len}); const parsed_level = try parseLevel(.{ @@ -65,6 +68,7 @@ fn make(step: *std.build.Step) !void { .ldtk = ldtk, .level = level, .entity_array = &entity_array, + .wires = &wires, }); try levels.append(parsed_level); @@ -80,6 +84,10 @@ fn make(step: *std.build.Step) !void { std.log.warn("{:0>2}: {}", .{ i, node }); } + for (wires.items) |node, i| { + std.log.warn("Wire {:0>2}: {any}", .{ i, node }); + } + // 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); @@ -109,7 +117,14 @@ fn make(step: *std.build.Step) !void { defer data.deinit(); const writer = data.writer(); - try world.write(writer, level_headers.items, entity_array.items, circuit.items, levels.items); + try world.write( + writer, + level_headers.items, + entity_array.items, + wires.items, + circuit.items, + levels.items, + ); // Open output file and write data into it cwd.makePath(this.builder.getInstallPath(.lib, "")) catch |e| switch (e) { @@ -127,11 +142,13 @@ fn parseLevel(opt: struct { ldtk: LDtk.Root, level: LDtk.Level, entity_array: *std.ArrayList(world.Entity), + wires: *std.ArrayList(world.Wire), }) !world.Level { const ldtk = opt.ldtk; const level = opt.level; const entity_array = opt.entity_array; const allocator = opt.allocator; + const wires = opt.wires; const layers = level.layerInstances orelse return error.NoLayers; @@ -147,11 +164,12 @@ fn parseLevel(opt: struct { std.debug.assert(layer.__type == .Entities); for (layer.entityInstances) |entity| { + var is_wire = false; var kind_opt: ?world.EntityKind = null; if (std.mem.eql(u8, entity.__identifier, "Player")) { kind_opt = .Player; } else if (std.mem.eql(u8, entity.__identifier, "Wire")) { - kind_opt = .WireNode; + is_wire = true; } else if (std.mem.eql(u8, entity.__identifier, "Coin")) { kind_opt = .Coin; } else if (std.mem.eql(u8, entity.__identifier, "Door")) { @@ -160,59 +178,57 @@ fn parseLevel(opt: struct { kind_opt = .Trapdoor; } + const levelc = world.Coordinate.fromWorld(world_x, world_y); // Parsing code for wire entities. They're a little more complex // than the rest if (kind_opt) |kind| { - const levelc = world.Coordinate.fromWorld(world_x, world_y); - if (kind != .WireNode) { - const entc = world.Coordinate.init(.{ - @intCast(i16, entity.__grid[0]), - @intCast(i16, entity.__grid[1]), - }); - const world_entity = world.Entity{ - .kind = kind, - .coord = levelc.addC(entc) - }; - try entity_array.append(world_entity); - } else { - var anchor1 = false; - var anchor2 = false; - const p1_c = world.Coordinate.init(.{ - @intCast(i16, entity.__grid[0]), - @intCast(i16, entity.__grid[1]), - }); - var p2_c = world.Coordinate.init(.{ - @intCast(i16, entity.__grid[0]), - @intCast(i16, entity.__grid[1]), - }); - for (entity.fieldInstances) |field| { - if (std.mem.eql(u8, field.__identifier, "Anchor")) { - const anchors = field.__value.Array.items; - anchor1 = anchors[0].Bool; - anchor2 = anchors[1].Bool; - } else if (std.mem.eql(u8, field.__identifier, "Point")) { - const end = field.__value.Array.items.len - 1; - const endpoint = field.__value.Array.items[end]; - const x = endpoint.Object.get("cx").?; - const y = endpoint.Object.get("cy").?; - p2_c.val = .{ - @intCast(i16, x.Integer), - @intCast(i16, y.Integer), - }; - } + const entc = world.Coordinate.init(.{ + @intCast(i16, entity.__grid[0]), + @intCast(i16, entity.__grid[1]), + }); + const world_entity = world.Entity{ .kind = kind, .coord = levelc.addC(entc) }; + try entity_array.append(world_entity); + } + if (is_wire) { + var anchor1 = false; + var anchor2 = false; + const p1_c = world.Coordinate.init(.{ + @intCast(i16, entity.__grid[0]), + @intCast(i16, entity.__grid[1]), + }); + var p2_c = world.Coordinate.init(.{ + @intCast(i16, entity.__grid[0]), + @intCast(i16, entity.__grid[1]), + }); + for (entity.fieldInstances) |field| { + if (std.mem.eql(u8, field.__identifier, "Anchor")) { + const anchors = field.__value.Array.items; + anchor1 = anchors[0].Bool; + anchor2 = anchors[1].Bool; + } else if (std.mem.eql(u8, field.__identifier, "Point")) { + const end = field.__value.Array.items.len - 1; + const endpoint = field.__value.Array.items[end]; + const x = endpoint.Object.get("cx").?; + const y = endpoint.Object.get("cy").?; + p2_c.val = .{ + @intCast(i16, x.Integer), + @intCast(i16, y.Integer), + }; } - const wire_begin = world.Entity{ - .kind = if (anchor1) .WireAnchor else .WireNode, - .coord = p1_c.addC(levelc), - }; - try entity_array.append(wire_begin); - - const wire_end = world.Entity{ - .kind = if (anchor2) .WireEndAnchor else .WireEndNode, - .coord = p2_c.addC(levelc), - }; - try entity_array.append(wire_end); } + + if (anchor1) { + try wires.append(.{ .BeginPinned = p1_c.addC(levelc) }); + } else { + try wires.append(.{ .Begin = p1_c.addC(levelc) }); + } + + if (anchor2) { + try wires.append(.{ .PointPinned = p2_c.subC(p1_c).toOffset() }); + } else { + try wires.append(.{ .Point = p2_c.subC(p1_c).toOffset() }); + } + try wires.append(.End); } }