diff --git a/Obsidian.API/Events/BlockBreakEventArgs.cs b/Obsidian.API/Events/BlockBreakEventArgs.cs
index 0c11f3ff4..2adb99bcb 100644
--- a/Obsidian.API/Events/BlockBreakEventArgs.cs
+++ b/Obsidian.API/Events/BlockBreakEventArgs.cs
@@ -10,7 +10,7 @@ public class BlockBreakEventArgs : BlockEventArgs, ICancellable
///
public bool IsCancelled { get; private set; }
- internal BlockBreakEventArgs(IServer server, IPlayer player, IBlock block, Vector location) : base(server, block, location)
+ internal BlockBreakEventArgs(IServer server, IPlayer player, IBlock block, Vector location, IWorld world) : base(server, block, location, world)
{
Player = player;
}
diff --git a/Obsidian.API/Events/BlockEventArgs.cs b/Obsidian.API/Events/BlockEventArgs.cs
index bfdedb7ca..35abe1f53 100644
--- a/Obsidian.API/Events/BlockEventArgs.cs
+++ b/Obsidian.API/Events/BlockEventArgs.cs
@@ -1,26 +1,22 @@
namespace Obsidian.API.Events;
-public abstract class BlockEventArgs : BaseMinecraftEventArgs
+public abstract class BlockEventArgs(IServer server, IBlock block, Vector location, IWorld world) : BaseMinecraftEventArgs(server)
{
///
/// The impacted block.
///
- public IBlock Block { get; }
+ public IBlock Block { get; } = block;
///
/// Location of the impacted block.
///
- public Vector Location { get; }
+ public Vector Location { get; } = location;
///
/// World where the event took place.
///
- public IWorld World { get; }
+ public IWorld World { get; } = world;
- protected BlockEventArgs(IServer server, IBlock block, Vector location) : base(server)
- {
- Block = block;
- Location = location;
- World = server.DefaultWorld;
- }
+
+ public int Sequence { get; init; }
}
diff --git a/Obsidian.API/Events/PlayerLeaveEventArgs.cs b/Obsidian.API/Events/PlayerLeaveEventArgs.cs
index ddcf71c91..c0e6a07b7 100644
--- a/Obsidian.API/Events/PlayerLeaveEventArgs.cs
+++ b/Obsidian.API/Events/PlayerLeaveEventArgs.cs
@@ -1,14 +1,9 @@
namespace Obsidian.API.Events;
-public class PlayerLeaveEventArgs : PlayerEventArgs
+public class PlayerLeaveEventArgs(IPlayer player, IServer server, DateTimeOffset leave) : PlayerEventArgs(player, server)
{
///
/// The date the player left.
///
- public DateTimeOffset LeaveDate { get; }
-
- public PlayerLeaveEventArgs(IPlayer player, IServer server, DateTimeOffset leave) : base(player, server)
- {
- this.LeaveDate = leave;
- }
+ public DateTimeOffset LeaveDate { get; } = leave;
}
diff --git a/Obsidian.API/_Types/Velocity.cs b/Obsidian.API/_Types/Velocity.cs
index fcb83125f..f4cf15f9b 100644
--- a/Obsidian.API/_Types/Velocity.cs
+++ b/Obsidian.API/_Types/Velocity.cs
@@ -34,7 +34,7 @@ public struct Velocity(double x, double y, double z)
/// How many blocks can be travelled on the Z axis in a second.
public static Velocity FromBlockPerSecond(float x, float y, float z)
{
- return new Velocity((short)(400f * x), (short)(400f * y), (short)(400f * z));
+ return new Velocity(x / 20f, y / 20f, z / 20f);
}
///
@@ -45,7 +45,7 @@ public static Velocity FromBlockPerSecond(float x, float y, float z)
/// How many blocks can be travelled on the Z axis in a tick (50ms).
public static Velocity FromBlockPerTick(float x, float y, float z)
{
- return new Velocity((short)(8000f * x), (short)(8000f * y), (short)(8000f * z));
+ return new Velocity(x, y, z);
}
///
diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs
index 61b0b3511..a5205cb99 100644
--- a/Obsidian/Entities/Player.cs
+++ b/Obsidian/Entities/Player.cs
@@ -116,7 +116,13 @@ public IScoreboard? CurrentScoreboard
public short CurrentHeldItemSlot
{
- get => field;
+ get
+ {
+ if (field < 36 || field > 44)
+ field = 36;
+
+ return field;
+ }
set
{
if (value is < 0 or > 8)
diff --git a/Obsidian/Events/MainEventHandler.World.cs b/Obsidian/Events/MainEventHandler.World.cs
new file mode 100644
index 000000000..15138a412
--- /dev/null
+++ b/Obsidian/Events/MainEventHandler.World.cs
@@ -0,0 +1,67 @@
+using Obsidian.API.Events;
+using Obsidian.Entities;
+using Obsidian.Net.Packets.Play.Clientbound;
+
+namespace Obsidian.Events;
+
+public partial class MainEventHandler
+{
+ [EventPriority(Priority = Priority.Internal)]
+ public async ValueTask OnBlockBreak(BlockBreakEventArgs args)
+ {
+ var player = args.Player;
+ var sequence = args.Sequence;
+ var block = args.Block;
+ var world = player.World;
+ var location = args.Location;
+
+ player.Client.SendPacket(new BlockChangedAckPacket
+ {
+ SequenceID = sequence
+ });
+
+ if (args.IsCancelled)
+ {
+ player.Client.SendPacket(new BlockUpdatePacket(location, block.GetHashCode()));
+ return;
+ }
+
+ await world.SetBlockAsync(location, BlocksRegistry.Air, true);
+
+ player.Client.SendPacket(new BlockUpdatePacket(location, BlocksRegistry.Air.GetHashCode()));
+
+ world.PacketBroadcaster.QueuePacketToWorld(world, 0, new BlockDestructionPacket
+ {
+ EntityId = player.EntityId,
+ Position = location,
+ DestroyStage = -1
+ }, player.EntityId);
+
+ var droppedItem = ItemsRegistry.GetSingleItem(block.Material);
+
+ if (droppedItem.Type == Material.Air)
+ return;
+
+ var item = new ItemEntity
+ {
+ EntityId = Server.GetNextEntityId(),
+ Item = droppedItem,
+ World = player.World,
+ Position = (VectorF)location + 0.5f,
+ };
+
+ player.World.TryAddEntity(item);
+
+ var power = GetRandDropVelocity();
+ var direction = Globals.Random.NextFloat() * 6.2f;
+
+ item.SpawnEntity(new Velocity(-Math.Sin(direction) * power, 0.2f, Math.Cos(direction) * power));
+ }
+
+ private static float GetRandDropVelocity()
+ {
+ var f = Globals.Random.NextFloat();
+
+ return f * 0.5f;
+ }
+}
diff --git a/Obsidian/Events/MainEventHandler.cs b/Obsidian/Events/MainEventHandler.cs
index c6b8235ea..99ac2886d 100644
--- a/Obsidian/Events/MainEventHandler.cs
+++ b/Obsidian/Events/MainEventHandler.cs
@@ -315,8 +315,6 @@ public async Task OnPlayerLeave(PlayerLeaveEventArgs e)
await player.SaveAsync();
- player.World.TryRemovePlayer(player);
-
packetBroadcaster.Broadcast(new PlayerInfoRemovePacket
{
UUIDs = [player.Uuid]
diff --git a/Obsidian/Net/NetworkBuffer.Writing.cs b/Obsidian/Net/NetworkBuffer.Writing.cs
index aa6949885..34562de34 100644
--- a/Obsidian/Net/NetworkBuffer.Writing.cs
+++ b/Obsidian/Net/NetworkBuffer.Writing.cs
@@ -11,13 +11,11 @@
using Obsidian.Nbt;
using Obsidian.Net.Packets.Play.Clientbound;
using Obsidian.Serialization.Attributes;
-using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
-using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
@@ -561,15 +559,16 @@ public void WriteSoundEvent(SoundEvent soundEvent)
}
private const double MinAbsValue = 3.051944088384301E-5;
+ private const double MaxVelocityComponent = 1.7179869183E10;
[WriteMethod]
public void WriteVelocity(Velocity value)
{
- var sanitizedX = Sanitize(value.X);
- var sanitizedY = Sanitize(value.Y);
- var sanitizedZ = Sanitize(value.Z);
+ var x = SanitizeVelocityComponent(value.X);
+ var y = SanitizeVelocityComponent(value.Y);
+ var z = SanitizeVelocityComponent(value.Z);
- var maxAbsValue = NumericsHelper.AbsMax(sanitizedX, NumericsHelper.AbsMax(sanitizedY, sanitizedZ));
+ var maxAbsValue = NumericsHelper.AbsMax(x, NumericsHelper.AbsMax(y, z));
if (maxAbsValue < MinAbsValue)
{
@@ -578,16 +577,14 @@ public void WriteVelocity(Velocity value)
}
var scaleFactor = (long)Math.Ceiling(maxAbsValue);
- var needsExtraBytes = (scaleFactor & ~3L) != 0; // Check if bits beyond the lower 2 are set
- var adjustedScale = needsExtraBytes ? (scaleFactor & 3L) | 4L : scaleFactor;
+ var needsExtraBytes = (scaleFactor & 3L) != scaleFactor;
+ var packedScale = needsExtraBytes ? (scaleFactor & 3L) | 4L : scaleFactor;
- var scale = (double)scaleFactor;
- var packedX = PackVelocity(sanitizedX / scale) << 3;
- var packedY = PackVelocity(sanitizedY / scale) << 18;
- var packedZ = PackVelocity(sanitizedZ / scale) << 33;
-
- var packedData = adjustedScale | packedX | packedY | packedZ;
+ var packedX = PackVelocityComponent(x / scaleFactor) << 3;
+ var packedY = PackVelocityComponent(y / scaleFactor) << 18;
+ var packedZ = PackVelocityComponent(z / scaleFactor) << 33;
+ var packedData = packedScale | packedX | packedY | packedZ;
this.WriteByte((byte)packedData);
this.WriteByte((byte)(packedData >> 8));
this.WriteInt((int)(packedData >> 16));
@@ -699,15 +696,12 @@ public void WriteOptional(TValue? value) where TValue : INetworkSerializ
public byte[] ToArray() => this.Data;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static long PackVelocity(double value)
- {
- const double scale = 0.5;
- const int maxValue = short.MaxValue - 1;
- return (long)Math.Round((value * scale + scale) * maxValue);
- }
+ private const double VelocityPackingScale = 0.5;
+ private const int VelocityPackingMaxValue = short.MaxValue - 1;
+
+ private static long PackVelocityComponent(double value) =>
+ (long)Math.Round((value * VelocityPackingScale + VelocityPackingScale) * VelocityPackingMaxValue, MidpointRounding.AwayFromZero);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static double Sanitize(double value) =>
- double.IsNaN(value) ? 0.0 : Math.Clamp(value, -1.7179869183E10, 1.7179869183E10);
+ private static double SanitizeVelocityComponent(double value) =>
+ double.IsNaN(value) ? 0.0 : Math.Clamp(value, -MaxVelocityComponent, MaxVelocityComponent);
}
diff --git a/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs
index 28cb18cce..4c459b757 100644
--- a/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs
+++ b/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs
@@ -35,22 +35,20 @@ public async override ValueTask HandleAsync(IServer server, IPlayer player)
if (Status == PlayerActionStatus.FinishedDigging || (Status == PlayerActionStatus.StartedDigging && player.Gamemode == Gamemode.Creative))
{
- await player.World.SetBlockAsync(Position, BlocksRegistry.Air, true);
- player.Client.SendPacket(new BlockChangedAckPacket
+ var args = new BlockBreakEventArgs(server, player, block, Position, player.World)
{
- SequenceID = Sequence
- });
+ Sequence = this.Sequence
+ };
- var args = new BlockBreakEventArgs(server, player, block, Position);
await server.EventDispatcher.ExecuteEventAsync(args);
- if (args.Handled)
- return;
+
+ return;
}
- this.BroadcastPlayerAction(player, block);
+ this.BroadcastPlayerAction(player);
}
- private void BroadcastPlayerAction(IPlayer player, IBlock block)
+ private void BroadcastPlayerAction(IPlayer player)
{
switch (this.Status)
{
@@ -66,44 +64,20 @@ private void BroadcastPlayerAction(IPlayer player, IBlock block)
}
case PlayerActionStatus.StartedDigging:
case PlayerActionStatus.CancelledDigging:
+ player.Client.SendPacket(new BlockChangedAckPacket
+ {
+ SequenceID = this.Sequence
+ });
break;
case PlayerActionStatus.FinishedDigging:
{
- player.World.PacketBroadcaster.QueuePacketToWorld(player.World, 0, new BlockDestructionPacket
- {
- EntityId = player.EntityId,
- Position = this.Position,
- DestroyStage = -1
- }, player.EntityId);
-
- var droppedItem = ItemsRegistry.GetSingleItem(block.Material);
- if (droppedItem.Type == Material.Air) { break; }
-
- var item = new ItemEntity
- {
- EntityId = Server.GetNextEntityId(),
- Item = droppedItem,
- World = player.World,
- Position = (VectorF)this.Position + 0.5f,
- };
-
- player.World.TryAddEntity(item);
-
- item.SpawnEntity(Velocity.FromBlockPerTick(GetRandDropVelocity(), GetRandDropVelocity(), GetRandDropVelocity()));
break;
}
}
}
- private static float GetRandDropVelocity()
- {
- var f = Globals.Random.NextFloat();
-
- return f * 0.5f;
- }
-
private static void DropItem(IPlayer player, sbyte amountToRemove)
{
var droppedItem = player.GetHeldItem();
@@ -111,7 +85,8 @@ private static void DropItem(IPlayer player, sbyte amountToRemove)
if (droppedItem is null or { Type: Material.Air })
return;
- var loc = new VectorF(player.Position.X, (float)player.HeadY - 0.3f, player.Position.Z);
+ var lookDir = player.GetLookDirection();
+ var loc = new VectorF(player.Position.X, (float)player.HeadY - 0.3f, player.Position.Z) + lookDir * 0.3f;
var item = new ItemEntity
{
@@ -123,9 +98,7 @@ private static void DropItem(IPlayer player, sbyte amountToRemove)
player.World.TryAddEntity(item);
- var lookDir = player.GetLookDirection();
-
- var vel = Velocity.FromDirection(loc, lookDir);//TODO properly shoot the item towards the direction the players looking at
+ var vel = Velocity.FromBlockPerTick(lookDir.X * 0.45f, lookDir.Y * 0.45f + 0.1f, lookDir.Z * 0.45f);
item.SpawnEntity(vel);
@@ -160,7 +133,9 @@ public enum PlayerActionStatus : int
DropItemStack,
DropItem,
- ShootArrowOrFinishEating,
+ ReleaseUseItem,
+
+ SwapItemInHand,
- SwapItemInHand
+ Stab
}
diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs
index cf4ddab6d..af4590095 100644
--- a/Obsidian/Server.cs
+++ b/Obsidian/Server.cs
@@ -306,6 +306,8 @@ public bool RemovePlayer(IPlayer player)
{
this.UsernameToUuidMappings.Remove(player.Username, out _);
+ player.World.TryRemovePlayer(player);
+
return this.OnlinePlayers.Remove(player.Uuid, out _);
}
diff --git a/Obsidian/Utilities/NumericsHelper.cs b/Obsidian/Utilities/NumericsHelper.cs
index 2e82cdc66..362d97d9e 100644
--- a/Obsidian/Utilities/NumericsHelper.cs
+++ b/Obsidian/Utilities/NumericsHelper.cs
@@ -17,6 +17,5 @@ public static void LongToInts(long l, out int a, out int b)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Modulo(int x, int mod) => (x % mod + mod) % mod;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double AbsMax(double a, double b) => Math.Max(Math.Abs(a), Math.Abs(b));
}
diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs
index ef7cfd1c8..b58219297 100644
--- a/Obsidian/WorldData/World.cs
+++ b/Obsidian/WorldData/World.cs
@@ -199,7 +199,7 @@ public async ValueTask SetBlockAsync(int x, int y, int z, IBlock block, bool doB
private void BroadcastBlockChange(IBlock block, Vector location)
{
var packet = new BlockUpdatePacket(location, block.GetHashCode());
- foreach (Player player in this.PlayersInRange(location))
+ foreach (Player player in PlayersInRange(location).Cast())
{
player.Client.SendPacket(packet);
}