Directional drops (#1251)

* Add some crazy complicated logic to unstuck items

* Add bouncinessFactor

* Fix formatting issues

* Use rayIntersection to determine direction of drop

* Simplify blockDrop() code

* Apply review change requests

* Send bounding box for block drops

* Rename BlockDrop to BlockDropLocation

* Apply review change requests

* Apply review change requests

* Update src/Inventory.zig

* Apply review change requests

* Remove code reversing direction for carpet

* Update src/Inventory.zig

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>

* Update src/Inventory.zig

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>

* Remove factors variable

* Strip types from randomOffset

---------

Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
This commit is contained in:
Krzysztof Wiśniewski 2025-04-27 17:29:02 +02:00 committed by GitHub
parent 735e99826e
commit 668f9d0a9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 119 additions and 24 deletions

View File

@ -13,6 +13,7 @@ const Vec3d = vec.Vec3d;
const Vec3f = vec.Vec3f;
const Vec3i = vec.Vec3i;
const ZonElement = main.ZonElement;
const Neighbor = main.chunk.Neighbor;
const Gamemode = main.game.Gamemode;
@ -1555,9 +1556,80 @@ pub const Command = struct { // MARK: Command
const UpdateBlock = struct { // MARK: UpdateBlock
source: InventoryAndSlot,
pos: Vec3i,
dropLocation: BlockDropLocation,
oldBlock: Block,
newBlock: Block,
const half = @as(Vec3f, @splat(0.5));
const itemHitBoxMargin: f32 = @floatCast(main.itemdrop.ItemDropManager.radius);
const itemHitBoxMarginVec: Vec3f = @splat(itemHitBoxMargin);
const BlockDropLocation = struct {
dir: Neighbor,
min: Vec3f,
max: Vec3f,
pub fn drop(self: BlockDropLocation, pos: Vec3i, newBlock: Block, _drop: main.blocks.BlockDrop) void {
if(newBlock.collide()) {
self.dropOutside(pos, _drop);
} else {
self.dropInside(pos, _drop);
}
}
fn dropInside(self: BlockDropLocation, pos: Vec3i, _drop: main.blocks.BlockDrop) void {
for(_drop.items) |itemStack| {
main.server.world.?.drop(itemStack.clone(), self.insidePos(pos), self.dropDir(), self.dropVelocity());
}
}
fn insidePos(self: BlockDropLocation, _pos: Vec3i) Vec3d {
const pos: Vec3d = @floatFromInt(_pos);
return pos + self.randomOffset();
}
fn randomOffset(self: BlockDropLocation) Vec3f {
const max = @min(@as(Vec3f, @splat(1.0)) - itemHitBoxMarginVec, @max(itemHitBoxMarginVec, self.max - itemHitBoxMarginVec));
const min = @min(max, @max(itemHitBoxMarginVec, self.min + itemHitBoxMarginVec));
const center = (max + min)*half;
const width = (max - min)*half;
return center + width*main.random.nextFloatVectorSigned(3, &main.seed)*half;
}
fn dropOutside(self: BlockDropLocation, pos: Vec3i, _drop: main.blocks.BlockDrop) void {
for(_drop.items) |itemStack| {
main.server.world.?.drop(itemStack.clone(), self.outsidePos(pos), self.dropDir(), self.dropVelocity());
}
}
fn outsidePos(self: BlockDropLocation, _pos: Vec3i) Vec3d {
const pos: Vec3d = @floatFromInt(_pos);
return pos + self.randomOffset()*self.minor() + self.directionOffset()*self.major() + self.direction()*itemHitBoxMarginVec;
}
fn directionOffset(self: BlockDropLocation) Vec3d {
return half + self.direction()*half;
}
inline fn direction(self: BlockDropLocation) Vec3d {
return @floatFromInt(self.dir.relPos());
}
inline fn major(self: BlockDropLocation) Vec3d {
return @floatFromInt(@abs(self.dir.relPos()));
}
inline fn minor(self: BlockDropLocation) Vec3d {
return @floatFromInt(self.dir.orthogonalComponents());
}
fn dropDir(self: BlockDropLocation) Vec3f {
const randomnessVec: Vec3f = main.random.nextFloatVectorSigned(3, &main.seed)*@as(Vec3f, @splat(0.25));
const directionVec: Vec3f = @as(Vec3f, @floatCast(self.direction())) + randomnessVec;
const z: f32 = directionVec[2];
return vec.normalize(Vec3f{
directionVec[0],
directionVec[1],
if(z < -0.5) 0 else if(z < 0.0) (z + 0.5)*4.0 else z + 2.0,
});
}
fn dropVelocity(self: BlockDropLocation) f32 {
const velocity = 3.5 + main.random.nextFloatSigned(&main.seed)*0.5;
if(self.direction()[2] < -0.5) return velocity*0.333;
return velocity;
}
};
fn run(self: UpdateBlock, allocator: NeverFailingAllocator, cmd: *Command, side: Side, user: ?*main.server.User, gamemode: Gamemode) error{serverFailure}!void {
if(self.source.inv.type != .normal) return;
@ -1610,7 +1682,7 @@ pub const Command = struct { // MARK: Command
for(0..amount) |_| {
for(self.newBlock.blockDrops()) |drop| {
if(drop.chance == 1 or main.random.nextFloat(&main.seed) < drop.chance) {
blockDrop(self.pos, drop);
self.dropLocation.drop(self.pos, self.newBlock, drop);
}
}
}
@ -1621,25 +1693,18 @@ pub const Command = struct { // MARK: Command
if(side == .server and gamemode != .creative and self.oldBlock.typ != self.newBlock.typ and shouldDropSourceBlockOnSuccess) {
for(self.oldBlock.blockDrops()) |drop| {
if(drop.chance == 1 or main.random.nextFloat(&main.seed) < drop.chance) {
blockDrop(self.pos, drop);
self.dropLocation.drop(self.pos, self.newBlock, drop);
}
}
}
}
fn blockDrop(pos: Vec3i, drop: main.blocks.BlockDrop) void {
for(drop.items) |itemStack| {
const dropPos = @as(Vec3d, @floatFromInt(pos)) + @as(Vec3d, @splat(0.5)) + main.random.nextDoubleVectorSigned(3, &main.seed)*@as(Vec3d, @splat(0.5 - main.itemdrop.ItemDropManager.radius));
const dir = vec.normalize(main.random.nextFloatVectorSigned(3, &main.seed));
main.server.world.?.drop(itemStack.clone(), dropPos, dir, main.random.nextFloat(&main.seed)*1.5);
}
}
fn serialize(self: UpdateBlock, writer: *utils.BinaryWriter) void {
self.source.write(writer);
writer.writeInt(i32, self.pos[0]);
writer.writeInt(i32, self.pos[1]);
writer.writeInt(i32, self.pos[2]);
writer.writeVec(Vec3i, self.pos);
writer.writeEnum(Neighbor, self.dropLocation.dir);
writer.writeVec(Vec3f, self.dropLocation.min);
writer.writeVec(Vec3f, self.dropLocation.max);
writer.writeInt(u32, @as(u32, @bitCast(self.oldBlock)));
writer.writeInt(u32, @as(u32, @bitCast(self.newBlock)));
}
@ -1647,10 +1712,11 @@ pub const Command = struct { // MARK: Command
fn deserialize(reader: *utils.BinaryReader, side: Side, user: ?*main.server.User) !UpdateBlock {
return .{
.source = try InventoryAndSlot.read(reader, side, user),
.pos = .{
try reader.readInt(i32),
try reader.readInt(i32),
try reader.readInt(i32),
.pos = try reader.readVec(Vec3i),
.dropLocation = .{
.dir = try reader.readEnum(Neighbor),
.min = try reader.readVec(Vec3f),
.max = try reader.readVec(Vec3f),
},
.oldBlock = @bitCast(try reader.readInt(u32)),
.newBlock = @bitCast(try reader.readInt(u32)),

View File

@ -716,6 +716,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection
var currentSwingTime: f32 = 0;
var selectionMin: Vec3f = undefined;
var selectionMax: Vec3f = undefined;
var selectionFace: chunk.Neighbor = undefined;
var lastPos: Vec3d = undefined;
var lastDir: Vec3f = undefined;
pub fn select(pos: Vec3d, _dir: Vec3f, item: ?main.items.Item) void {
@ -750,6 +751,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection
selectedBlockPos = voxelPos;
selectionMin = intersection.min;
selectionMax = intersection.max;
selectionFace = intersection.face;
break;
}
}
@ -927,7 +929,19 @@ pub const MeshSelection = struct { // MARK: MeshSelection
}
fn updateBlockAndSendUpdate(source: main.items.Inventory, slot: u32, x: i32, y: i32, z: i32, oldBlock: blocks.Block, newBlock: blocks.Block) void {
main.items.Inventory.Sync.ClientSide.executeCommand(.{.updateBlock = .{.source = .{.inv = source, .slot = slot}, .pos = .{x, y, z}, .oldBlock = oldBlock, .newBlock = newBlock}});
main.items.Inventory.Sync.ClientSide.executeCommand(.{
.updateBlock = .{
.source = .{.inv = source, .slot = slot},
.pos = .{x, y, z},
.dropLocation = .{
.dir = selectionFace,
.min = selectionMin,
.max = selectionMax,
},
.oldBlock = oldBlock,
.newBlock = newBlock,
},
});
mesh_storage.updateBlock(x, y, z, newBlock);
}

View File

@ -18,6 +18,7 @@ pub const RayIntersectionResult = struct {
distance: f64,
min: Vec3f,
max: Vec3f,
face: Neighbor,
};
pub const Degrees = enum(u2) {
@ -65,10 +66,27 @@ pub const RotationMode = struct { // MARK: RotationMode
const boxTMin = @reduce(.Max, @min(t1, t2));
const boxTMax = @reduce(.Min, @max(t1, t2));
if(boxTMin <= boxTMax and boxTMax > 0) {
var face: Neighbor = undefined;
if(boxTMin == t1[0]) {
face = Neighbor.dirNegX;
} else if(boxTMin == t1[1]) {
face = Neighbor.dirNegY;
} else if(boxTMin == t1[2]) {
face = Neighbor.dirDown;
} else if(boxTMin == t2[0]) {
face = Neighbor.dirPosX;
} else if(boxTMin == t2[1]) {
face = Neighbor.dirPosY;
} else if(boxTMin == t2[2]) {
face = Neighbor.dirUp;
} else {
unreachable;
}
return .{
.distance = boxTMin,
.min = min,
.max = max,
.face = face,
};
}
return null;

View File

@ -296,15 +296,12 @@ pub fn rayIntersection(block: Block, item: ?main.items.Item, relativePlayerPos:
if(item) |_item| {
switch(_item) {
.baseItem => |baseItem| {
if(std.mem.eql(u8, baseItem.id, "cubyz:chisel")) { // Select only one eigth of a block
if(std.mem.eql(u8, baseItem.id, "cubyz:chisel")) { // Select only one eighth of a block
if(intersectionPos(block, relativePlayerPos, playerDir)) |intersection| {
const offset: Vec3f = @floatFromInt(intersection.minPos);
const half: Vec3f = @splat(0.5);
return .{
.distance = intersection.minT,
.min = half*offset,
.max = half + half*offset,
};
const fullIntersection = RotationMode.DefaultFunctions.rayIntersection(block, item, relativePlayerPos, playerDir) orelse unreachable;
return .{.distance = intersection.minT, .min = half*offset, .max = half + half*offset, .face = fullIntersection.face};
}
return null;
}