diff --git a/src/game.zig b/src/game.zig index 06ae28b..3d3618b 100644 --- a/src/game.zig +++ b/src/game.zig @@ -207,6 +207,7 @@ fn loadLevel(lvl: usize) !void { map.clear(); circuit.clearMap(); level = try db.levelLoad(alloc, lvl); + const levelc = world.Coordinate.fromWorld(level.world_x, level.world_y); try extract.extractLevel(.{ .alloc = frame_alloc, @@ -225,9 +226,11 @@ fn loadLevel(lvl: usize) !void { { _ = try wires.resize(0); var a: usize = 0; - while (level.getWire(a)) |wire| : (a += 1) { - const p1 = util.vec2ToVec2f(Vec2{ wire[0].x, wire[0].y } * tile_size + Vec2{ 4, 4 }); - const p2 = util.vec2ToVec2f(Vec2{ wire[1].x, wire[1].y } * tile_size + Vec2{ 4, 4 }); + while (db.getWire(level, a)) |wire| : (a += 1) { + const coord0 = wire[0].coord.subC(levelc); + const coord1 = wire[1].coord.subC(levelc); + const p1 = util.vec2ToVec2f(coord0.toVec2() * tile_size + Vec2{ 4, 4 }); + const p2 = util.vec2ToVec2f(coord1.toVec2() * tile_size + Vec2{ 4, 4 }); var w = try wires.addOne(); _ = try w.nodes.resize(0); @@ -249,15 +252,16 @@ fn loadLevel(lvl: usize) !void { { var i: usize = 0; - while (level.getDoor(i)) |door| : (i += 1) { - try circuit.addDoor(Vec2{ door.x, door.y }); + while (db.getDoor(level, i)) |door| : (i += 1) { + const coord = door.coord.subC(levelc); + try circuit.addDoor(coord.toVec2()); } } { var i: usize = 0; while (level.getJoin(i)) |join| : (i += 1) { - const globalc = Coord.fromWorld(level.world_x, level.world_y).addC(join); + const globalc = levelc.addC(join); var e = false; if (db.isEnergized(globalc)) { e = true; @@ -270,9 +274,10 @@ fn loadLevel(lvl: usize) !void { try coins.resize(0); // if (!try Disk.load()) { var i: usize = 0; - while (level.getCoin(i)) |coin| : (i += 1) { + while (db.getCoin(level, i)) |coin| : (i += 1) { + const coord = coin.coord.subC(levelc); try coins.append(.{ - .pos = Pos.init(util.vec2ToVec2f(Vec2{ coin.x, coin.y } * tile_size)), + .pos = Pos.init(util.vec2ToVec2f(coord.toVec2() * 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 } }, @@ -342,18 +347,20 @@ pub fn start() !void { db = try world.Database.init(db_alloc); - try loadLevel(0); + const spawn = db.getSpawn(); - const spawnArr = level.getSpawn() orelse return error.NoPlayerSpawn; - const spawn = Vec2{ spawnArr[0], spawnArr[1] }; + const spawn_worldc = spawn.coord.toWorld(); + const first_level = db.findLevel(spawn_worldc[0], spawn_worldc[1]) orelse return error.SpawnOutOfBounds; - camera = @divTrunc(spawn, @splat(2, @as(i32, 20))) * @splat(2, @as(i32, 20)); + try loadLevel(first_level); + + camera = @divTrunc(spawn.coord.toVec2(), @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(spawn * tile_size + offset)), + .pos = Pos.init(util.vec2ToVec2f(spawn.coord.toVec2() * 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 } }, @@ -713,15 +720,19 @@ fn updateCircuit() !void { // Add doors to map var i: usize = 0; - while (level.getDoor(i)) |door| : (i += 1) { + while (db.getDoor(level, i)) |door| : (i += 1) { const tile: u8 = if (door.kind == .Door) world.Tiles.Door else world.Tiles.Trapdoor; - try map.set_cell(.{ door.x, door.y }, tile); + const globalc = world.Coordinate.fromWorld(level.world_x, level.world_y); + const coord = door.coord.subC(globalc); + w4.tracef("[getDoor] (%d, %d)", coord.val[0], coord.val[1]); + try map.set_cell(coord.toVec2(), tile); } // Remove doors that have been unlocked const enabledDoors = try circuit.enabledDoors(frame_alloc); defer frame_alloc.free(enabledDoors.items); for (enabledDoors.items) |door| { + w4.tracef("[enabledDoors] (%d, %d)", door[0], door[1]); try map.set_cell(door, world.Tiles.Empty); } diff --git a/src/main.zig b/src/main.zig index 68ddeff..89090a9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -50,7 +50,7 @@ export fn update() void { error.EndOfStream => showErr(@errorName(e)), error.OutOfMemory => showErr(@errorName(e)), error.NullTiles => showErr(@errorName(e)), - error.NoPlayerSpawn => showErr(@errorName(e)), + error.SpawnOutOfBounds => showErr(@errorName(e)), error.InvalidLevel => showErr(@errorName(e)), }, } diff --git a/src/world.zig b/src/world.zig index aa95cdb..692ce00 100644 --- a/src/world.zig +++ b/src/world.zig @@ -178,20 +178,37 @@ pub const Coordinate = struct { return .{ .val = .{ coord.val[0] + val[0], coord.val[1] + val[1] } }; } + pub fn sub(coord: Coordinate, val: [2]i16) Coordinate { + return .{ .val = .{ coord.val[0] - val[0], coord.val[1] - val[1] } }; + } + pub fn addC(coord: Coordinate, other: Coordinate) Coordinate { return .{ .val = .{ coord.val[0] + other.val[0], coord.val[1] + other.val[1] } }; } + pub fn subC(coord: Coordinate, other: Coordinate) Coordinate { + return .{ .val = .{ coord.val[0] - other.val[0], coord.val[1] - other.val[1] } }; + } + pub fn eq(coord: Coordinate, other: Coordinate) bool { return coord.val[0] == other.val[0] and coord.val[1] == other.val[1]; } + pub fn within(coord: Coord, nw: Coord, se: Coord) bool { + return coord.val[0] >= nw.val[0] and coord.val[1] >= nw.val[1] and + coord.val[0] < se.val[0] and coord.val[1] < se.val[1]; + } + pub fn toWorld(coord: Coordinate) [2]i8 { const world_x = @intCast(i8, @divFloor(coord.val[0], LEVELSIZE)); const world_y = @intCast(i8, @divFloor(coord.val[1], LEVELSIZE)); return .{ world_x, world_y }; } + pub fn toVec2(coord: Coordinate) @Vector(2, i32) { + return .{ coord.val[0], coord.val[1] }; + } + pub fn fromWorld(x: i8, y: i8) Coordinate { return .{ .val = .{ @intCast(i16, x) * 20, @@ -222,50 +239,37 @@ pub const Level = struct { world_y: i8, width: u16, size: u16, - entity_count: u16, tiles: ?[]TileData, - entities: ?[]Entity = null, - pub fn init(x: u8, y: u8, width: u16, buf: []TileData, entities: []Entity) Level { + pub fn init(x: u8, y: u8, width: u16, buf: []TileData) Level { return Level{ .world_x = x, .world_y = y, .width = width, .size = buf.len, - .entity_count = entities.len, .tiles = buf, - .entities = entities, }; } 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; + tiles.len; } 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(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); for (tiles) |tile| { try writer.writeByte(tile.toByte()); } - - for (entities) |entity| { - try entity.write(writer); - } } pub fn read(reader: anytype) !Level { @@ -274,9 +278,7 @@ pub const Level = struct { .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), .tiles = null, - .entities = null, }; } @@ -289,25 +291,6 @@ pub const Level = struct { } } - 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] = try 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; - } - pub fn getTile(level: Level, globalc: Coord) ?TileData { const tiles = level.tiles orelse return null; const worldc = globalc.toLevelTopLeft(); @@ -338,47 +321,6 @@ pub const Level = struct { } return null; } - - pub fn getWire(level: *Level, num: usize) ?[2]Entity { - std.debug.assert(level.entities != null); - var node_begin: ?Entity = null; - var wire_count: usize = 0; - for (level.entities.?) |entity| { - 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; - } - } - return null; - } - - pub fn getDoor(level: *Level, num: usize) ?Entity { - std.debug.assert(level.entities != null); - var count: usize = 0; - for (level.entities.?) |entity| { - if (entity.kind == .Door or entity.kind == .Trapdoor) { - if (count == num) return entity; - count += 1; - } - } - return null; - } - - pub fn getCoin(level: *Level, num: usize) ?Entity { - std.debug.assert(level.entities != null); - var count: usize = 0; - for (level.entities.?) |entity| { - if (entity.kind == .Coin) { - if (count == num) return entity; - count += 1; - } - } - return null; - } }; // AutoTile algorithm datatypes @@ -493,8 +435,7 @@ pub const EntityKind = enum(u8) { pub const Entity = struct { kind: EntityKind, - x: i16, - y: i16, + coord: Coordinate, pub fn calculateSize() usize { return @sizeOf(u8) + // kind @@ -504,15 +445,13 @@ pub const Entity = struct { 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); + try entity.coord.write(writer); } pub fn read(reader: anytype) !Entity { return Entity{ .kind = @intToEnum(EntityKind, try reader.readInt(u8, .Little)), - .x = try reader.readInt(i16, .Little), - .y = try reader.readInt(i16, .Little), + .coord = try Coordinate.read(reader), }; } }; @@ -546,11 +485,14 @@ pub const LevelHeader = struct { pub fn write( writer: anytype, level_headers: []LevelHeader, + entities: []Entity, circuit_nodes: []CircuitNode, levels: []Level, ) !void { // Write number of levels 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 circuit nodes try writer.writeInt(u16, @intCast(u16, circuit_nodes.len), .Little); @@ -559,6 +501,11 @@ pub fn write( try lvl_header.write(writer); } + // Write entity data + for (entities) |entity| { + try entity.write(writer); + } + // Write node data for (circuit_nodes) |node| { try node.write(writer); @@ -574,6 +521,7 @@ const Cursor = std.io.FixedBufferStream([]const u8); pub const Database = struct { cursor: Cursor, level_info: []LevelHeader, + entities: []Entity, circuit_info: []CircuitNode, level_data_begin: usize, @@ -589,6 +537,8 @@ pub const Database = struct { // read number of levels const level_count = try reader.readInt(u16, .Little); + // read number of entities + const entity_count = try reader.readInt(u16, .Little); // read number of nodes const node_count = try reader.readInt(u16, .Little); @@ -599,23 +549,34 @@ pub const Database = struct { level_headers[i] = try LevelHeader.read(reader); } + // read entities + var entities = try alloc.alloc(Entity, entity_count); + + for (entities) |_, i| { + entities[i] = try Entity.read(reader); + } + + // read circuits var circuit_nodes = try alloc.alloc(CircuitNode, node_count); - // read headers for (circuit_nodes) |_, i| { circuit_nodes[i] = try CircuitNode.read(reader); } + // Save where the rest of the data ended, and the level data begins var level_data_begin = @intCast(usize, try cursor.getPos()); return Database{ .cursor = cursor, .level_info = level_headers, + .entities = entities, .circuit_info = circuit_nodes, .level_data_begin = level_data_begin, }; } + // Level functions + pub fn levelInfo(db: *Database, level: usize) !Level { if (level > db.level_info.len) return error.InvalidLevel; try db.cursor.seekTo(db.level_data_begin + db.level_info[level].offset); @@ -632,9 +593,6 @@ pub const Database = struct { var level_buf = try alloc.alloc(TileData, level_info.size); try level_info.readTiles(reader, level_buf); - var entity_buf = try alloc.alloc(Entity, level_info.entity_count); - try level_info.readEntities(reader, entity_buf); - return level_info; } @@ -647,6 +605,8 @@ pub const Database = struct { return null; } + // Circuit functions + fn getNodeID(db: *Database, coord: Coord) ?NodeID { for (db.circuit_info) |node, i| { if (!coord.eq(node.coord)) continue; @@ -752,6 +712,66 @@ pub const Database = struct { if (i == 0) break; } } + + // Entity functions + + pub fn getWire(database: *Database, level: Level, num: usize) ?[2]Entity { + const nw = Coord.fromWorld(level.world_x, level.world_y); + const se = nw.add(.{ 20, 20 }); + var node_begin: ?Entity = 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; + } + } + return null; + } + + pub fn getDoor(database: *Database, level: Level, num: usize) ?Entity { + const nw = Coord.fromWorld(level.world_x, level.world_y); + const se = nw.add(.{ 20, 20 }); + var count: usize = 0; + for (database.entities) |entity| { + if (!entity.coord.within(nw, se)) continue; + if (entity.kind == .Door or entity.kind == .Trapdoor) { + if (count == num) return entity; + count += 1; + } + } + return null; + } + + pub fn getCoin(database: *Database, level: Level, num: usize) ?Entity { + const nw = Coord.fromWorld(level.world_x, level.world_y); + const se = nw.add(.{ 20, 20 }); + var count: usize = 0; + for (database.entities) |entity| { + if (!entity.coord.within(nw, se)) continue; + if (entity.kind == .Coin) { + if (count == num) return entity; + count += 1; + } + } + return null; + } + + /// Returns the players spawn location. + /// Assumes a spawn exists and that there is only one of them + pub fn getSpawn(database: *Database) Entity { + for (database.entities) |entity| { + if (entity.kind == .Player) { + return entity; + } + } + @panic("No player spawn found! Invalid state"); + } }; // All levels in the game. If two rooms are next to each other, they diff --git a/tools/LDtkImport.zig b/tools/LDtkImport.zig index e173294..47abcf2 100644 --- a/tools/LDtkImport.zig +++ b/tools/LDtkImport.zig @@ -55,10 +55,11 @@ fn make(step: *std.build.Step) !void { 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(); + var entity_array = std.ArrayList(world.Entity).init(allocator); + defer entity_array.deinit(); + for (ldtk.levels) |level| { + std.log.warn("Level: {}", .{levels.items.len}); const parsed_level = try parseLevel(.{ .allocator = allocator, .ldtk = ldtk, @@ -70,7 +71,6 @@ fn make(step: *std.build.Step) !void { } defer for (levels.items) |level| { allocator.free(level.tiles.?); - allocator.free(level.entities.?); }; var circuit = try buildCircuit(allocator, levels.items); @@ -109,7 +109,7 @@ fn make(step: *std.build.Step) !void { defer data.deinit(); const writer = data.writer(); - try world.write(writer, level_headers.items, circuit.items, levels.items); + try world.write(writer, level_headers.items, entity_array.items, circuit.items, levels.items); // Open output file and write data into it cwd.makePath(this.builder.getInstallPath(.lib, "")) catch |e| switch (e) { @@ -163,20 +163,28 @@ fn parseLevel(opt: struct { // 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, - .x = @intCast(i16, entity.__grid[0]), - .y = @intCast(i16, entity.__grid[1]), + .coord = levelc.addC(entc) }; 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; + 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; @@ -187,26 +195,28 @@ fn parseLevel(opt: struct { 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); + p2_c.val = .{ + @intCast(i16, x.Integer), + @intCast(i16, y.Integer), + }; } } const wire_begin = world.Entity{ .kind = if (anchor1) .WireAnchor else .WireNode, - .x = p1_x, - .y = p1_y, + .coord = p1_c.addC(levelc), }; try entity_array.append(wire_begin); const wire_end = world.Entity{ .kind = if (anchor2) .WireEndAnchor else .WireEndNode, - .x = p2_x, - .y = p2_y, + .coord = p2_c.addC(levelc), }; try entity_array.append(wire_end); } } } + + std.log.warn("Entities: {}", .{entity_array.items.len}); } else if (std.mem.eql(u8, layer.__identifier, "Circuit")) { // Circuit std.debug.assert(layer.__type == .IntGrid); @@ -235,14 +245,13 @@ fn parseLevel(opt: struct { const width = @intCast(u16, circuit.__cWid); const size = @intCast(u16, width * circuit.__cHei); + // Entities go into global scope now 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 = try allocator.alloc(world.TileData, size), - .entities = try allocator.dupe(world.Entity, entity_array.items), }; const tiles = parsed_level.tiles.?;