Flesh out world data format, make parseLevel fn
parent
9655abc120
commit
3fcd7e0942
|
@ -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 {
|
pub const World = struct {
|
||||||
/// All levels in the game. If two rooms are next to each other, they
|
/// 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
|
/// 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
|
/// I will need to freeze it and move it in a snake like fashion. Or just leave the
|
||||||
/// other level loaded.
|
/// other level loaded.
|
||||||
levels: []Level,
|
levels: []Level,
|
||||||
/// List of all circuit joins between levels. Levels can have multiple joins
|
/// An abstract representation of all circuits in game.
|
||||||
circuit_nodes: []CircuitNode,
|
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 {
|
pub const CircuitNode = struct {
|
||||||
energized: bool,
|
energized: bool,
|
||||||
kind: CircuitKind,
|
kind: NodeKind,
|
||||||
inputs: []usize,
|
inputs: []usize,
|
||||||
|
|
||||||
|
// pub fn write_header(node: CircuitNode, writer: anytype) !void {
|
||||||
|
// try writer.writeInt(u16, )
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CircuitKind = enum {
|
pub const NodeKind = union(enum) {
|
||||||
/// This join is conditional on logic state inside of the level
|
/// An And logic gate,
|
||||||
Logic,
|
And: [2]NodeID,
|
||||||
/// This join is a source of power
|
/// This node is a source of power
|
||||||
Source,
|
Source,
|
||||||
/// This node has no logic and provides no power
|
/// This node has no logic but can pass it on from another source
|
||||||
Conduit,
|
Conduit: NodeID,
|
||||||
// TODO: This type of node would be a wire stretching
|
/// This node represents a physical plug in the game world
|
||||||
// between multiple levels. This doesn't work with the rules
|
Plug: NodeID,
|
||||||
// for moving wires between levels at the moment.
|
|
||||||
// Bridge,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -57,148 +57,21 @@ fn make(step: *std.build.Step) !void {
|
||||||
const ldtk = ldtk_parser.root;
|
const ldtk = ldtk_parser.root;
|
||||||
|
|
||||||
if (ldtk.levels.len > 0) {
|
if (ldtk.levels.len > 0) {
|
||||||
const level0 = ldtk.levels[0];
|
const level = 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)));
|
|
||||||
|
|
||||||
var entity_array = std.ArrayList(world.Entity).init(allocator);
|
var entity_array = std.ArrayList(world.Entity).init(allocator);
|
||||||
defer entity_array.deinit();
|
defer entity_array.deinit();
|
||||||
|
|
||||||
var circuit_layer: ?LDtk.LayerInstance = null;
|
const parsed_level = try parseLevel(.{
|
||||||
var collision_layer: ?LDtk.LayerInstance = null;
|
.allocator = allocator,
|
||||||
for (layers) |layer| {
|
.ldtk = ldtk,
|
||||||
if (std.mem.eql(u8, layer.__identifier, "Entities")) {
|
.level = level,
|
||||||
// Entities
|
.entity_array = &entity_array,
|
||||||
std.debug.assert(layer.__type == .Entities);
|
});
|
||||||
|
defer allocator.free(parsed_level.tiles.?);
|
||||||
|
|
||||||
for (layer.entityInstances) |entity| {
|
// Save the level!
|
||||||
var kind_opt: ?world.EntityKind = null;
|
try parsed_level.write(writer);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open output file and write data
|
// Open output file and write data
|
||||||
|
@ -210,3 +83,156 @@ fn make(step: *std.build.Step) !void {
|
||||||
|
|
||||||
this.world_data.path = output;
|
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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue