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