diff --git a/src/blocks.zig b/src/blocks.zig index 4c1247d7..ffa9f47e 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -2,8 +2,11 @@ const std = @import("std"); const main = @import("main"); const Tag = main.Tag; +const utils = main.utils; const ZonElement = @import("zon.zig").ZonElement; -const Neighbor = @import("chunk.zig").Neighbor; +const chunk = @import("chunk.zig"); +const Neighbor = chunk.Neighbor; +const Chunk = chunk.Chunk; const graphics = @import("graphics.zig"); const SSBO = graphics.SSBO; const Image = graphics.Image; @@ -76,6 +79,7 @@ var _opaqueVariant: [maxBlockCount]u16 = undefined; var _friction: [maxBlockCount]f32 = undefined; var _allowOres: [maxBlockCount]bool = undefined; +var _tickEvent: [maxBlockCount]?TickEvent = undefined; var _touchFunction: [maxBlockCount]?*const TouchFunction = undefined; var _blockEntity: [maxBlockCount]?*BlockEntityType = undefined; @@ -124,7 +128,16 @@ pub fn register(_: []const u8, id: []const u8, zon: ZonElement) u16 { _hasBackFace[size] = zon.get(bool, "hasBackFace", false); _friction[size] = zon.get(f32, "friction", 20); _allowOres[size] = zon.get(bool, "allowOres", false); - _touchFunction[size] = TouchFunctions.getFunctionPointer(zon.get([]const u8, "touchFunction", "")); + _tickEvent[size] = TickEvent.loadFromZon(zon.getChild("tickEvent")); + + _touchFunction[size] = if(zon.get(?[]const u8, "touchFunction", null)) |touchFunctionName| blk: { + const _function = touchFunctions.getFunctionPointer(touchFunctionName); + if(_function == null) { + std.log.err("Could not find TouchFunction {s}!", .{touchFunctionName}); + } + break :blk _function; + } else null; + _blockEntity[size] = block_entity.getByID(zon.get(?[]const u8, "blockEntity", null)); const oreProperties = zon.getChild("ore"); @@ -382,6 +395,10 @@ pub const Block = packed struct { // MARK: Block return _allowOres[self.typ]; } + pub inline fn tickEvent(self: Block) ?TickEvent { + return _tickEvent[self.typ]; + } + pub inline fn touchFunction(self: Block) ?*const TouchFunction { return _touchFunction[self.typ]; } @@ -395,35 +412,49 @@ pub const Block = packed struct { // MARK: Block } }; -pub const TouchFunction = fn(block: Block, entity: Entity, posX: i32, posY: i32, posZ: i32, isEntityInside: bool) void; +// MARK: Tick +pub var tickFunctions: utils.NamedCallbacks(TickFunctions, TickFunction) = undefined; +pub const TickFunction = fn(block: Block, _chunk: *chunk.ServerChunk, x: i32, y: i32, z: i32) void; +pub const TickFunctions = struct { + pub fn replaceWithCobble(block: Block, _chunk: *chunk.ServerChunk, x: i32, y: i32, z: i32) void { + std.log.debug("Replace with cobblestone at ({d},{d},{d})", .{x, y, z}); + const cobblestone = parseBlock("cubyz:cobblestone"); -pub const TouchFunctions = struct { - var hashMap: std.StringHashMap(*const TouchFunction) = undefined; + const wx = _chunk.super.pos.wx + x; + const wy = _chunk.super.pos.wy + y; + const wz = _chunk.super.pos.wz + z; - pub fn init() void { - hashMap = .init(main.globalAllocator.allocator); - inline for(@typeInfo(TouchFunctions).@"struct".decls) |declaration| { - if(@TypeOf(@field(TouchFunctions, declaration.name)) == TouchFunction) { - hashMap.putNoClobber(declaration.name, &@field(TouchFunctions, declaration.name)) catch unreachable; - } - } - } - - pub fn deinit() void { - hashMap.deinit(); - } - - pub fn getFunctionPointer(id: []const u8) ?*const TouchFunction { - const pointer = hashMap.getPtr(id); - if(pointer == null) { - if(id.len != 0) - std.log.err("Could not find touch function {s}.", .{id}); - return null; - } - return pointer.?.*; + _ = main.server.world.?.cmpxchgBlock(wx, wy, wz, block, cobblestone); } }; +pub const TickEvent = struct { + function: *const TickFunction, + chance: f32, + + pub fn loadFromZon(zon: ZonElement) ?TickEvent { + const functionName = zon.get(?[]const u8, "name", null) orelse return null; + + const function = tickFunctions.getFunctionPointer(functionName) orelse { + std.log.err("Could not find TickFunction {s}.", .{functionName}); + return null; + }; + + return TickEvent{.function = function, .chance = zon.get(f32, "chance", 1)}; + } + + pub fn tryRandomTick(self: *const TickEvent, block: Block, _chunk: *chunk.ServerChunk, x: i32, y: i32, z: i32) void { + if(self.chance >= 1.0 or main.random.nextFloat(&main.seed) < self.chance) { + self.function(block, _chunk, x, y, z); + } + } +}; + +// MARK: Touch +pub var touchFunctions: utils.NamedCallbacks(TouchFunctions, TouchFunction) = undefined; +pub const TouchFunction = fn(block: Block, entity: Entity, posX: i32, posY: i32, posZ: i32, isEntityInside: bool) void; +pub const TouchFunctions = struct {}; + pub const meshes = struct { // MARK: meshes const AnimationData = extern struct { startFrame: u32, diff --git a/src/main.zig b/src/main.zig index 950f9405..a22c5ae3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -609,8 +609,11 @@ pub fn main() void { // MARK: main() block_entity.init(); defer block_entity.deinit(); - blocks.TouchFunctions.init(); - defer blocks.TouchFunctions.deinit(); + blocks.tickFunctions = .init(); + defer blocks.tickFunctions.deinit(); + + blocks.touchFunctions = .init(); + defer blocks.touchFunctions.deinit(); models.init(); defer models.deinit(); diff --git a/src/server/world.zig b/src/server/world.zig index 6eda8055..bbb4d933 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -393,6 +393,7 @@ const WorldIO = struct { // MARK: WorldIO self.world.spawn = worldData.get(Vec3i, "spawn", .{0, 0, 0}); self.world.biomeChecksum = worldData.get(i64, "biomeChecksum", 0); self.world.name = main.globalAllocator.dupe(u8, worldData.get([]const u8, "name", self.world.path)); + self.world.tickSpeed = worldData.get(u32, "tickSpeed", 12); } pub fn saveWorldData(self: WorldIO) !void { @@ -406,6 +407,7 @@ const WorldIO = struct { // MARK: WorldIO worldData.put("biomeChecksum", self.world.biomeChecksum); worldData.put("name", self.world.name); worldData.put("lastUsedTime", std.time.milliTimestamp()); + worldData.put("tickSpeed", self.world.tickSpeed); // TODO: Save entities try self.dir.writeZon("world.zig.zon", worldData); } @@ -426,6 +428,8 @@ pub const ServerWorld = struct { // MARK: ServerWorld lastUnimportantDataSent: i64, doGameTimeCycle: bool = true, + tickSpeed: u32 = 12, + defaultGamemode: main.game.Gamemode = undefined, allowCheats: bool = undefined, @@ -920,6 +924,42 @@ pub const ServerWorld = struct { // MARK: ServerWorld self.dropWithCooldown(stack, pos, dir, velocity, 0); } + fn tickBlocksInChunk(self: *ServerWorld, _chunk: *chunk.ServerChunk) void { + for(0..self.tickSpeed) |_| { + const blockIndex: i32 = main.random.nextInt(i32, &main.seed); + + const x: i32 = blockIndex >> chunk.chunkShift2 & chunk.chunkMask; + const y: i32 = blockIndex >> chunk.chunkShift & chunk.chunkMask; + const z: i32 = blockIndex & chunk.chunkMask; + + _chunk.mutex.lock(); + const block = _chunk.getBlock(x, y, z); + _chunk.mutex.unlock(); + if(block.tickEvent()) |event| { + event.tryRandomTick(block, _chunk, x, y, z); + } + } + } + + fn tick(self: *ServerWorld) void { + ChunkManager.mutex.lock(); + var iter = ChunkManager.entityChunkHashMap.valueIterator(); + var currentChunks: main.ListUnmanaged(*EntityChunk) = .initCapacity(main.stackAllocator, iter.len); + defer currentChunks.deinit(main.stackAllocator); + while(iter.next()) |entityChunk| { + entityChunk.*.increaseRefCount(); + currentChunks.append(main.stackAllocator, entityChunk.*); + } + ChunkManager.mutex.unlock(); + + // tick blocks + for(currentChunks.items) |entityChunk| { + defer entityChunk.decreaseRefCount(); + const ch = entityChunk.getChunk() orelse continue; + self.tickBlocksInChunk(ch); + } + } + pub fn update(self: *ServerWorld) void { // MARK: update() const newTime = std.time.milliTimestamp(); var deltaTime = @as(f32, @floatFromInt(newTime - self.lastUpdateTime))/1000.0; @@ -941,6 +981,7 @@ pub const ServerWorld = struct { // MARK: ServerWorld main.network.Protocols.genericUpdate.sendTime(user.conn, self); } } + self.tick(); // TODO: Entities // Item Entities diff --git a/src/utils.zig b/src/utils.zig index 3304fb07..97646064 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -2095,3 +2095,54 @@ fn CastFunctionReturnToAnyopaqueType(Fn: type) type { pub fn castFunctionReturnToAnyopaque(function: anytype) *const CastFunctionReturnToAnyopaqueType(@TypeOf(function)) { return @ptrCast(&function); } + +// MARK: Callback +pub fn NamedCallbacks(comptime Child: type, comptime Function: type) type { + return struct { + const Self = @This(); + + hashMap: std.StringHashMap(*const Function) = undefined, + + pub fn init() Self { + var self = Self{.hashMap = .init(main.globalAllocator.allocator)}; + inline for(@typeInfo(Child).@"struct".decls) |declaration| { + if(@TypeOf(@field(Child, declaration.name)) == Function) { + std.log.debug("Registered Callback '{s}'", .{declaration.name}); + self.hashMap.putNoClobber(declaration.name, &@field(Child, declaration.name)) catch unreachable; + } + } + return self; + } + + pub fn deinit(self: *Self) void { + self.hashMap.deinit(); + } + + pub fn getFunctionPointer(self: *Self, id: []const u8) ?*const Function { + return self.hashMap.get(id); + } + }; +} + +test "NamedCallbacks registers functions" { + const TestFunction = fn(_: i32) void; + const TestFunctions = struct { + // Callback should register this + pub fn testFunction(_: i32) void {} + pub fn otherTestFunction(_: i32) void {} + // Callback should ignore this + pub fn wrongSignatureFunction(_: i32, _: bool) void {} + }; + var testFunctions: NamedCallbacks(TestFunctions, TestFunction) = undefined; + + testFunctions = .init(); + defer testFunctions.deinit(); + + try std.testing.expectEqual(2, testFunctions.hashMap.count()); + + try std.testing.expectEqual(&TestFunctions.testFunction, testFunctions.getFunctionPointer("testFunction").?); + try std.testing.expectEqual(&TestFunctions.otherTestFunction, testFunctions.getFunctionPointer("otherTestFunction").?); + + try std.testing.expectEqual(null, testFunctions.getFunctionPointer("functionTest")); + try std.testing.expectEqual(null, testFunctions.getFunctionPointer("wrongSignatureFunction")); +}