diff --git a/deps/zig-ldtk b/deps/zig-ldtk index 91d78d9..e936186 160000 --- a/deps/zig-ldtk +++ b/deps/zig-ldtk @@ -1 +1 @@ -Subproject commit 91d78d9c52f4f00f906b89bad8ff27fe295dd1f6 +Subproject commit e93618602d79d1e998a10c0ce1b4473459b19ff4 diff --git a/src/circuit.zig b/src/circuit.zig index 5377f26..09f79f0 100644 --- a/src/circuit.zig +++ b/src/circuit.zig @@ -234,17 +234,17 @@ pub fn enable(this: *@This(), cell: Cell) void { this.levels[i] += 1; } -pub fn bridge(this: *@This(), cells: [2]Cell, bridgeID: usize) !void { +pub fn bridge(this: *@This(), cells: [2]Cell, bridgeID: usize) void { if (this.indexOf(cells[0])) |_| { if (this.indexOf(cells[1])) |_| { - try this.bridges.append(.{ .cells = cells, .id = bridgeID, .enabled = false }); + this.bridges.append(.{ .cells = cells, .id = bridgeID, .enabled = false }); } } } -pub fn addSource(this: *@This(), cell: Cell) !void { +pub fn addSource(this: *@This(), cell: Cell) void { if (this.indexOf(cell)) |_| { - try this.sources.append(cell); + this.sources.append(cell); } } @@ -265,7 +265,7 @@ pub fn enabledBridges(this: @This(), alloc: std.mem.Allocator) !util.Buffer(usiz pub fn enabledDoors(this: @This(), alloc: std.mem.Allocator) !util.Buffer(Cell) { var items = try alloc.alloc(Cell, this.doors.len); - var buffer = util.buffer(Cell).init(items); + var buffer = util.Buffer(Cell).init(items); for (this.doors.items) |d| { if (d.enabled) buffer.append(d.cell); } @@ -289,11 +289,10 @@ pub fn toggle(this: *@This(), c: Cell) void { pub fn clear(this: *@This()) void { std.mem.set(u8, this.levels, 0); - for (this.doors.slice()) |*door| { + for (this.doors.items) |*door| { door.enabled = false; } - // Resizing to zero should always work - this.bridges.resize(0) catch unreachable; + this.bridges.reset(); } pub fn reset(this: *@This()) void { @@ -303,7 +302,7 @@ pub fn reset(this: *@This()) void { } const w4 = @import("wasm4.zig"); -const Queue = util.Queue(Cell, MAXCELLS); +const Queue = util.Queue(Cell); // Returns number of cells filled pub fn fill(this: *@This(), alloc: std.mem.Allocator) !usize { var count: usize = 0; @@ -313,13 +312,15 @@ pub fn fill(this: *@This(), alloc: std.mem.Allocator) !usize { var visited = util.Buffer(usize).init(items); - var q = try Queue.init(); - for (this.sources.slice()) |source| { + var q_buf = try alloc.alloc(Cell, MAXCELLS); + var q = Queue.init(q_buf); + + for (this.sources.items) |source| { try q.insert(source); } while (q.remove()) |cell| { const tile = this.get_cell(cell) orelse { - for (this.doors.slice()) |*d| { + for (this.doors.items) |*d| { if (@reduce(.And, d.cell == cell)) { d.enabled = true; } @@ -328,9 +329,9 @@ pub fn fill(this: *@This(), alloc: std.mem.Allocator) !usize { }; const index = this.indexOf(cell) orelse continue; this.enable(cell); - const hasVisited = std.mem.containsAtLeast(usize, visited.slice(), 1, &.{index}); + const hasVisited = std.mem.containsAtLeast(usize, visited.items, 1, &.{index}); if (hasVisited and !is_logic(tile)) continue; - try visited.append(index); + visited.append(index); count += 1; if (get_logic(tile)) |logic| { // TODO: implement other logic (though I'm pretty sure that requires a graph...) @@ -349,7 +350,7 @@ pub fn fill(this: *@This(), alloc: std.mem.Allocator) !usize { nextCell[1] >= this.map_size[1]) continue; const nextTile = this.get_cell(nextCell) orelse here: { - for (this.doors.slice()) |*d| { + for (this.doors.items) |*d| { if (@reduce(.And, d.cell == nextCell)) { d.enabled = true; } @@ -360,7 +361,7 @@ pub fn fill(this: *@This(), alloc: std.mem.Allocator) !usize { try q.insert(nextCell); } if (is_plug(tile)) { - for (this.bridges.slice()) |*b| { + for (this.bridges.items) |*b| { if (@reduce(.And, b.cells[0] == cell)) { try q.insert(b.cells[1]); b.enabled = true; diff --git a/src/game.zig b/src/game.zig index 2e2a251..ab22636 100644 --- a/src/game.zig +++ b/src/game.zig @@ -1,6 +1,5 @@ const std = @import("std"); const w4 = @import("wasm4.zig"); -const assets = @import("assets"); const input = @import("input.zig"); const util = @import("util.zig"); const Circuit = @import("circuit.zig"); @@ -8,6 +7,9 @@ const Map = @import("map.zig"); const Music = @import("music.zig"); const State = @import("main.zig").State; const Disk = @import("disk.zig"); +const extract = @import("extract.zig"); +const world = @import("world.zig"); +const world_data = @import("world_data"); const Vec2 = util.Vec2; const Vec2f = util.Vec2f; @@ -136,6 +138,15 @@ fn randRangeF(min: f32, max: f32) f32 { return min + (random.float(f32) * (max - min)); } +// Allocators +var fba_buf: [4096]u8 = undefined; +var fba = std.heap.FixedBufferAllocator.init(&fba_buf); +var alloc = fba.allocator(); + +var frame_fba_buf: [4096]u8 = undefined; +var frame_fba = std.heap.FixedBufferAllocator.init(&frame_fba_buf); +var frame_alloc = frame_fba.allocator(); + // Global vars var map: Map = undefined; var circuit: Circuit = undefined; @@ -156,9 +167,12 @@ var ScoreCoin = Sprite{ .flags = .{ .bpp = .b2 }, }; -var solids_mutable = assets.solid; -pub var conduit_mutable = assets.conduit; -var conduitLevels_mutable: [conduit_mutable.len]u8 = undefined; +var map_buf: [400]u8 = undefined; + +var circuit_lvl_buf: [400]u8 = undefined; +var circuit_buf: [400]u8 = undefined; + +var circuit_options: Circuit.Options = undefined; pub const anim_store = struct { const stand = Anim.frame(8); @@ -187,17 +201,66 @@ fn showErr(msg: []const u8) noreturn { pub fn start() !void { particles = try ParticleSystem.init(); - std.mem.set(u8, &conduitLevels_mutable, 0); - circuit = try Circuit.init(&conduit_mutable, &conduitLevels_mutable, assets.conduit_size); - map = Map.init(&solids_mutable, assets.solid_size); + var level_size = Vec2{ 20, 20 }; - camera = @divTrunc(assets.spawn, @splat(2, @as(i32, 20))) * @splat(2, @as(i32, 20)); + circuit_options = .{ + .map = &circuit_buf, + .levels = &circuit_lvl_buf, + .map_size = level_size, + .bridges = try alloc.alloc(Circuit.BridgeState, 5), + .sources = try alloc.alloc(util.Cell, 5), + .doors = try alloc.alloc(Circuit.DoorState, 5), + }; + circuit = Circuit.init(circuit_options); + + map = Map.init(&map_buf, level_size); + + var stream = std.io.FixedBufferStream([]const u8){ + .pos = 0, + .buffer = world_data, + }; + const world_reader = stream.reader(); + + var level = try world.Level.read(world_reader); + var level_buf = try alloc.alloc(world.TileData, level.size); + try level.readTiles(world_reader, level_buf); + + try extract.extractLevel(.{ + .alloc = frame_alloc, + .level = level, + .map = &map, + .circuit = &circuit, + .tileset = world.AutoTileset.initOffsetFull(113), + .conduit = world.AutoTileset.initOffsetFull(97), + .plug = world.AutoTileset.initOffsetCardinal(17), + .switch_off = world.AutoTileset.initSwitches(&.{ + 29, // South-North + 25, // South-West-North + 27, // South-East-North + }, 2), + .switch_on = world.AutoTileset.initSwitches(&.{ + 30, // South-North + 26, // South-West-North + 28, // South-East-West + }, 2), + }); + + var entity_buf = try alloc.alloc(world.Entity, level.entity_count); + try level.readEntities(world_reader, entity_buf); + + const spawnArr = level.getSpawn().?; + const spawn = Vec2{ spawnArr[0], spawnArr[1] }; + // std.mem.set(u8, &conduitLevels_mutable, 0); + // circuit = try Circuit.init(&conduit_mutable, &conduitLevels_mutable, assets.conduit_size); + // map = Map.init(&solids_mutable, assets.solid_size); + + camera = @divTrunc(spawn, @splat(2, @as(i32, 20))) * @splat(2, @as(i32, 20)); const tile_size = Vec2{ 8, 8 }; const offset = Vec2{ 4, 8 }; player = .{ - .pos = Pos.init(util.vec2ToVec2f(assets.spawn * tile_size + offset)), + .pos = Pos.init(util.vec2ToVec2f(spawn * tile_size + offset)), .control = .{ .controller = .player, .state = .stand }, .sprite = .{ .offset = .{ -4, -8 }, .size = .{ 8, 8 }, .index = 8, .flags = .{ .bpp = .b2 } }, .physics = .{ .friction = Vec2f{ 0.15, 0.1 }, .gravity = Vec2f{ 0, 0.25 } }, @@ -210,12 +273,12 @@ pub fn start() !void { _ = try wires.resize(0); for (assets.wire) |wire| { - var w = wires.addOne() catch showErr("New wire"); + var w = try wires.addOne(); _ = try w.nodes.resize(0); const divisions = wire.divisions; var i: usize = 0; while (i <= divisions) : (i += 1) { - w.nodes.append(Pos.init(Vec2f{ 0, 0 })) catch showErr("Appending nodes"); + try w.nodes.append(Pos.init(Vec2f{ 0, 0 })); } w.begin().pos = util.vec2ToVec2f(wire.p1); w.end().pos = util.vec2ToVec2f(wire.p2); @@ -237,12 +300,12 @@ pub fn start() !void { try coins.resize(0); if (!try Disk.load()) { for (assets.coins) |coin| { - coins.append(.{ + try coins.append(.{ .pos = Pos.init(util.vec2ToVec2f(coin * tile_size)), .sprite = .{ .offset = .{ 0, 0 }, .size = .{ 8, 8 }, .index = 4, .flags = .{ .bpp = .b2 } }, .anim = Anim{ .anim = &anim_store.coin }, .area = .{ .pos = .{ 0, 0 }, .size = .{ 8, 8 } }, - }) catch showErr("Appending coin"); + }); } } @@ -252,6 +315,9 @@ pub fn start() !void { var indicator: ?Interaction = null; pub fn update(time: usize) !State { + // Clear the frame buffer + frame_fba.reset(); + for (wires.slice()) |*wire| { try wirePhysicsProcess(1, wire); if (wire.enabled) { @@ -387,8 +453,8 @@ pub fn update(time: usize) !State { } // Music - const musicCommand = try music.getNext(1); - for (musicCommand.constSlice()) |sfx| { + const musicCommand = try music.getNext(1, frame_alloc); + for (musicCommand.items) |sfx| { w4.tone(sfx.freq, sfx.duration, sfx.volume, sfx.flags); } @@ -539,9 +605,9 @@ fn updateCircuit() !void { const cellBegin = util.world2cell(nodes[0].pos); const cellEnd = util.world2cell(nodes[nodes.len - 1].pos); - try circuit.bridge(.{ cellBegin, cellEnd }, wireID); + circuit.bridge(.{ cellBegin, cellEnd }, wireID); } - _ = try circuit.fill(); + _ = try circuit.fill(frame_alloc); for (wires.slice()) |*wire| { const begin = wire.begin(); const end = wire.end(); @@ -551,8 +617,8 @@ fn updateCircuit() !void { (circuit.isEnabled(cellEnd) and end.pinned)) wire.enabled = true; } map.reset(&assets.solid); - const enabledDoors = try circuit.enabledDoors(); - for (enabledDoors.constSlice()) |door| { + const enabledDoors = try circuit.enabledDoors(frame_alloc); + for (enabledDoors.items) |door| { try map.set_cell(door, 0); } } @@ -698,7 +764,7 @@ fn controlProcess(_: f32, pos: *Pos, control: *Control, physics: *Physics, kinem fn kinematicProcess(_: f32, pos: *Pos, kinematic: *Kinematic) !void { var next = pos.last; next[0] = pos.pos[0]; - var hcol = try map.collide(kinematic.col.addv(next)); + var hcol = map.collide(kinematic.col.addv(next)); if (hcol.len > 0) { kinematic.lastCol[0] = next[0] - pos.last[0]; next[0] = pos.last[0]; @@ -707,7 +773,7 @@ fn kinematicProcess(_: f32, pos: *Pos, kinematic: *Kinematic) !void { } next[1] = pos.pos[1]; - var vcol = try map.collide(kinematic.col.addv(next)); + var vcol = map.collide(kinematic.col.addv(next)); if (vcol.len > 0) { kinematic.lastCol[1] = next[1] - pos.last[1]; next[1] = pos.last[1]; @@ -716,7 +782,7 @@ fn kinematicProcess(_: f32, pos: *Pos, kinematic: *Kinematic) !void { } var colPosAbs = next + kinematic.lastCol; - var lastCol = try map.collide(kinematic.col.addv(colPosAbs)); + var lastCol = map.collide(kinematic.col.addv(colPosAbs)); if (lastCol.len == 0) { kinematic.lastCol = Vec2f{ 0, 0 }; } diff --git a/src/main.zig b/src/main.zig index 4945b5c..f854997 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,7 +4,7 @@ const assets = @import("assets"); const input = @import("input.zig"); const util = @import("util.zig"); -const game = @import("rewrite.zig"); +const game = @import("game.zig"); const menu = @import("menu.zig"); pub const State = enum { @@ -29,8 +29,9 @@ export fn update() void { .Menu => menu.update(), .Game => game.update(time) catch |e| switch (e) { error.Overflow => showErr(@errorName(e)), - // error.OutOfBounds => showErr(@errorName(e)), - error.EndOfStream => showErr(@errorName(e)), + error.OutOfBounds => showErr(@errorName(e)), + // error.EndOfStream => showErr(@errorName(e)), + error.OutOfMemory => showErr(@errorName(e)), }, }; if (state != newState) { @@ -39,8 +40,8 @@ export fn update() void { .Menu => menu.start(), .Game => game.start() catch |e| switch (e) { // error.Overflow => showErr(@errorName(e)), - // error.OutOfBounds => showErr(@errorName(e)), - error.EndOfStream => showErr(@errorName(e)), + error.OutOfBounds => showErr(@errorName(e)), + // error.EndOfStream => showErr(@errorName(e)), error.OutOfMemory => showErr(@errorName(e)), error.NullTiles => showErr(@errorName(e)), }, diff --git a/src/map.zig b/src/map.zig index d30f8d8..0ccf8b1 100644 --- a/src/map.zig +++ b/src/map.zig @@ -118,7 +118,7 @@ pub const CollisionInfo = struct { }; } - pub fn append(col: CollisionInfo, item: util.AABB) void { + pub fn append(col: *CollisionInfo, item: util.AABB) void { std.debug.assert(col.len < 9); col.items[col.len] = item; col.len += 1; diff --git a/src/music.zig b/src/music.zig index 21bb252..eb60553 100644 --- a/src/music.zig +++ b/src/music.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const util = @import("util.zig"); const w4 = @import("wasm4.zig"); // Adapted from https://gist.github.com/YuxiUx/c3a8787209e32fc29fb48e8454f0009c @@ -89,9 +90,10 @@ pub const Procedural = struct { this.collect = .{ .score = score, .start = beatTotal + 1, .end = beatTotal + (this.beatsPerBar * length) + 1 }; } - pub fn getNext(this: *@This(), dt: u32) MusicCommand { - var i = 0; - var cmd: [4]Sfx = undefined; + pub fn getNext(this: *@This(), dt: u32, alloc: std.mem.Allocator) !util.Buffer(Sfx) { + var sfx_buf = try alloc.alloc(Sfx, 4); + var cmd = util.Buffer(Sfx).init(sfx_buf); + const beatProgress = this.tick % this.beat; const beatTotal = @divTrunc(this.tick, this.beat); const beat = beatTotal % this.beatsPerBar; @@ -102,13 +104,12 @@ pub const Procedural = struct { const playNote = if (collect.score < 6) beat % 2 == 0 else beat % 4 != 3; if (beatTotal >= collect.start and beatTotal < collect.end and playNote and beatProgress == 0) { // const notelen = @intCast(u8, this.beat * this.beatsPerBar); - cmd[i] = (Sfx{ + cmd.append(Sfx{ .freq = .{ .start = this.nextNote(this.note) }, .duration = .{ .sustain = 5, .release = 5 }, .volume = 25, .flags = .{ .channel = .pulse2, .mode = .p25 }, }); - i += 1; this.note += 1; } if (bar > collect.end) { @@ -117,31 +118,28 @@ pub const Procedural = struct { } } if (this.intensity.atLeast(.calm) and beat == 0 and beatProgress == 0) { - cmd[i] = (.{ + cmd.append (.{ .freq = .{ .start = 220, .end = 110 }, .duration = .{ .release = 3 }, .volume = 100, .flags = .{ .channel = .triangle }, }); - i += 1; } if (this.intensity.atLeast(.active) and beat == this.beatsPerBar / 2 and beatProgress == 0) { - cmd[i] = (.{ + cmd.append(.{ .freq = .{ .start = 110, .end = 55 }, .duration = .{ .release = 3 }, .volume = 100, .flags = .{ .channel = .triangle }, }); - i += 1; } if (this.walking and beat % 3 == 1 and beatProgress == 7) { - cmd[i] = (.{ + cmd.append(.{ .freq = .{ .start = 1761, .end = 1 }, .duration = .{ .release = 5 }, .volume = 25, .flags = .{ .channel = .noise }, }); - i += 1; } return cmd; } diff --git a/src/util.zig b/src/util.zig index b331384..390c00b 100644 --- a/src/util.zig +++ b/src/util.zig @@ -117,11 +117,11 @@ pub fn Buffer(comptime T: type) type { }; } - pub fn reset(buf: @This()) void { + pub fn reset(buf: *@This()) void { buf.len = 0; } - pub fn append(buf: @This(), item: T) void { + pub fn append(buf: *@This(), item: T) void { std.debug.assert(buf.len < buf.items.len); buf.items[buf.len] = item; buf.len += 1; diff --git a/src/world.zig b/src/world.zig index 1bd53ca..025a462 100644 --- a/src/world.zig +++ b/src/world.zig @@ -2,7 +2,8 @@ const std = @import("std"); -// Tile Storage Types +/// The CircuitType of a tile modifies how the tile responds to +/// electricity pub const CircuitType = enum(u4) { None = 0, Conduit = 1, @@ -51,36 +52,48 @@ pub const Level = struct { world_y: u8, width: u16, size: u16, + entity_count: u16, tiles: ?[]TileData, + entities: ?[]Entity = null, - pub fn init(x: u8, y: u8, width: u16, buf: []TileData) Level { + pub fn init(x: u8, y: u8, width: u16, buf: []TileData, entities: []Entity) Level { return Level{ .world_x = x, .world_y = y, .width = width, .size = buf.len, + .entity_count = entities.len, .tiles = buf, + .entities = entities, }; } pub fn write(level: Level, writer: anytype) !void { var tiles = level.tiles orelse return error.NullTiles; - try writer.writeInt(u8, level.world_x, .Big); - try writer.writeInt(u8, level.world_y, .Big); - try writer.writeInt(u16, level.width, .Big); - try writer.writeInt(u16, level.size, .Big); + 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(u16, level.width, .Little); + try writer.writeInt(u16, level.size, .Little); + try writer.writeInt(u16, level.entity_count, .Little); + for (tiles) |tile| { try writer.writeByte(tile.toByte()); } + + for (entities) |entity| { + try entity.write(writer); + } } pub fn read(reader: anytype) !Level { return Level{ - .world_x = try reader.readInt(u8, .Big), - .world_y = try reader.readInt(u8, .Big), - .width = try reader.readInt(u16, .Big), - .size = try reader.readInt(u16, .Big), + .world_x = try reader.readInt(u8, .Little), + .world_y = try reader.readInt(u8, .Little), + .width = try reader.readInt(u16, .Little), + .size = try reader.readInt(u16, .Little), .tiles = null, + .entities = null, }; } @@ -92,6 +105,25 @@ pub const Level = struct { buf[i] = TileData.fromByte(try reader.readByte()); } } + + pub fn readEntities(level: *Level, reader: anytype, buf: []Entity) !void { + std.debug.assert(buf.len >= level.entity_count); + level.entities = buf; + var i: usize = 0; + while (i < level.entity_count) : (i += 1) { + buf[i] = Entity.read(reader); + } + } + + pub fn getSpawn(level: *Level) ?[2]i16 { + std.debug.assert(level.entities != null); + for (level.entities) |entity| { + if (entity.kind == .Player) { + return [2]i16{ entity.x, entity.y }; + } + } + return null; + } }; // AutoTile algorithm datatypes @@ -192,3 +224,30 @@ pub const AutoTileset = struct { } } }; + +pub const EntityKind = enum(u8) { + Player, + Coin, + WireNode, + WireEndNode, + Door, + Trapdoor, +}; + +pub const Entity = struct { + kind: EntityKind, + x: i16, + y: i16, + + pub fn write(entity: Entity, writer: anytype) !void { + try writer.writeInt(u8, @enumToInt(entity.kind), .Little); + try writer.writeInt(i16, entity.x, .Little); + try writer.writeInt(i16, entity.y, .Little); + } + + pub fn read(entity: Entity, reader: anytype) !void { + try reader.readInt(u8, @intToEnum(EntityKind, entity.kind), .Little); + try reader.readInt(i16, entity.x, .Little); + try reader.readInt(i16, entity.y, .Little); + } +}; diff --git a/tools/LDtkImport.zig b/tools/LDtkImport.zig index e10cbd9..5ca0a6e 100644 --- a/tools/LDtkImport.zig +++ b/tools/LDtkImport.zig @@ -51,8 +51,10 @@ fn make(step: *std.build.Step) !void { defer data.deinit(); const writer = data.writer(); - const ldtk = try LDtk.parse(allocator, source); - defer ldtk.deinit(allocator); + var ldtk_parser = try LDtk.parse(allocator, source); + defer ldtk_parser.deinit(); + + const ldtk = ldtk_parser.root; if (ldtk.levels.len > 0) { const level0 = ldtk.levels[0]; @@ -60,23 +62,85 @@ fn make(step: *std.build.Step) !void { 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))); + 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); + for (layer.entityInstances) |entity| { - std.log.warn("{s}", .{entity.__identifier}); + 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 wire_begin = world.Entity{ + .kind = .WireNode, + .x = @intCast(i16, entity.__grid[0]), + .y = @intCast(i16, entity.__grid[1]), + }; + try entity_array.append(wire_begin); + + for (entity.fieldInstances) |field| { + if (std.mem.eql(u8, field.__identifier, "Point")) { + const end = field.__value.Array.items.len - 1; + const endpoint = field.__value.Array.items[end]; + // const jstr = switch (endpoint) { + // .Array => "Array", + // .Object => "Object", + // .Integer => "Integer", + // else => "Other", + // }; + // std.log.warn("{s}", .{jstr}); + // std.log.warn("{}", .{endpoint.Integer}); + // endpoint.dump(); + const x = endpoint.Object.get("cx").?; + const y = endpoint.Object.get("cy").?; + const wire_end = world.Entity{ + .kind = .WireEndNode, + .x = @intCast(i16, x.Integer), + .y = @intCast(i16, y.Integer), + }; + try entity_array.append(wire_end); + break; + } + } + } + } } } 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 }); } } @@ -100,13 +164,16 @@ fn make(step: *std.build.Step) !void { .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 straight tile data for (collision.autoLayerTiles) |autotile| { const x = @divExact(autotile.px[0], collision.__gridSize); const y = @divExact(autotile.px[1], collision.__gridSize); @@ -114,6 +181,7 @@ fn make(step: *std.build.Step) !void { tiles[i] = world.TileData{ .tile = @intCast(u7, autotile.t + 1) }; } + // Add circuit tiles for (circuit.intGridCsv) |cir64, i| { const cir = @intCast(u4, cir64); const col = collision.intGridCsv[i]; @@ -125,6 +193,7 @@ fn make(step: *std.build.Step) !void { } } + // Save the level! try level.write(writer); } }