Database now handles entities

master
Louis Pearson 2022-08-08 18:00:25 -06:00
parent a35d481b15
commit 240587344d
4 changed files with 162 additions and 122 deletions

View File

@ -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);
}

View File

@ -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)),
},
}

View File

@ -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

View File

@ -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();
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.?;