diff --git a/assets/cubyz/blocks/torch.zig.zon b/assets/cubyz/blocks/torch.zig.zon index 2ad108c8d8..aece574d97 100644 --- a/assets/cubyz/blocks/torch.zig.zon +++ b/assets/cubyz/blocks/torch.zig.zon @@ -13,6 +13,12 @@ .onUpdate = .{ .type = .check_support_blocks, }, + .onInteract = .{ + .type = .torch_update_particles, + }, + .onClientUpdate = .{ + .type = .torch_update_particles, + }, .model = .{ .base = "cubyz:torch", .side = "cubyz:torch_side", diff --git a/assets/cubyz/particles/flame.zig.zon b/assets/cubyz/particles/flame.zig.zon index b5a0c4fbce..cbcf712ea0 100644 --- a/assets/cubyz/particles/flame.zig.zon +++ b/assets/cubyz/particles/flame.zig.zon @@ -1,6 +1,6 @@ .{ .texture = "cubyz:flame", .density = .{0.00065, 0.00085}, - .rotationVelocity = .{20, 35}, + .rotationVelocity = .{5, 10}, .dragCoefficient = .{0.2, 0.3}, } diff --git a/src/blocks.zig b/src/blocks.zig index 4ac584866f..ff2e723976 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -155,6 +155,7 @@ var _mobility: [maxBlockCount]f32 = undefined; var _allowOres: [maxBlockCount]bool = undefined; var _onTick: [maxBlockCount]ServerBlockCallback = undefined; var _onTouch: [maxBlockCount]BlockTouchCallback = undefined; +var _onClientUpdate: [maxBlockCount]ClientBlockCallback = undefined; var _blockEntity: [maxBlockCount]?*const BlockEntityType = undefined; var reverseIndices: std.StringHashMapUnmanaged(u16) = .{}; @@ -329,6 +330,12 @@ fn registerCallbacks(typ: u16, zon: ZonElement) void { break :blk .noop; }; }; + _onClientUpdate[typ] = blk: { + break :blk ClientBlockCallback.init(zon.getChildOrNull("onClientUpdate") orelse break :blk .noop, .{.block = .{.typ = typ, .data = 0}}) orelse { + std.log.err("Failed to load onClientUpdate event for block {s}", .{_id[typ]}); + break :blk .noop; + }; + }; } pub fn finishBlocks(zonElements: Assets.ZonHashMap) void { @@ -569,6 +576,10 @@ pub const Block = packed struct(u32) { // MARK: Block return _onTouch[self.typ]; } + pub inline fn onClientUpdate(self: Block) ClientBlockCallback { + return _onClientUpdate[self.typ]; + } + pub fn blockEntity(self: Block) ?*const BlockEntityType { return _blockEntity[self.typ]; } diff --git a/src/callbacks/block/client/_list.zig b/src/callbacks/block/client/_list.zig index 9a77fb7eb6..16ea67c5ca 100644 --- a/src/callbacks/block/client/_list.zig +++ b/src/callbacks/block/client/_list.zig @@ -1,3 +1,4 @@ pub const edit_sign = @import("edit_sign.zig"); pub const open_chest = @import("open_chest.zig"); pub const open_window = @import("open_window.zig"); +pub const torch_update_particles = @import("torch_update_particles.zig"); diff --git a/src/callbacks/block/client/torch_update_particles.zig b/src/callbacks/block/client/torch_update_particles.zig new file mode 100644 index 0000000000..c2a50297e2 --- /dev/null +++ b/src/callbacks/block/client/torch_update_particles.zig @@ -0,0 +1,24 @@ +const std = @import("std"); + +const main = @import("main"); +const Block = main.blocks.Block; +const vec = main.vec; +const Vec3i = vec.Vec3i; +const ZonElement = main.ZonElement; +const particles = main.particles; + +pub fn init(_: ZonElement, _: main.callbacks.Creator) ?*anyopaque { + return @as(*anyopaque, undefined); +} + +pub fn run(_: *anyopaque, params: main.callbacks.ClientBlockCallback.Params) main.callbacks.Result { + var emitter: particles.Emitter = undefined; + const emitterProperties = particles.EmitterProperties{ + .speed = .init(0.15, 0.2), + .lifeTime = .init(0.25, 0.5), + .randomizeRotation = false, + }; + emitter = .init("cubyz:flame", false, .{.point = .{}}, emitterProperties, .spread); + emitter.spawnParticles(@as(vec.Vec3f, @floatFromInt(params.blockPos)) + vec.Vec3f{0.5, 0.5, 0.85}, 1); + return .handled; +} diff --git a/src/game.zig b/src/game.zig index 97c06a49b2..f4d2f6fc05 100644 --- a/src/game.zig +++ b/src/game.zig @@ -281,6 +281,7 @@ pub const World = struct { // MARK: World entityComponentPalette: *assets.Palette = undefined, itemDrops: ClientItemDropManager = undefined, playerBiome: Atomic(*const main.server.terrain.biomes.Biome) = undefined, + randomTicksTime: f64 = 0, shouldRestart: std.atomic.Value(bool) = .init(false), shouldReload: bool = false, @@ -412,6 +413,36 @@ pub const World = struct { // MARK: World } network.protocols.playerPosition.send(self.conn, Player.getPosBlocking(), Player.getVelBlocking(), @intCast(newTime & 65535)); self.dayTime.update(deltaTime); + + self.randomTicksTime += deltaTime; + const tickrate: f32 = 1.0/20.0; + if (self.randomTicksTime >= tickrate) { + updateRandomTickBlocks(self); + self.randomTicksTime = 0; + } + } + + pub fn updateRandomTickBlocks(_: *World) void { + const chunkRadius = settings.visualRandomTickRadius; + const playerBlockPos: Vec3i = @intFromFloat(@floor(Player.getPosBlocking())); + const thing: i32 = @intCast(if (@mod(chunkRadius, 2) == 0) @divFloor(chunkRadius, 2) else @divFloor(chunkRadius - 1, 2)); + for (0..chunkRadius) |cx| { + for (0..chunkRadius) |cy| { + for (0..chunkRadius) |cz| { + const chunkMultiplierPos: Vec3i = playerBlockPos +% @as(Vec3i, @splat(32))*Vec3i{@as(i32, @intCast(cx)) - thing, @as(i32, @intCast(cy)) - thing, @as(i32, @intCast(cz)) - thing}; + const chunkMesh = main.renderer.mesh_storage.getMesh(.initFromWorldPos(chunkMultiplierPos, 1)); + + if (chunkMesh) |mesh| { + for (0..20) |_| { + const curBlockPos = Vec3i{main.random.nextIntBounded(i32, &main.seed, 32), main.random.nextIntBounded(i32, &main.seed, 32), main.random.nextIntBounded(i32, &main.seed, 32)}; + const block = mesh.chunk.getBlock(curBlockPos[0], curBlockPos[1], curBlockPos[2]); + const onClientUpdate = block.onClientUpdate(); + if (onClientUpdate.run(.{.blockPos = curBlockPos + Vec3i{mesh.pos.wx, mesh.pos.wy, mesh.pos.wz}, .block = block, .chunk = mesh.chunk}) == .handled) continue; + } + } + } + } + } } pub const DayTime = struct { // MARK: DayTime diff --git a/src/gui/windows/graphics.zig b/src/gui/windows/graphics.zig index f84818a1f8..d14fb731cf 100644 --- a/src/gui/windows/graphics.zig +++ b/src/gui/windows/graphics.zig @@ -38,6 +38,8 @@ const fpsPresetsText = blk: { break :blk strings; }; +const visualRandomTickRadius = [_]u8{1, 2, 3, 4, 5, 6}; + fn fpsCapGetIndex(fpsOptional: ?u32) u16 { const fps: u16 = @truncate(fpsOptional orelse return fpsPresetsValue.len); return @intCast(std.sort.lowerBound(u16, &fpsPresetsValue, fps, struct { @@ -128,6 +130,11 @@ fn resolutionScaleCallback(newValue: u16) void { main.Window.GLFWCallbacks.framebufferSize(null, main.Window.width, main.Window.height); } +fn visualRandomTickRadiusCallback(newValue: u16) void { + settings.visualRandomTickRadius = newValue; + settings.save(); +} + pub fn onOpen() void { const list = VerticalList.init(.{padding, 16 + padding}, 300, 16); list.add(DiscreteSlider.init(.{0, 0}, 128, "#ffffffFPS Limit:\n", "{s}", &fpsPresetsText, fpsCapGetIndex(settings.fpsCap), &fpsCapCallback)); @@ -151,6 +158,7 @@ pub fn onOpen() void { else => 2, }, &anisotropicFilteringCallback)); list.add(DiscreteSlider.init(.{0, 0}, 128, "#ffffffResolution Scale: ", "{}%", &resolutions, @as(u16, @trunc(@log2(settings.resolutionScale) + 2.0)), &resolutionScaleCallback)); + list.add(DiscreteSlider.init(.{0, 0}, 128, "#ffffffVisual random tick radius: ", "{} chunks", &visualRandomTickRadius, settings.visualRandomTickRadius, &visualRandomTickRadiusCallback)); list.finish(.center); window.rootComponent = list.toComponent(); window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @as(Vec2f, @splat(padding)); diff --git a/src/particles.zig b/src/particles.zig index 0af8fbd985..943ec5fa8b 100644 --- a/src/particles.zig +++ b/src/particles.zig @@ -404,6 +404,8 @@ pub const DirectionMode = union(enum) { pub const Emitter = struct { typ: u16 = 0, + particleTypeLocal: ParticleTypeLocal, + particleType: ParticleType, collides: bool, spawnShape: SpawnShape, properties: EmitterProperties, @@ -506,6 +508,8 @@ pub const Emitter = struct { return Emitter{ .typ = typ, + .particleTypeLocal = ParticleManager.typesLocal.items[typ], + .particleType = ParticleManager.types.items[typ], .collides = collides, .spawnShape = spawnShape, .properties = properties, @@ -526,6 +530,8 @@ pub const Emitter = struct { return Emitter{ .typ = typ, + .particleTypeLocal = ParticleManager.typesLocal.items[typ], + .particleType = ParticleManager.types.items[typ], .collides = collides, .spawnShape = spawnShape, .properties = EmitterProperties.parse(zon), @@ -538,9 +544,7 @@ pub const Emitter = struct { for (0..count) |_| { const particlePos, const particleVel = self.spawnShape.spawn(pos, self.properties, self.mode); - const particleTypeLocal = ParticleManager.typesLocal.items[self.typ]; - const particleType = ParticleManager.types.items[self.typ]; - ParticleSystem.addParticle(self.typ, particleTypeLocal, particleType, particlePos, particleVel, self.collides, self.properties); + ParticleSystem.addParticle(self.typ, self.particleTypeLocal, self.particleType, particlePos, particleVel, self.collides, self.properties); } } }; diff --git a/src/settings.zig b/src/settings.zig index 2cf4ddde27..fe6fdbfb79 100644 --- a/src/settings.zig +++ b/src/settings.zig @@ -71,6 +71,8 @@ pub var updateRepeatDelay: std.Io.Duration = .fromMilliseconds(500); pub var controllerAxisDeadzone: f32 = 0.2; +pub var visualRandomTickRadius: u16 = 5; + const settingsFile = if (builtin.mode == .Debug) "debug_settings.zig.zon" else "settings.zig.zon"; pub fn init() void {