Begin converting game.zig to use ldtk data

master
Louis Pearson 2022-08-04 17:18:34 -06:00
parent b5ca48e1b3
commit 84b5e4cb41
9 changed files with 265 additions and 71 deletions

2
deps/zig-ldtk vendored

@ -1 +1 @@
Subproject commit 91d78d9c52f4f00f906b89bad8ff27fe295dd1f6 Subproject commit e93618602d79d1e998a10c0ce1b4473459b19ff4

View File

@ -234,17 +234,17 @@ pub fn enable(this: *@This(), cell: Cell) void {
this.levels[i] += 1; 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[0])) |_| {
if (this.indexOf(cells[1])) |_| { 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)) |_| { 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) { pub fn enabledDoors(this: @This(), alloc: std.mem.Allocator) !util.Buffer(Cell) {
var items = try alloc.alloc(Cell, this.doors.len); 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| { for (this.doors.items) |d| {
if (d.enabled) buffer.append(d.cell); 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 { pub fn clear(this: *@This()) void {
std.mem.set(u8, this.levels, 0); std.mem.set(u8, this.levels, 0);
for (this.doors.slice()) |*door| { for (this.doors.items) |*door| {
door.enabled = false; door.enabled = false;
} }
// Resizing to zero should always work this.bridges.reset();
this.bridges.resize(0) catch unreachable;
} }
pub fn reset(this: *@This()) void { pub fn reset(this: *@This()) void {
@ -303,7 +302,7 @@ pub fn reset(this: *@This()) void {
} }
const w4 = @import("wasm4.zig"); const w4 = @import("wasm4.zig");
const Queue = util.Queue(Cell, MAXCELLS); const Queue = util.Queue(Cell);
// Returns number of cells filled // Returns number of cells filled
pub fn fill(this: *@This(), alloc: std.mem.Allocator) !usize { pub fn fill(this: *@This(), alloc: std.mem.Allocator) !usize {
var count: usize = 0; 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 visited = util.Buffer(usize).init(items);
var q = try Queue.init(); var q_buf = try alloc.alloc(Cell, MAXCELLS);
for (this.sources.slice()) |source| { var q = Queue.init(q_buf);
for (this.sources.items) |source| {
try q.insert(source); try q.insert(source);
} }
while (q.remove()) |cell| { while (q.remove()) |cell| {
const tile = this.get_cell(cell) orelse { const tile = this.get_cell(cell) orelse {
for (this.doors.slice()) |*d| { for (this.doors.items) |*d| {
if (@reduce(.And, d.cell == cell)) { if (@reduce(.And, d.cell == cell)) {
d.enabled = true; d.enabled = true;
} }
@ -328,9 +329,9 @@ pub fn fill(this: *@This(), alloc: std.mem.Allocator) !usize {
}; };
const index = this.indexOf(cell) orelse continue; const index = this.indexOf(cell) orelse continue;
this.enable(cell); 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; if (hasVisited and !is_logic(tile)) continue;
try visited.append(index); visited.append(index);
count += 1; count += 1;
if (get_logic(tile)) |logic| { if (get_logic(tile)) |logic| {
// TODO: implement other logic (though I'm pretty sure that requires a graph...) // 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]) nextCell[1] >= this.map_size[1])
continue; continue;
const nextTile = this.get_cell(nextCell) orelse here: { const nextTile = this.get_cell(nextCell) orelse here: {
for (this.doors.slice()) |*d| { for (this.doors.items) |*d| {
if (@reduce(.And, d.cell == nextCell)) { if (@reduce(.And, d.cell == nextCell)) {
d.enabled = true; d.enabled = true;
} }
@ -360,7 +361,7 @@ pub fn fill(this: *@This(), alloc: std.mem.Allocator) !usize {
try q.insert(nextCell); try q.insert(nextCell);
} }
if (is_plug(tile)) { if (is_plug(tile)) {
for (this.bridges.slice()) |*b| { for (this.bridges.items) |*b| {
if (@reduce(.And, b.cells[0] == cell)) { if (@reduce(.And, b.cells[0] == cell)) {
try q.insert(b.cells[1]); try q.insert(b.cells[1]);
b.enabled = true; b.enabled = true;

View File

@ -1,6 +1,5 @@
const std = @import("std"); const std = @import("std");
const w4 = @import("wasm4.zig"); const w4 = @import("wasm4.zig");
const assets = @import("assets");
const input = @import("input.zig"); const input = @import("input.zig");
const util = @import("util.zig"); const util = @import("util.zig");
const Circuit = @import("circuit.zig"); const Circuit = @import("circuit.zig");
@ -8,6 +7,9 @@ const Map = @import("map.zig");
const Music = @import("music.zig"); const Music = @import("music.zig");
const State = @import("main.zig").State; const State = @import("main.zig").State;
const Disk = @import("disk.zig"); 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 Vec2 = util.Vec2;
const Vec2f = util.Vec2f; const Vec2f = util.Vec2f;
@ -136,6 +138,15 @@ fn randRangeF(min: f32, max: f32) f32 {
return min + (random.float(f32) * (max - min)); 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 // Global vars
var map: Map = undefined; var map: Map = undefined;
var circuit: Circuit = undefined; var circuit: Circuit = undefined;
@ -156,9 +167,12 @@ var ScoreCoin = Sprite{
.flags = .{ .bpp = .b2 }, .flags = .{ .bpp = .b2 },
}; };
var solids_mutable = assets.solid; var map_buf: [400]u8 = undefined;
pub var conduit_mutable = assets.conduit;
var conduitLevels_mutable: [conduit_mutable.len]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 { pub const anim_store = struct {
const stand = Anim.frame(8); const stand = Anim.frame(8);
@ -187,17 +201,66 @@ fn showErr(msg: []const u8) noreturn {
pub fn start() !void { pub fn start() !void {
particles = try ParticleSystem.init(); particles = try ParticleSystem.init();
std.mem.set(u8, &conduitLevels_mutable, 0); var level_size = Vec2{ 20, 20 };
circuit = try Circuit.init(&conduit_mutable, &conduitLevels_mutable, assets.conduit_size);
map = Map.init(&solids_mutable, assets.solid_size);
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 tile_size = Vec2{ 8, 8 };
const offset = Vec2{ 4, 8 }; const offset = Vec2{ 4, 8 };
player = .{ 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 }, .control = .{ .controller = .player, .state = .stand },
.sprite = .{ .offset = .{ -4, -8 }, .size = .{ 8, 8 }, .index = 8, .flags = .{ .bpp = .b2 } }, .sprite = .{ .offset = .{ -4, -8 }, .size = .{ 8, 8 }, .index = 8, .flags = .{ .bpp = .b2 } },
.physics = .{ .friction = Vec2f{ 0.15, 0.1 }, .gravity = Vec2f{ 0, 0.25 } }, .physics = .{ .friction = Vec2f{ 0.15, 0.1 }, .gravity = Vec2f{ 0, 0.25 } },
@ -210,12 +273,12 @@ pub fn start() !void {
_ = try wires.resize(0); _ = try wires.resize(0);
for (assets.wire) |wire| { for (assets.wire) |wire| {
var w = wires.addOne() catch showErr("New wire"); var w = try wires.addOne();
_ = try w.nodes.resize(0); _ = try w.nodes.resize(0);
const divisions = wire.divisions; const divisions = wire.divisions;
var i: usize = 0; var i: usize = 0;
while (i <= divisions) : (i += 1) { 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.begin().pos = util.vec2ToVec2f(wire.p1);
w.end().pos = util.vec2ToVec2f(wire.p2); w.end().pos = util.vec2ToVec2f(wire.p2);
@ -237,12 +300,12 @@ pub fn start() !void {
try coins.resize(0); try coins.resize(0);
if (!try Disk.load()) { if (!try Disk.load()) {
for (assets.coins) |coin| { for (assets.coins) |coin| {
coins.append(.{ try coins.append(.{
.pos = Pos.init(util.vec2ToVec2f(coin * tile_size)), .pos = Pos.init(util.vec2ToVec2f(coin * tile_size)),
.sprite = .{ .offset = .{ 0, 0 }, .size = .{ 8, 8 }, .index = 4, .flags = .{ .bpp = .b2 } }, .sprite = .{ .offset = .{ 0, 0 }, .size = .{ 8, 8 }, .index = 4, .flags = .{ .bpp = .b2 } },
.anim = Anim{ .anim = &anim_store.coin }, .anim = Anim{ .anim = &anim_store.coin },
.area = .{ .pos = .{ 0, 0 }, .size = .{ 8, 8 } }, .area = .{ .pos = .{ 0, 0 }, .size = .{ 8, 8 } },
}) catch showErr("Appending coin"); });
} }
} }
@ -252,6 +315,9 @@ pub fn start() !void {
var indicator: ?Interaction = null; var indicator: ?Interaction = null;
pub fn update(time: usize) !State { pub fn update(time: usize) !State {
// Clear the frame buffer
frame_fba.reset();
for (wires.slice()) |*wire| { for (wires.slice()) |*wire| {
try wirePhysicsProcess(1, wire); try wirePhysicsProcess(1, wire);
if (wire.enabled) { if (wire.enabled) {
@ -387,8 +453,8 @@ pub fn update(time: usize) !State {
} }
// Music // Music
const musicCommand = try music.getNext(1); const musicCommand = try music.getNext(1, frame_alloc);
for (musicCommand.constSlice()) |sfx| { for (musicCommand.items) |sfx| {
w4.tone(sfx.freq, sfx.duration, sfx.volume, sfx.flags); 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 cellBegin = util.world2cell(nodes[0].pos);
const cellEnd = util.world2cell(nodes[nodes.len - 1].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| { for (wires.slice()) |*wire| {
const begin = wire.begin(); const begin = wire.begin();
const end = wire.end(); const end = wire.end();
@ -551,8 +617,8 @@ fn updateCircuit() !void {
(circuit.isEnabled(cellEnd) and end.pinned)) wire.enabled = true; (circuit.isEnabled(cellEnd) and end.pinned)) wire.enabled = true;
} }
map.reset(&assets.solid); map.reset(&assets.solid);
const enabledDoors = try circuit.enabledDoors(); const enabledDoors = try circuit.enabledDoors(frame_alloc);
for (enabledDoors.constSlice()) |door| { for (enabledDoors.items) |door| {
try map.set_cell(door, 0); 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 { fn kinematicProcess(_: f32, pos: *Pos, kinematic: *Kinematic) !void {
var next = pos.last; var next = pos.last;
next[0] = pos.pos[0]; 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) { if (hcol.len > 0) {
kinematic.lastCol[0] = next[0] - pos.last[0]; kinematic.lastCol[0] = next[0] - pos.last[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]; 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) { if (vcol.len > 0) {
kinematic.lastCol[1] = next[1] - pos.last[1]; kinematic.lastCol[1] = next[1] - pos.last[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 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) { if (lastCol.len == 0) {
kinematic.lastCol = Vec2f{ 0, 0 }; kinematic.lastCol = Vec2f{ 0, 0 };
} }

View File

@ -4,7 +4,7 @@ const assets = @import("assets");
const input = @import("input.zig"); const input = @import("input.zig");
const util = @import("util.zig"); const util = @import("util.zig");
const game = @import("rewrite.zig"); const game = @import("game.zig");
const menu = @import("menu.zig"); const menu = @import("menu.zig");
pub const State = enum { pub const State = enum {
@ -29,8 +29,9 @@ export fn update() void {
.Menu => menu.update(), .Menu => menu.update(),
.Game => game.update(time) catch |e| switch (e) { .Game => game.update(time) catch |e| switch (e) {
error.Overflow => showErr(@errorName(e)), error.Overflow => showErr(@errorName(e)),
// error.OutOfBounds => showErr(@errorName(e)), error.OutOfBounds => showErr(@errorName(e)),
error.EndOfStream => showErr(@errorName(e)), // error.EndOfStream => showErr(@errorName(e)),
error.OutOfMemory => showErr(@errorName(e)),
}, },
}; };
if (state != newState) { if (state != newState) {
@ -39,8 +40,8 @@ export fn update() void {
.Menu => menu.start(), .Menu => menu.start(),
.Game => game.start() catch |e| switch (e) { .Game => game.start() catch |e| switch (e) {
// error.Overflow => showErr(@errorName(e)), // error.Overflow => showErr(@errorName(e)),
// error.OutOfBounds => showErr(@errorName(e)), error.OutOfBounds => showErr(@errorName(e)),
error.EndOfStream => showErr(@errorName(e)), // error.EndOfStream => showErr(@errorName(e)),
error.OutOfMemory => showErr(@errorName(e)), error.OutOfMemory => showErr(@errorName(e)),
error.NullTiles => showErr(@errorName(e)), error.NullTiles => showErr(@errorName(e)),
}, },

View File

@ -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); std.debug.assert(col.len < 9);
col.items[col.len] = item; col.items[col.len] = item;
col.len += 1; col.len += 1;

View File

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const util = @import("util.zig");
const w4 = @import("wasm4.zig"); const w4 = @import("wasm4.zig");
// Adapted from https://gist.github.com/YuxiUx/c3a8787209e32fc29fb48e8454f0009c // 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 }; this.collect = .{ .score = score, .start = beatTotal + 1, .end = beatTotal + (this.beatsPerBar * length) + 1 };
} }
pub fn getNext(this: *@This(), dt: u32) MusicCommand { pub fn getNext(this: *@This(), dt: u32, alloc: std.mem.Allocator) !util.Buffer(Sfx) {
var i = 0; var sfx_buf = try alloc.alloc(Sfx, 4);
var cmd: [4]Sfx = undefined; var cmd = util.Buffer(Sfx).init(sfx_buf);
const beatProgress = this.tick % this.beat; const beatProgress = this.tick % this.beat;
const beatTotal = @divTrunc(this.tick, this.beat); const beatTotal = @divTrunc(this.tick, this.beat);
const beat = beatTotal % this.beatsPerBar; 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; 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) { if (beatTotal >= collect.start and beatTotal < collect.end and playNote and beatProgress == 0) {
// const notelen = @intCast(u8, this.beat * this.beatsPerBar); // const notelen = @intCast(u8, this.beat * this.beatsPerBar);
cmd[i] = (Sfx{ cmd.append(Sfx{
.freq = .{ .start = this.nextNote(this.note) }, .freq = .{ .start = this.nextNote(this.note) },
.duration = .{ .sustain = 5, .release = 5 }, .duration = .{ .sustain = 5, .release = 5 },
.volume = 25, .volume = 25,
.flags = .{ .channel = .pulse2, .mode = .p25 }, .flags = .{ .channel = .pulse2, .mode = .p25 },
}); });
i += 1;
this.note += 1; this.note += 1;
} }
if (bar > collect.end) { if (bar > collect.end) {
@ -117,31 +118,28 @@ pub const Procedural = struct {
} }
} }
if (this.intensity.atLeast(.calm) and beat == 0 and beatProgress == 0) { if (this.intensity.atLeast(.calm) and beat == 0 and beatProgress == 0) {
cmd[i] = (.{ cmd.append (.{
.freq = .{ .start = 220, .end = 110 }, .freq = .{ .start = 220, .end = 110 },
.duration = .{ .release = 3 }, .duration = .{ .release = 3 },
.volume = 100, .volume = 100,
.flags = .{ .channel = .triangle }, .flags = .{ .channel = .triangle },
}); });
i += 1;
} }
if (this.intensity.atLeast(.active) and beat == this.beatsPerBar / 2 and beatProgress == 0) { if (this.intensity.atLeast(.active) and beat == this.beatsPerBar / 2 and beatProgress == 0) {
cmd[i] = (.{ cmd.append(.{
.freq = .{ .start = 110, .end = 55 }, .freq = .{ .start = 110, .end = 55 },
.duration = .{ .release = 3 }, .duration = .{ .release = 3 },
.volume = 100, .volume = 100,
.flags = .{ .channel = .triangle }, .flags = .{ .channel = .triangle },
}); });
i += 1;
} }
if (this.walking and beat % 3 == 1 and beatProgress == 7) { if (this.walking and beat % 3 == 1 and beatProgress == 7) {
cmd[i] = (.{ cmd.append(.{
.freq = .{ .start = 1761, .end = 1 }, .freq = .{ .start = 1761, .end = 1 },
.duration = .{ .release = 5 }, .duration = .{ .release = 5 },
.volume = 25, .volume = 25,
.flags = .{ .channel = .noise }, .flags = .{ .channel = .noise },
}); });
i += 1;
} }
return cmd; return cmd;
} }

View File

@ -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; 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); std.debug.assert(buf.len < buf.items.len);
buf.items[buf.len] = item; buf.items[buf.len] = item;
buf.len += 1; buf.len += 1;

View File

@ -2,7 +2,8 @@
const std = @import("std"); const std = @import("std");
// Tile Storage Types /// The CircuitType of a tile modifies how the tile responds to
/// electricity
pub const CircuitType = enum(u4) { pub const CircuitType = enum(u4) {
None = 0, None = 0,
Conduit = 1, Conduit = 1,
@ -51,36 +52,48 @@ pub const Level = struct {
world_y: u8, world_y: u8,
width: u16, width: u16,
size: u16, size: u16,
entity_count: u16,
tiles: ?[]TileData, 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{ return Level{
.world_x = x, .world_x = x,
.world_y = y, .world_y = y,
.width = width, .width = width,
.size = buf.len, .size = buf.len,
.entity_count = entities.len,
.tiles = buf, .tiles = buf,
.entities = entities,
}; };
} }
pub fn write(level: Level, writer: anytype) !void { pub fn write(level: Level, writer: anytype) !void {
var tiles = level.tiles orelse return error.NullTiles; var tiles = level.tiles orelse return error.NullTiles;
try writer.writeInt(u8, level.world_x, .Big); var entities = level.entities orelse return error.NullEntities;
try writer.writeInt(u8, level.world_y, .Big); try writer.writeInt(u8, level.world_x, .Little);
try writer.writeInt(u16, level.width, .Big); try writer.writeInt(u8, level.world_y, .Little);
try writer.writeInt(u16, level.size, .Big); 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| { for (tiles) |tile| {
try writer.writeByte(tile.toByte()); try writer.writeByte(tile.toByte());
} }
for (entities) |entity| {
try entity.write(writer);
}
} }
pub fn read(reader: anytype) !Level { pub fn read(reader: anytype) !Level {
return Level{ return Level{
.world_x = try reader.readInt(u8, .Big), .world_x = try reader.readInt(u8, .Little),
.world_y = try reader.readInt(u8, .Big), .world_y = try reader.readInt(u8, .Little),
.width = try reader.readInt(u16, .Big), .width = try reader.readInt(u16, .Little),
.size = try reader.readInt(u16, .Big), .size = try reader.readInt(u16, .Little),
.tiles = null, .tiles = null,
.entities = null,
}; };
} }
@ -92,6 +105,25 @@ pub const Level = struct {
buf[i] = TileData.fromByte(try reader.readByte()); 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 // 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);
}
};

View File

@ -51,8 +51,10 @@ fn make(step: *std.build.Step) !void {
defer data.deinit(); defer data.deinit();
const writer = data.writer(); const writer = data.writer();
const ldtk = try LDtk.parse(allocator, source); var ldtk_parser = try LDtk.parse(allocator, source);
defer ldtk.deinit(allocator); defer ldtk_parser.deinit();
const ldtk = ldtk_parser.root;
if (ldtk.levels.len > 0) { if (ldtk.levels.len > 0) {
const level0 = ldtk.levels[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_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 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 circuit_layer: ?LDtk.LayerInstance = null;
var collision_layer: ?LDtk.LayerInstance = null; var collision_layer: ?LDtk.LayerInstance = null;
for (layers) |layer| { for (layers) |layer| {
if (std.mem.eql(u8, layer.__identifier, "Entities")) { if (std.mem.eql(u8, layer.__identifier, "Entities")) {
// Entities
std.debug.assert(layer.__type == .Entities); std.debug.assert(layer.__type == .Entities);
for (layer.entityInstances) |entity| { 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")) { } else if (std.mem.eql(u8, layer.__identifier, "Circuit")) {
// Circuit
std.debug.assert(layer.__type == .IntGrid); std.debug.assert(layer.__type == .IntGrid);
circuit_layer = layer; circuit_layer = layer;
} else if (std.mem.eql(u8, layer.__identifier, "Collision")) { } else if (std.mem.eql(u8, layer.__identifier, "Collision")) {
// Collision
std.debug.assert(layer.__type == .IntGrid); std.debug.assert(layer.__type == .IntGrid);
collision_layer = layer; collision_layer = layer;
} else { } else {
// Unknown
std.log.warn("{s}: {}", .{ layer.__identifier, layer.__type }); std.log.warn("{s}: {}", .{ layer.__identifier, layer.__type });
} }
} }
@ -100,13 +164,16 @@ fn make(step: *std.build.Step) !void {
.world_y = world_y, .world_y = world_y,
.width = @intCast(u16, width), .width = @intCast(u16, width),
.size = @intCast(u16, size), .size = @intCast(u16, size),
.entity_count = @intCast(u16, entity_array.items.len),
.tiles = null, .tiles = null,
.entities = entity_array.items,
}; };
level.tiles = try allocator.alloc(world.TileData, size); level.tiles = try allocator.alloc(world.TileData, size);
defer allocator.free(level.tiles.?); defer allocator.free(level.tiles.?);
const tiles = level.tiles.?; const tiles = level.tiles.?;
// Add straight tile data
for (collision.autoLayerTiles) |autotile| { for (collision.autoLayerTiles) |autotile| {
const x = @divExact(autotile.px[0], collision.__gridSize); const x = @divExact(autotile.px[0], collision.__gridSize);
const y = @divExact(autotile.px[1], 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) }; tiles[i] = world.TileData{ .tile = @intCast(u7, autotile.t + 1) };
} }
// Add circuit tiles
for (circuit.intGridCsv) |cir64, i| { for (circuit.intGridCsv) |cir64, i| {
const cir = @intCast(u4, cir64); const cir = @intCast(u4, cir64);
const col = collision.intGridCsv[i]; const col = collision.intGridCsv[i];
@ -125,6 +193,7 @@ fn make(step: *std.build.Step) !void {
} }
} }
// Save the level!
try level.write(writer); try level.write(writer);
} }
} }