Add random tick events (#1338)

This pull request adds random tick events for blocks. Tick events can be
added by using the `tickEvent` trait and contain the `name` of the
callback function and the probability ( `chance`).

Resolves: https://github.com/PixelGuys/Cubyz/issues/77

---------

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
This commit is contained in:
ChibChi 2025-05-19 20:55:42 +02:00 committed by GitHub
parent 2f48161712
commit 0e1aa68174
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 154 additions and 28 deletions

View File

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

View File

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

View File

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

View File

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