Merge d5093c88bba72bfa46dbefa8b85997632b4a1878 into 2dc2a6e79018221d5aab44e09be242d9dab3ddf2

This commit is contained in:
MnHs 2025-07-31 12:47:36 +02:00 committed by GitHub
commit 46ced50441
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 134 additions and 0 deletions

View File

@ -6,6 +6,7 @@ const assets = @import("assets.zig");
const Block = @import("blocks.zig").Block;
const chunk = @import("chunk.zig");
const entity = @import("entity.zig");
const particles = @import("particles.zig");
const items = @import("items.zig");
const Inventory = items.Inventory;
const ItemStack = items.ItemStack;
@ -1018,6 +1019,7 @@ pub const Protocols = struct {
worldEditPos = 2,
time = 3,
biome = 4,
particle = 5,
};
const WorldEditPosition = enum(u2) {
@ -1090,6 +1092,20 @@ pub const Protocols = struct {
}
}
},
.particle => {
if(conn.manager.world) |_| {
const particleId = particles.ParticleManager.getIdByTypeIndex(try reader.readInt(u16));
const pos = try reader.readVec(Vec3d);
const collides = try reader.readInt(u8) == 1;
const count = try reader.readInt(u32);
const emitter: particles.Emitter = .init(particleId, collides);
emitter.spawnParticles(count, particles.Emitter.SpawnPoint, .{
.mode = .spread,
.position = pos,
});
}
},
}
}
@ -1130,6 +1146,19 @@ pub const Protocols = struct {
conn.send(.fast, id, writer.data.items);
}
pub fn sendParticle(conn: *Connection, particleId: u16, pos: Vec3d, collides: bool, count: u32) void {
var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, 32);
defer writer.deinit();
writer.writeEnum(UpdateType, .particle);
writer.writeInt(u16, particleId);
writer.writeVec(Vec3d, pos);
writer.writeInt(u8, if(collides) 1 else 0);
writer.writeInt(u32, count);
conn.send(.fast, id, writer.data.items);
}
pub fn sendTime(conn: *Connection, world: *const main.server.ServerWorld) void {
var writer = utils.BinaryWriter.initCapacity(main.stackAllocator, 13);
defer writer.deinit();

View File

@ -35,6 +35,7 @@ pub const ParticleManager = struct {
const ParticleIndex = u16;
var particleTypeHashmap: std.StringHashMapUnmanaged(ParticleIndex) = .{};
var typeIds: main.List([]const u8) = undefined;
pub fn init() void {
types = .init(arenaAllocator);
@ -42,6 +43,7 @@ pub const ParticleManager = struct {
emissionTextures = .init(arenaAllocator);
textureArray = .init();
emissionTextureArray = .init();
typeIds = .init(arenaAllocator);
particleTypesSSBO = SSBO.init();
ParticleSystem.init();
}
@ -52,6 +54,7 @@ pub const ParticleManager = struct {
emissionTextures.deinit();
textureArray.deinit();
emissionTextureArray.deinit();
typeIds.deinit();
particleTypeHashmap.deinit(arenaAllocator.allocator);
ParticleSystem.deinit();
particleTypesSSBO.deinit();
@ -67,6 +70,7 @@ pub const ParticleManager = struct {
const particleType = readTextureDataAndParticleType(assetsFolder, textureId);
particleTypeHashmap.put(arenaAllocator.allocator, id, @intCast(types.items.len)) catch unreachable;
typeIds.append(id);
types.append(particleType);
std.log.debug("Registered particle type: {s}", .{id});
@ -147,6 +151,16 @@ pub const ParticleManager = struct {
particleTypesSSBO.bufferData(ParticleType, ParticleManager.types.items);
particleTypesSSBO.bind(14);
}
pub fn getTypeIndexById(id: []const u8) ?ParticleIndex {
return particleTypeHashmap.get(id);
}
pub fn getIdByTypeIndex(index: ParticleIndex) []const u8 {
std.debug.assert(index < typeIds.items.len);
return typeIds.items[index];
}
};
pub const ParticleSystem = struct {

View File

@ -3,6 +3,7 @@ pub const gamemode = @import("gamemode.zig");
pub const help = @import("help.zig");
pub const invite = @import("invite.zig");
pub const kill = @import("kill.zig");
pub const particle = @import("particle.zig");
pub const time = @import("time.zig");
pub const tp = @import("tp.zig");

View File

@ -0,0 +1,90 @@
const std = @import("std");
const main = @import("main");
const particles = main.particles;
const User = main.server.User;
pub const description = "Spawns particles.";
pub const usage =
\\/particle <id> <x> <y> <z>
\\/particle <id> <x> <y> <z> <collides>
\\/particle <id> <x> <y> <z> <collides> <count>
;
pub fn execute(args: []const u8, source: *User) void {
const particleId, const x, const y, const z, const collides, const particleCount = parseArguments(source, args) catch |err| {
switch(err) {
error.TooFewArguments => source.sendMessage("#ff0000Too few arguments for command /particle", .{}),
error.TooManyArguments => source.sendMessage("#ff0000Too many arguments for command /particle", .{}),
error.InvalidParticleId => source.sendMessage("#ff0000Invalid particle id", .{}),
error.InvalidBoolean => source.sendMessage("#ff0000Invalid argument. Expected \"true\" or \"false\"", .{}),
error.InvalidNumber => return,
}
return;
};
const users = main.server.getUserListAndIncreaseRefCount(main.stackAllocator);
defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, users);
for(users) |user| {
main.network.Protocols.genericUpdate.sendParticle(user.conn, particleId, .{x, y, z}, collides, particleCount);
}
}
pub const CommandError = error{
TooManyArguments,
TooFewArguments,
InvalidBoolean,
InvalidNumber,
InvalidParticleId,
};
fn parseArguments(source: *User, args: []const u8) CommandError!struct {u16, f64, f64, f64, bool, u32} {
var split = std.mem.splitScalar(u8, args, ' ');
const particleId = split.next() orelse return error.TooFewArguments;
const indexId = particles.ParticleManager.getTypeIndexById(particleId) orelse return error.InvalidParticleId;
const x = try parsePosition(split.next() orelse return error.TooFewArguments, source.player.pos[0], source);
const y = try parsePosition(split.next() orelse return error.TooFewArguments, source.player.pos[1], source);
const z = try parsePosition(split.next() orelse return error.TooFewArguments, source.player.pos[2], source);
const collides = try parseBool(split.next() orelse "false");
const particleCount = try parseNumber(split.next() orelse "1", source);
if(split.next() != null) return error.TooManyArguments;
return .{indexId, x, y, z, collides, particleCount};
}
fn parsePosition(arg: []const u8, playerPos: f64, source: *User) CommandError!f64 {
const hasTilde = if(arg.len == 0) false else arg[0] == '~';
const numberSlice = if(hasTilde) arg[1..] else arg;
const num: f64 = std.fmt.parseFloat(f64, numberSlice) catch ret: {
if(arg.len > 1 or arg.len == 0) {
source.sendMessage("#ff0000Expected number, found \"{s}\"", .{arg});
return error.InvalidNumber;
}
break :ret 0;
};
return if(hasTilde) playerPos + num else num;
}
fn parseBool(arg: []const u8) CommandError!bool {
if(std.mem.eql(u8, arg, "true")) {
return true;
} else if(std.mem.eql(u8, arg, "false")) {
return false;
}
return error.InvalidBoolean;
}
fn parseNumber(arg: []const u8, source: *User) CommandError!u32 {
return std.fmt.parseUnsigned(u32, arg, 0) catch |err| {
switch(err) {
error.Overflow => return std.math.maxInt(u32),
error.InvalidCharacter => {
source.sendMessage("#ff0000Expected number, found \"{s}\"", .{arg});
return error.InvalidNumber;
},
}
};
}