diff --git a/src/world.zig b/src/world.zig index d92c02a..3b51e11 100644 --- a/src/world.zig +++ b/src/world.zig @@ -389,6 +389,16 @@ pub const Entity = struct { } }; +// Data format: +// | node count | level count | +// | node headers... | +// | level headers... | +// | node data... | +// | level data... | + +const LevelHeader = struct { x: u8, y: u8, offset: u16 }; + + 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 @@ -403,25 +413,37 @@ pub const World = struct { /// I will need to freeze it and move it in a snake like fashion. Or just leave the /// other level loaded. levels: []Level, - /// List of all circuit joins between levels. Levels can have multiple joins - circuit_nodes: []CircuitNode, + /// 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); + // } + // } }; +const NodeID = u16; + pub const CircuitNode = struct { energized: bool, - kind: CircuitKind, + kind: NodeKind, inputs: []usize, + + // pub fn write_header(node: CircuitNode, writer: anytype) !void { + // try writer.writeInt(u16, ) + // } }; -pub const CircuitKind = enum { - /// This join is conditional on logic state inside of the level - Logic, - /// This join is a source of power +pub const NodeKind = union(enum) { + /// An And logic gate, + And: [2]NodeID, + /// This node is a source of power Source, - /// This node has no logic and provides no power - Conduit, - // TODO: This type of node would be a wire stretching - // between multiple levels. This doesn't work with the rules - // for moving wires between levels at the moment. - // Bridge, + /// This node has no logic but can pass it on from another source + Conduit: NodeID, + /// This node represents a physical plug in the game world + Plug: NodeID, }; diff --git a/tools/LDtkImport.zig b/tools/LDtkImport.zig index a234bcb..305f1de 100644 --- a/tools/LDtkImport.zig +++ b/tools/LDtkImport.zig @@ -57,148 +57,21 @@ fn make(step: *std.build.Step) !void { const ldtk = ldtk_parser.root; if (ldtk.levels.len > 0) { - const level0 = ldtk.levels[0]; - if (level0.layerInstances) |layers| { - const world_x: u8 = @intCast(u8, @divExact(level0.worldX, (ldtk.worldGridWidth orelse 160))); - const world_y: u8 = @intCast(u8, @divExact(level0.worldY, (ldtk.worldGridHeight orelse 160))); + const level = ldtk.levels[0]; - var entity_array = std.ArrayList(world.Entity).init(allocator); - defer entity_array.deinit(); + var entity_array = std.ArrayList(world.Entity).init(allocator); + defer entity_array.deinit(); - var circuit_layer: ?LDtk.LayerInstance = null; - var collision_layer: ?LDtk.LayerInstance = null; - for (layers) |layer| { - if (std.mem.eql(u8, layer.__identifier, "Entities")) { - // Entities - std.debug.assert(layer.__type == .Entities); + const parsed_level = try parseLevel(.{ + .allocator = allocator, + .ldtk = ldtk, + .level = level, + .entity_array = &entity_array, + }); + defer allocator.free(parsed_level.tiles.?); - for (layer.entityInstances) |entity| { - 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; - } else if (std.mem.eql(u8, entity.__identifier, "Coin")) { - kind_opt = .Coin; - } else if (std.mem.eql(u8, entity.__identifier, "Door")) { - kind_opt = .Door; - } else if (std.mem.eql(u8, entity.__identifier, "Trapdoor")) { - kind_opt = .Trapdoor; - } - - if (kind_opt) |kind| { - if (kind != .WireNode) { - const world_entity = world.Entity{ - .kind = kind, - .x = @intCast(i16, entity.__grid[0]), - .y = @intCast(i16, entity.__grid[1]), - }; - try entity_array.append(world_entity); - } else { - const p1_x: i16 = @intCast(i16, entity.__grid[0]); - const p1_y: i16 = @intCast(i16, entity.__grid[1]); - var anchor1 = false; - var anchor2 = false; - var p2_x: i16 = p1_x; - var p2_y: i16 = p1_y; - 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_x = @intCast(i16, x.Integer); - p2_y = @intCast(i16, y.Integer); - } - } - const wire_begin = world.Entity{ - .kind = if (anchor1) .WireAnchor else .WireNode, - .x = p1_x, - .y = p1_y, - }; - try entity_array.append(wire_begin); - - const wire_end = world.Entity{ - .kind = if (anchor2) .WireEndAnchor else .WireEndNode, - .x = p2_x, - .y = p2_y, - }; - try entity_array.append(wire_end); - } - } - } - } else if (std.mem.eql(u8, layer.__identifier, "Circuit")) { - // Circuit - std.debug.assert(layer.__type == .IntGrid); - - circuit_layer = layer; - } else if (std.mem.eql(u8, layer.__identifier, "Collision")) { - // Collision - std.debug.assert(layer.__type == .IntGrid); - - collision_layer = layer; - } else { - // Unknown - std.log.warn("{s}: {}", .{ layer.__identifier, layer.__type }); - } - } - - if (circuit_layer == null) return error.MissingCircuitLayer; - if (collision_layer == null) return error.MissingCollisionLayer; - - const circuit = circuit_layer.?; - const collision = collision_layer.?; - - std.debug.assert(circuit.__cWid == collision.__cWid); - std.debug.assert(circuit.__cHei == collision.__cHei); - - const width = @intCast(u16, circuit.__cWid); - const size = @intCast(u16, width * circuit.__cHei); - - var level = world.Level{ - .world_x = world_x, - .world_y = world_y, - .width = @intCast(u16, width), - .size = @intCast(u16, size), - .entity_count = @intCast(u16, entity_array.items.len), - .tiles = null, - .entities = entity_array.items, - }; - level.tiles = try allocator.alloc(world.TileData, size); - defer allocator.free(level.tiles.?); - - const tiles = level.tiles.?; - - // Add unchanged tile data - for (collision.autoLayerTiles) |autotile| { - const x = @divExact(autotile.px[0], collision.__gridSize); - const y = @divExact(autotile.px[1], collision.__gridSize); - const i = @intCast(usize, x + y * width); - const sx = @divExact(autotile.src[0], collision.__gridSize); - const sy = @divExact(autotile.src[1], collision.__gridSize); - const t = sx + sy * 16; - tiles[i] = world.TileData{ .tile = @intCast(u7, t) }; - } - - // Add circuit tiles - for (circuit.intGridCsv) |cir64, i| { - const cir = @intCast(u4, cir64); - const col = collision.intGridCsv[i]; - if (col == 0 or col == 1) { - tiles[i] = world.TileData{ .flags = .{ - .solid = col == 1, - .circuit = cir, - } }; - } - } - - // Save the level! - try level.write(writer); - } + // Save the level! + try parsed_level.write(writer); } // Open output file and write data @@ -210,3 +83,156 @@ fn make(step: *std.build.Step) !void { this.world_data.path = output; } + +/// Returns parsed layers of the +fn parseLevel(opt: struct { + allocator: std.mem.Allocator, + ldtk: LDtk.Root, + level: LDtk.Level, + entity_array: *std.ArrayList(world.Entity), +}) !world.Level { + const ldtk = opt.ldtk; + const level = opt.level; + const entity_array = opt.entity_array; + const allocator = opt.allocator; + + 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))); + + var circuit_layer: ?LDtk.LayerInstance = null; + var collision_layer: ?LDtk.LayerInstance = null; + + for (layers) |layer| { + if (std.mem.eql(u8, layer.__identifier, "Entities")) { + // Entities + std.debug.assert(layer.__type == .Entities); + + for (layer.entityInstances) |entity| { + 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; + } else if (std.mem.eql(u8, entity.__identifier, "Coin")) { + kind_opt = .Coin; + } else if (std.mem.eql(u8, entity.__identifier, "Door")) { + kind_opt = .Door; + } else if (std.mem.eql(u8, entity.__identifier, "Trapdoor")) { + kind_opt = .Trapdoor; + } + + if (kind_opt) |kind| { + if (kind != .WireNode) { + const world_entity = world.Entity{ + .kind = kind, + .x = @intCast(i16, entity.__grid[0]), + .y = @intCast(i16, entity.__grid[1]), + }; + try entity_array.append(world_entity); + } else { + const p1_x: i16 = @intCast(i16, entity.__grid[0]); + const p1_y: i16 = @intCast(i16, entity.__grid[1]); + var anchor1 = false; + var anchor2 = false; + var p2_x: i16 = p1_x; + var p2_y: i16 = p1_y; + 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_x = @intCast(i16, x.Integer); + p2_y = @intCast(i16, y.Integer); + } + } + const wire_begin = world.Entity{ + .kind = if (anchor1) .WireAnchor else .WireNode, + .x = p1_x, + .y = p1_y, + }; + try entity_array.append(wire_begin); + + const wire_end = world.Entity{ + .kind = if (anchor2) .WireEndAnchor else .WireEndNode, + .x = p2_x, + .y = p2_y, + }; + try entity_array.append(wire_end); + } + } + } + } else if (std.mem.eql(u8, layer.__identifier, "Circuit")) { + // Circuit + std.debug.assert(layer.__type == .IntGrid); + + circuit_layer = layer; + } else if (std.mem.eql(u8, layer.__identifier, "Collision")) { + // Collision + std.debug.assert(layer.__type == .IntGrid); + + collision_layer = layer; + } else { + // Unknown + std.log.warn("{s}: {}", .{ layer.__identifier, layer.__type }); + } + } + + if (circuit_layer == null) return error.MissingCircuitLayer; + if (collision_layer == null) return error.MissingCollisionLayer; + + const circuit = circuit_layer.?; + const collision = collision_layer.?; + + std.debug.assert(circuit.__cWid == collision.__cWid); + std.debug.assert(circuit.__cHei == collision.__cHei); + + const width = @intCast(u16, circuit.__cWid); + const size = @intCast(u16, width * circuit.__cHei); + + var parsed_level = world.Level{ + .world_x = world_x, + .world_y = world_y, + .width = @intCast(u16, width), + .size = @intCast(u16, size), + .entity_count = @intCast(u16, entity_array.items.len), + .tiles = null, + .entities = entity_array.items, + }; + parsed_level.tiles = try allocator.alloc(world.TileData, size); + // NOTE: + // defer allocator.free(level.tiles.?); + + const tiles = parsed_level.tiles.?; + + // Add unchanged tile data + for (collision.autoLayerTiles) |autotile| { + const x = @divExact(autotile.px[0], collision.__gridSize); + const y = @divExact(autotile.px[1], collision.__gridSize); + const i = @intCast(usize, x + y * width); + const sx = @divExact(autotile.src[0], collision.__gridSize); + const sy = @divExact(autotile.src[1], collision.__gridSize); + const t = sx + sy * 16; + tiles[i] = world.TileData{ .tile = @intCast(u7, t) }; + } + + // Add circuit tiles + for (circuit.intGridCsv) |cir64, i| { + const cir = @intCast(u4, cir64); + const col = collision.intGridCsv[i]; + if (col == 0 or col == 1) { + tiles[i] = world.TileData{ .flags = .{ + .solid = col == 1, + .circuit = cir, + } }; + } + } + + return parsed_level; +}