diff --git a/Obsidian.API/_Interfaces/IPlayer.cs b/Obsidian.API/_Interfaces/IPlayer.cs index 7b07bdcb..d98cc365 100644 --- a/Obsidian.API/_Interfaces/IPlayer.cs +++ b/Obsidian.API/_Interfaces/IPlayer.cs @@ -46,11 +46,11 @@ public interface IPlayer : ILiving public ValueTask SendMessageAsync(ChatMessage message); public ValueTask SendMessageAsync(ChatMessage message, Guid sender, SecureMessageSignature messageSignature); public ValueTask SetActionBarTextAsync(ChatMessage message); - public Task SendSoundAsync(ISoundEffect soundEffect); - public Task KickAsync(ChatMessage reason); - public Task KickAsync(string reason); - public Task OpenInventoryAsync(BaseContainer container); - public Task DisplayScoreboardAsync(IScoreboard scoreboard, DisplaySlot position); + public ValueTask SendSoundAsync(ISoundEffect soundEffect); + public ValueTask KickAsync(ChatMessage reason); + public ValueTask KickAsync(string reason); + public ValueTask OpenInventoryAsync(BaseContainer container); + public ValueTask DisplayScoreboardAsync(IScoreboard scoreboard, DisplaySlot position); /// /// Sends a title message to the player. @@ -59,7 +59,7 @@ public interface IPlayer : ILiving /// Time in ticks for the title to fade in /// Time in ticks for the title to stay on screen /// Time in ticks for the title to fade out - public Task SendTitleAsync(ChatMessage title, int fadeIn, int stay, int fadeOut); + public ValueTask SendTitleAsync(ChatMessage title, int fadeIn, int stay, int fadeOut); /// /// Sends a title and subtitle message to the player. @@ -69,7 +69,7 @@ public interface IPlayer : ILiving /// Time in ticks for the title to fade in /// Time in ticks for the title to stay on screen /// Time in ticks for the title to fade out - public Task SendTitleAsync(ChatMessage title, ChatMessage subtitle, int fadeIn, int stay, int fadeOut); + public ValueTask SendTitleAsync(ChatMessage title, ChatMessage subtitle, int fadeIn, int stay, int fadeOut); /// /// Sends a subtitle message to the player. @@ -78,122 +78,24 @@ public interface IPlayer : ILiving /// Time in ticks for the title to fade in /// Time in ticks for the title to stay on screen /// Time in ticks for the title to fade out - public Task SendSubtitleAsync(ChatMessage subtitle, int fadeIn, int stay, int fadeOut); + public ValueTask SendSubtitleAsync(ChatMessage subtitle, int fadeIn, int stay, int fadeOut); /// /// Sends an action bar text to the player. /// /// The text of the action bar. - public Task SendActionBarAsync(string text); + public ValueTask SendActionBarAsync(string text); - /// - /// Spawns the given particle at the target coordinates. - /// - /// The to be spawned. - /// The target x-coordination. - /// The target y-coordination. - /// The target z-coordination. - /// The amount of particles to be spawned. - /// The extra data of the particle, mostly used for speed. - public Task SpawnParticleAsync(ParticleType particle, float x, float y, float z, int count, float extra = 0); - - /// - /// Spawns the given particle at the target coordinates and with the given offset. - /// - /// The to be spawned. - /// The target x-coordination. - /// The target y-coordination. - /// The target z-coordination. - /// The amount of particles to be spawned. - /// The x-offset. - /// The y-offset. - /// The z-offset. - /// The extra data of the particle, mostly used for speed. - public Task SpawnParticleAsync(ParticleType particle, float x, float y, float z, int count, float offsetX, - float offsetY, float offsetZ, float extra = 0); - - /// - /// Spawns the given particle at the target position. - /// - /// The to be spawned. - /// The target position as . - /// The amount of particles to be spawned. - /// The extra data of the particle, mostly used for speed. - public Task SpawnParticleAsync(ParticleType particle, VectorF pos, int count, float extra = 0); - - /// - /// Spawns the given particle at the target position. - /// - /// The to be spawned. - /// The target position as . - /// The amount of particles to be spawned. - /// The x-offset. - /// The y-offset. - /// The z-offset. - /// The extra data of the particle, mostly used for speed. - public Task SpawnParticleAsync(ParticleType particle, VectorF pos, int count, float offsetX, float offsetY, - float offsetZ, float extra = 0); - - /// - /// Spawns the given particle at the target coordinates. - /// - /// The to be spawned. - /// The target x-coordination. - /// The target y-coordination. - /// The target z-coordination. - /// The amount of particles to be spawned. - /// The of the particle. - /// The extra data of the particle, mostly used for speed. - public Task SpawnParticleAsync(ParticleType particle, float x, float y, float z, int count, ParticleData data, float extra = 0); - - /// - /// Spawns the given particle at the target coordinates and with the given offset. - /// - /// The to be spawned. - /// The target x-coordination. - /// The target y-coordination. - /// The target z-coordination. - /// The amount of particles to be spawned. - /// The x-offset. - /// The y-offset. - /// The z-offset. - /// The of the particle. - /// The extra data of the particle, mostly used for speed. - public Task SpawnParticleAsync(ParticleType particle, float x, float y, float z, int count, float offsetX, - float offsetY, float offsetZ, ParticleData data, float extra = 0); - - /// - /// Spawns the given particle at the target position. - /// - /// The to be spawned. - /// The target position as . - /// The amount of particles to be spawned. - /// The of the particle. - /// The extra data of the particle, mostly used for speed. - public Task SpawnParticleAsync(ParticleType particle, VectorF pos, int count, ParticleData data, float extra = 0); - - /// - /// Spawns the given particle at the target position. - /// - /// The to be spawned. - /// The target position as . - /// The amount of particles to be spawned. - /// The x-offset. - /// The y-offset. - /// The z-offset. - /// The of the particle. - /// The extra data of the particle, mostly used for speed. - public Task SpawnParticleAsync(ParticleType particle, VectorF pos, int count, float offsetX, float offsetY, - float offsetZ, ParticleData data, float extra = 0); + public ValueTask SpawnParticleAsync(ParticleData data); public Task GrantPermissionAsync(string permission); public Task RevokePermissionAsync(string permission); public bool HasPermission(string permission); public bool HasAnyPermission(IEnumerable permissions); public bool HasAllPermissions(IEnumerable permissions); - public Task SetGamemodeAsync(Gamemode gamemode); + public ValueTask SetGamemodeAsync(Gamemode gamemode); - public Task UpdateDisplayNameAsync(string newDisplayName); + public ValueTask UpdateDisplayNameAsync(string newDisplayName); public ItemStack? GetHeldItem(); public ItemStack? GetOffHandItem(); diff --git a/Obsidian/Client.cs b/Obsidian/Client.cs index 1e8de4c4..db59a2b0 100644 --- a/Obsidian/Client.cs +++ b/Obsidian/Client.cs @@ -1,15 +1,16 @@ using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Obsidian.API.Events; +using Obsidian.Blocks; using Obsidian.Entities; using Obsidian.Events.EventArgs; using Obsidian.Net; using Obsidian.Net.ClientHandlers; using Obsidian.Net.Packets; -using Obsidian.Net.Packets.Handshaking; -using Obsidian.Net.Packets.Login; -using Obsidian.Net.Packets.Play.Clientbound; -using Obsidian.Net.Packets.Status; +using Obsidian.Net.Packets.Common; +using Obsidian.Net.Packets.Handshake.Serverbound; +using Obsidian.Net.Packets.Login.Clientbound; +using Obsidian.Net.Packets.Status.Clientbound; using Obsidian.Services; using Obsidian.Utilities.Mojang; using Obsidian.WorldData; @@ -43,7 +44,7 @@ public sealed class Client : IDisposable /// /// Used for signing chat messages. /// - internal MessageSigningData? messageSigningData; + internal SignedMessage? messageSigningData; /// /// The server that the client is connected to. @@ -215,82 +216,97 @@ private async ValueTask GetNextPacketAsync() private async Task HandlePacketQueueAsync() { - while (!cancellationSource.IsCancellationRequested && this.connectionContext.IsConnected()) + try { - var packet = await this.packetQueue.Reader.ReadAsync(this.cancellationSource.Token); + while (!cancellationSource.IsCancellationRequested && this.connectionContext.IsConnected()) + { + var packet = await this.packetQueue.Reader.ReadAsync(this.cancellationSource.Token); - this.SendPacket(packet); + this.SendPacket(packet); + } + } + catch (OperationCanceledException) + { + this.Logger.LogDebug("Client({id}) packet queue was cancelled", this.id); } } private async Task HandlePacketsAsync() { - while (!cancellationSource.IsCancellationRequested && this.connectionContext.IsConnected()) + try { - using var packetData = await GetNextPacketAsync(); - - if (State == ClientState.Play && packetData.Data.Length < 1) - Disconnect(); - - switch (State) + while (!cancellationSource.IsCancellationRequested && this.connectionContext.IsConnected()) { - case ClientState.Status: // Server ping/list - if (packetData.Id == 0x00) - { - var status = new ServerStatus(this.server); - - _ = await this.server.EventDispatcher.ExecuteEventAsync(new ServerStatusRequestEventArgs(this.server, status)); - - this.SendPacket(new RequestResponse(status)); - } - else if (packetData.Id == 0x01) - { - this.SendPacket(PingPong.Deserialize(packetData.Data)); - this.Disconnect(); - } - break; - - case ClientState.Handshaking: - if (packetData.Id == 0x00) - { - var handshake = Handshake.Deserialize(packetData.Data); - await handshake.HandleAsync(this); - } - else - { - // Handle legacy ping - } - break; - - case ClientState.Login: - await this.HandlePacketAsync(packetData); - break; - case ClientState.Configuration: - Debug.Assert(Player is not null); - - var result = await this.server.EventDispatcher.ExecuteEventAsync(new PacketReceivedEventArgs(Player, this.server, packetData.Id, packetData.Data)); - - if (result == EventResult.Cancelled) - return; - - await this.HandlePacketAsync(packetData); - break; - case ClientState.Play: - Debug.Assert(Player is not null); - - result = await this.server.EventDispatcher.ExecuteEventAsync(new PacketReceivedEventArgs(Player, this.server, packetData.Id, packetData.Data)); - - if (result == EventResult.Cancelled) - return; - - await this.HandlePacketAsync(packetData); - - break; - case ClientState.Closed: - default: - break; + using var packetData = await GetNextPacketAsync(); + + if (State == ClientState.Play && packetData.Data.Length < 0)//Empty packets get sent. + Disconnect(); + + switch (State) + { + case ClientState.Status: // Server ping/list + if (packetData.Id == 0x00) + { + var status = new ServerStatus(this.server); + + _ = await this.server.EventDispatcher.ExecuteEventAsync(new ServerStatusRequestEventArgs(this.server, status)); + + SendPacket(new StatusResponsePacket(status)); + } + else if (packetData.Id == 0x01) + { + var pong = Net.Packets.Status.Serverbound.PingRequestPacket.Deserialize(packetData.Data); + + SendPacket(new PongResponsePacket { Timestamp = pong.Timestamp }); + Disconnect(); + } + break; + + case ClientState.Handshaking: + if (packetData.Id == 0x00) + { + await IntentionPacket.Deserialize(packetData.Data).HandleAsync(this); + } + else + { + // Handle legacy ping + } + break; + + case ClientState.Login: + await this.HandlePacketAsync(packetData); + break; + case ClientState.Configuration: + Debug.Assert(Player is not null); + + var result = await this.server.EventDispatcher.ExecuteEventAsync(new PacketReceivedEventArgs(Player, this.server, packetData.Id, packetData.Data)); + + if (result == EventResult.Cancelled) + return; + + await this.HandlePacketAsync(packetData); + break; + case ClientState.Play: + Debug.Assert(Player is not null); + + result = await this.server.EventDispatcher.ExecuteEventAsync(new PacketReceivedEventArgs(Player, this.server, packetData.Id, packetData.Data)); + + if (result == EventResult.Cancelled) + return; + + await this.HandlePacketAsync(packetData); + + break; + case ClientState.Closed: + default: + break; + } } } + catch (OperationCanceledException) + { + this.Logger.LogDebug("Client({id}) main loop was cancelled", this.id); + } } public async Task StartConnectionAsync() @@ -305,7 +321,8 @@ public async Task StartConnectionAsync() await this.server.EventDispatcher.ExecuteEventAsync(new PlayerLeaveEventArgs(Player, this.server, DateTimeOffset.Now)); } - this.Disconnect(); + Disconnected?.Invoke(this); + this.Dispose();//Dispose client after } @@ -358,7 +375,7 @@ public async Task TryValidateEncryptionResponseAsync(byte[] sharedSecret, this.EncryptionEnabled = true; this.minecraftStream = new EncryptedMinecraftStream(networkStream, sharedKey); - this.SendPacket(new LoginSuccess(Player.Uuid, Player.Username) + this.SendPacket(new LoginFinishedPacket(Player.Uuid, Player.Username) { SkinProperties = this.Player.SkinProperties, }); @@ -381,7 +398,7 @@ public void Initialize(World world) this.randomToken = randomToken; - this.SendPacket(new EncryptionRequest + this.SendPacket(new HelloPacket { PublicKey = publicKey, VerifyToken = randomToken, @@ -395,7 +412,7 @@ public void InitializeOffline(string username, World world) this.Player = new Player(GuidHelper.FromStringHash($"OfflinePlayer:{username}"), username, this, world); - this.SendPacket(new LoginSuccess(Player.Uuid, Player.Username) + this.SendPacket(new LoginFinishedPacket(Player.Uuid, Player.Username) { SkinProperties = this.Player.SkinProperties, }); @@ -423,7 +440,16 @@ private async ValueTask HandlePacketAsync(PacketData packetData) return false; } - public async ValueTask DisconnectAsync(ChatMessage reason) => await this.QueuePacketAsync(new DisconnectPacket(reason, State)); + public async ValueTask DisconnectAsync(ChatMessage reason) + { + if (this.State == ClientState.Login) + { + await this.QueuePacketAsync(new LoginDisconnectPacket { ReasonJson = reason.ToString(Globals.JsonOptions) }); + return; + } + + await this.QueuePacketAsync(new DisconnectPacket { Reason = reason }); + } public async ValueTask QueuePacketAsync(IClientboundPacket packet) { @@ -449,7 +475,7 @@ internal void SendPacket(IClientboundPacket packet) { if (!compressionEnabled) { - packet.Serialize(minecraftStream); + this.minecraftStream.WritePacket(packet); } else { @@ -502,4 +528,4 @@ public void Dispose() GC.SuppressFinalize(this); } -} \ No newline at end of file +} diff --git a/Obsidian/Entities/Player.Helpers.cs b/Obsidian/Entities/Player.Helpers.cs index fa325174..aec21ca0 100644 --- a/Obsidian/Entities/Player.Helpers.cs +++ b/Obsidian/Entities/Player.Helpers.cs @@ -184,7 +184,7 @@ public async ValueTask UpdatePlayerInfoAsync() } await client.QueuePacketAsync(new PlayerInfoUpdatePacket(dict)); - await client.QueuePacketAsync(new PlayerAbilitiesPacket(true) + await client.QueuePacketAsync(new PlayerAbilitiesPacket { Abilities = client.Player!.Abilities }); @@ -217,25 +217,22 @@ await client.QueuePacketAsync(new PlayerInfoUpdatePacket(new Dictionary LoadedChunks.Contains(NumericsHelper.IntsToLong(x, z)) ? this.client.QueuePacketAsync(new UnloadChunkPacket(x, z)) : default; + internal ValueTask UnloadChunkAsync(int x, int z) => LoadedChunks.Contains(NumericsHelper.IntsToLong(x, z)) ? this.client.QueuePacketAsync(new ForgetLevelChunkPacket(x, z)) : default; private async ValueTask TrySpawnPlayerAsync(VectorF position) { @@ -287,7 +284,7 @@ private async Task PickupNearbyItemsAsync(float distance = 1.5f) if (!item.CanPickup) continue; - this.PacketBroadcaster.QueuePacketToWorld(this.World, new PickupItemPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new TakeItemEntityPacket { CollectedEntityId = item.EntityId, CollectorEntityId = EntityId, @@ -296,10 +293,10 @@ private async Task PickupNearbyItemsAsync(float distance = 1.5f) var slot = Inventory.AddItem(new ItemStack(item.Material, item.Count, item.ItemMeta)); - client.SendPacket(new SetContainerSlotPacket + client.SendPacket(new ContainerSetSlotPacket { Slot = (short)slot, - WindowId = 0, + ContainerId = 0, SlotData = Inventory.GetItem(slot)!, StateId = Inventory.StateId++ }); @@ -351,7 +348,7 @@ private void WriteItems(NbtWriter writer, bool inventory = true) private void InitializePlayer(NbtCompound compound) { - OnGround = compound.GetBool("OnGround"); + MovementFlags = (MovementFlags)compound.GetByte("MovementFlags"); Sleeping = compound.GetBool("Sleeping"); Air = compound.GetShort("Air"); AttackTime = compound.GetShort("AttackTime"); diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index 83931904..3094e676 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -169,7 +169,7 @@ internal Player(Guid uuid, string username, Client client, World world) public ItemStack? GetHeldItem() => Inventory.GetItem(inventorySlot); public ItemStack? GetOffHandItem() => Inventory.GetItem(45); - public async Task DisplayScoreboardAsync(IScoreboard scoreboard, ScoreboardPosition position)//TODO implement new features + public async ValueTask DisplayScoreboardAsync(IScoreboard scoreboard, DisplaySlot slot)//TODO implement new features { var actualBoard = (Scoreboard)scoreboard; @@ -178,7 +178,7 @@ internal Player(Guid uuid, string username, Client client, World world) CurrentScoreboard = actualBoard; - await client.QueuePacketAsync(new UpdateObjectivesPacket + await client.QueuePacketAsync(new SetObjectivePacket { ObjectiveName = actualBoard.name, Mode = ScoreboardMode.Create, @@ -188,24 +188,22 @@ await client.QueuePacketAsync(new UpdateObjectivesPacket foreach (var (_, score) in actualBoard.scores) { - await client.QueuePacketAsync(new UpdateScorePacket + await client.QueuePacketAsync(new SetScorePacket { EntityName = score.DisplayText, ObjectiveName = actualBoard.name, Value = score.Value, - HasDisplayName = false, - HasNumberFormat = false }); } - await client.QueuePacketAsync(new DisplayObjectivePacket + await client.QueuePacketAsync(new SetDisplayObjectivePacket { - ScoreName = actualBoard.name, - Position = position + ObjectiveName = actualBoard.name, + DisplaySlot = slot }); } - public async Task OpenInventoryAsync(BaseContainer container) + public async ValueTask OpenInventoryAsync(BaseContainer container) { OpenedContainer = container; @@ -214,7 +212,7 @@ public async Task OpenInventoryAsync(BaseContainer container) await client.QueuePacketAsync(new OpenScreenPacket(container, nextId)); if (container.HasItems()) - await client.QueuePacketAsync(new SetContainerContentPacket(nextId, container.ToList())); + await client.QueuePacketAsync(new ContainerSetContentPacket(nextId, container.ToList())); } public async override ValueTask TeleportAsync(VectorF pos) @@ -234,7 +232,7 @@ await client.server.EventDispatcher.ExecuteEventAsync( pos )); - await client.QueuePacketAsync(new SynchronizePlayerPositionPacket + await client.QueuePacketAsync(new PlayerPositionPacket { Position = pos, Flags = PositionFlags.None, @@ -252,7 +250,7 @@ public async override ValueTask TeleportAsync(IEntity to) TeleportId = Globals.Random.Next(0, 999); - await client.QueuePacketAsync(new SynchronizePlayerPositionPacket + await client.QueuePacketAsync(new PlayerPositionPacket { Position = to.Position, Flags = PositionFlags.None, @@ -282,55 +280,51 @@ public async override ValueTask TeleportAsync(IWorld world) // reload world stuff and send rest of the info await UpdateChunksAsync(true, 2); - await SendInitialInfoAsync(); + await SendPlayerInfoAsync(); var (chunkX, chunkZ) = Position.ToChunkCoord(); - await client.QueuePacketAsync(new SetCenterChunkPacket(chunkX, chunkZ)); + await client.QueuePacketAsync(new SetChunkCacheCenterPacket(chunkX, chunkZ)); } public ValueTask SendMessageAsync(ChatMessage message, Guid sender, SecureMessageSignature messageSignature) => throw new NotImplementedException(); public ValueTask SendMessageAsync(ChatMessage message) => - client.QueuePacketAsync(new SystemChatMessagePacket(message, false)); + client.QueuePacketAsync(new SystemChatPacket(message, false)); public ValueTask SetActionBarTextAsync(ChatMessage message) => - client.QueuePacketAsync(new SystemChatMessagePacket(message, true)); + client.QueuePacketAsync(new SystemChatPacket(message, true)); - public async Task SendSoundAsync(ISoundEffect soundEffect) + public async ValueTask SendSoundAsync(ISoundEffect soundEffect) { - IClientboundPacket packet = soundEffect.SoundPosition is SoundPosition soundPosition ? - new SoundEffectPacket + ClientboundPacket packet = soundEffect.SoundPosition is SoundPosition soundPosition ? + new SoundPacket { - SoundId = soundEffect.SoundId, + SoundLocation = soundEffect.SoundId, SoundPosition = soundPosition, Category = soundEffect.SoundCategory, Volume = soundEffect.Volume, Pitch = soundEffect.Pitch, Seed = soundEffect.Seed, - SoundName = soundEffect.SoundName, - HasFixedRange = soundEffect.HasFixedRange, - Range = soundEffect.Range + FixedRange = soundEffect.FixedRange } : - new EntitySoundEffectPacket + new SoundEntityPacket { - SoundId = soundEffect.SoundId, + SoundLocation = soundEffect.SoundId, EntityId = soundEffect.EntityId!.Value, Category = soundEffect.SoundCategory, Volume = soundEffect.Volume, Pitch = soundEffect.Pitch, Seed = soundEffect.Seed, - SoundName = soundEffect.SoundName, - HasFixedRange = soundEffect.HasFixedRange, - Range = soundEffect.Range + FixedRange = soundEffect.FixedRange }; await client.QueuePacketAsync(packet); } - public async Task KickAsync(string reason) => await client.DisconnectAsync(ChatMessage.Simple(reason)); - public async Task KickAsync(ChatMessage reason) => await client.DisconnectAsync(reason); + public async ValueTask KickAsync(string reason) => await client.DisconnectAsync(reason); + public async ValueTask KickAsync(ChatMessage reason) => await client.DisconnectAsync(reason); public async Task RespawnAsync(DataKept dataKept = DataKept.Metadata) { @@ -348,14 +342,17 @@ public async Task RespawnAsync(DataKept dataKept = DataKept.Metadata) await client.QueuePacketAsync(new RespawnPacket { - DimensionType = codec.Name, - DimensionName = world.DimensionName, - Gamemode = Gamemode, - PreviousGamemode = Gamemode, - HashedSeed = 0, - IsFlat = false, - IsDebug = false, - DataKept = dataKept + CommonPlayerSpawnInfo = new() + { + DimensionType = codec.Id, + DimensionName = world.DimensionName, + Gamemode = Gamemode, + PreviousGamemode = Gamemode, + HashedSeed = 0, + Flat = false, + Debug = false, + }, + DataKept = dataKept, }); visiblePlayers.Clear(); @@ -365,7 +362,7 @@ await client.QueuePacketAsync(new RespawnPacket await UpdateChunksAsync(true, 2); - await client.QueuePacketAsync(new SynchronizePlayerPositionPacket + await client.QueuePacketAsync(new PlayerPositionPacket { Position = Position, Yaw = 0, @@ -395,55 +392,36 @@ public async override ValueTask KillAsync(IEntity source, ChatMessage deathMessa attacker.visiblePlayers.Remove(this); } - public async override Task WriteAsync(MinecraftStream stream) + public override void Write(INetStreamWriter writer) { - await base.WriteAsync(stream); + base.Write(writer); - await stream.WriteEntityMetdata(15, EntityMetadataType.Float, AdditionalHearts); + writer.WriteEntityMetadataType(15, EntityMetadataType.Float); + writer.WriteFloat(AdditionalHearts); - await stream.WriteEntityMetdata(16, EntityMetadataType.VarInt, XpP); + writer.WriteEntityMetadataType(16, EntityMetadataType.VarInt); + writer.WriteVarInt(XpTotal); - await stream.WriteEntityMetdata(17, EntityMetadataType.Byte, (byte)ClientInformation.DisplayedSkinParts); + writer.WriteEntityMetadataType(17, EntityMetadataType.Byte); + writer.WriteByte((byte)ClientInformation.DisplayedSkinParts); - await stream.WriteEntityMetdata(18, EntityMetadataType.Byte, (byte)ClientInformation.MainHand); - - if (LeftShoulder is not null) - await stream.WriteEntityMetdata(19, EntityMetadataType.Nbt, LeftShoulder); - - if (RightShoulder is not null) - await stream.WriteEntityMetdata(20, EntityMetadataType.Nbt, RightShoulder); - } - - public override void Write(MinecraftStream stream) - { - base.Write(stream); - - stream.WriteEntityMetadataType(15, EntityMetadataType.Float); - stream.WriteFloat(AdditionalHearts); - - stream.WriteEntityMetadataType(16, EntityMetadataType.VarInt); - stream.WriteVarInt(XpTotal); - - stream.WriteEntityMetadataType(17, EntityMetadataType.Byte); - stream.WriteByte((byte)ClientInformation.DisplayedSkinParts); - - stream.WriteEntityMetadataType(18, EntityMetadataType.Byte); - stream.WriteByte((byte)ClientInformation.MainHand); + writer.WriteEntityMetadataType(18, EntityMetadataType.Byte); + writer.WriteByte((byte)ClientInformation.MainHand); if (LeftShoulder is not null) { - stream.WriteEntityMetadataType(19, EntityMetadataType.Nbt); - stream.WriteNbtCompound(new NbtCompound()); + writer.WriteEntityMetadataType(19, EntityMetadataType.Nbt); + ((MinecraftStream)writer).WriteNbtCompound(new NbtCompound()); } if (RightShoulder is not null) { - stream.WriteEntityMetadataType(20, EntityMetadataType.Nbt); - stream.WriteNbtCompound(new NbtCompound()); + writer.WriteEntityMetadataType(20, EntityMetadataType.Nbt); + ((MinecraftStream)writer).WriteNbtCompound(new NbtCompound()); } } - public async Task SetGamemodeAsync(Gamemode gamemode) + public async ValueTask SetGamemodeAsync(Gamemode gamemode) { this.PacketBroadcaster.QueuePacketToWorld(this.World, new PlayerInfoUpdatePacket(CompilePlayerInfo(new UpdateGamemodeInfoAction(gamemode)))); @@ -452,23 +430,23 @@ public async Task SetGamemodeAsync(Gamemode gamemode) Gamemode = gamemode; } - public Task UpdateDisplayNameAsync(string newDisplayName) + public ValueTask UpdateDisplayNameAsync(string newDisplayName) { this.PacketBroadcaster.QueuePacketToWorld(this.World, new PlayerInfoUpdatePacket(CompilePlayerInfo(new UpdateDisplayNameInfoAction(newDisplayName)))); CustomName = newDisplayName; - return Task.CompletedTask; + return default; } - public async Task SendTitleAsync(ChatMessage title, int fadeIn, int stay, int fadeOut) + public async ValueTask SendTitleAsync(ChatMessage title, int fadeIn, int stay, int fadeOut) { - var titlePacket = new SetTitleTextPacket(TitleMode.SetTitle) + var titlePacket = new SetTitleTextPacket { Text = title }; - var titleTimesPacket = new SetTitleAnimationTimesPacket + var titleTimesPacket = new SetTitlesAnimationPacket { FadeIn = fadeIn, FadeOut = fadeOut, @@ -479,9 +457,9 @@ public async Task SendTitleAsync(ChatMessage title, int fadeIn, int stay, int fa await client.QueuePacketAsync(titleTimesPacket); } - public async Task SendTitleAsync(ChatMessage title, ChatMessage subtitle, int fadeIn, int stay, int fadeOut) + public async ValueTask SendTitleAsync(ChatMessage title, ChatMessage subtitle, int fadeIn, int stay, int fadeOut) { - var titlePacket = new SetTitleTextPacket(TitleMode.SetSubtitle) + var titlePacket = new SetSubtitleTextPacket { Text = subtitle }; @@ -491,14 +469,14 @@ public async Task SendTitleAsync(ChatMessage title, ChatMessage subtitle, int fa await SendTitleAsync(title, fadeIn, stay, fadeOut); } - public async Task SendSubtitleAsync(ChatMessage subtitle, int fadeIn, int stay, int fadeOut) + public async ValueTask SendSubtitleAsync(ChatMessage subtitle, int fadeIn, int stay, int fadeOut) { - var titlePacket = new SetTitleTextPacket(TitleMode.SetSubtitle) + var titlePacket = new SetSubtitleTextPacket { Text = subtitle }; - var titleTimesPacket = new SetTitleAnimationTimesPacket + var titleTimesPacket = new SetTitlesAnimationPacket { FadeIn = fadeIn, FadeOut = fadeOut, @@ -509,7 +487,7 @@ public async Task SendSubtitleAsync(ChatMessage subtitle, int fadeIn, int stay, await client.QueuePacketAsync(titleTimesPacket); } - public async Task SendActionBarAsync(string text) + public async ValueTask SendActionBarAsync(string text) { var actionBarPacket = new SetActionBarTextPacket { @@ -519,62 +497,8 @@ public async Task SendActionBarAsync(string text) await client.QueuePacketAsync(actionBarPacket); } - public Task SpawnParticleAsync(ParticleType particle, float x, float y, float z, int count, float extra = 0) => - SpawnParticleAsync(particle, new VectorF(x, y, z), count, extra); - - public Task SpawnParticleAsync(ParticleType particle, float x, float y, float z, int count, float offsetX, - float offsetY, float offsetZ, float extra = 0) => - SpawnParticleAsync(particle, new VectorF(x, y, z), count, offsetX, offsetY, offsetZ, extra); - - public async Task SpawnParticleAsync(ParticleType particle, VectorF pos, int count, float extra = 0) => - await client.QueuePacketAsync(new ParticlePacket - { - Type = particle, - Position = pos, - ParticleCount = count, - MaxSpeed = extra - }); - - public async Task SpawnParticleAsync(ParticleType particle, VectorF pos, int count, float offsetX, float offsetY, - float offsetZ, float extra = 0) => await client.QueuePacketAsync( - new ParticlePacket - { - Type = particle, - Position = pos, - ParticleCount = count, - Offset = new VectorF(offsetX, offsetY, offsetZ), - MaxSpeed = extra - }); - - public Task SpawnParticleAsync(ParticleType particle, float x, float y, float z, int count, ParticleData data, - float extra = 0) => - SpawnParticleAsync(particle, new VectorF(x, y, z), count, extra); - - public Task SpawnParticleAsync(ParticleType particle, float x, float y, float z, int count, float offsetX, float offsetY, float offsetZ, ParticleData data, float extra = 0) => - SpawnParticleAsync(particle, new VectorF(x, y, z), count, offsetX, offsetY, offsetZ, extra); - - public async Task SpawnParticleAsync(ParticleType particle, VectorF pos, int count, ParticleData data, - float extra = 0) => - await client.QueuePacketAsync(new ParticlePacket - { - Type = particle, - Position = pos, - ParticleCount = count, - Data = data, - MaxSpeed = extra - }); - - public async Task SpawnParticleAsync(ParticleType particle, VectorF pos, int count, float offsetX, float offsetY, - float offsetZ, ParticleData data, float extra = 0) => await client.QueuePacketAsync( - new ParticlePacket - { - Type = particle, - Position = pos, - ParticleCount = count, - Data = data, - Offset = new VectorF(offsetX, offsetY, offsetZ), - MaxSpeed = extra - }); + //TODO + public ValueTask SpawnParticleAsync(ParticleData data) => throw new NotImplementedException(); public async Task GrantPermissionAsync(string permissionNode) { @@ -667,9 +591,9 @@ public byte GetNextContainerId() public override string ToString() => Username; - internal async override ValueTask UpdateAsync(VectorF position, bool onGround) + internal async override ValueTask UpdateAsync(VectorF position, MovementFlags movementFlags) { - await base.UpdateAsync(position, onGround); + await base.UpdateAsync(position, movementFlags); HeadY = position.Y + 1.62f; @@ -678,9 +602,9 @@ internal async override ValueTask UpdateAsync(VectorF position, bool onGround) await PickupNearbyItemsAsync(); } - internal async override ValueTask UpdateAsync(VectorF position, Angle yaw, Angle pitch, bool onGround) + internal async override ValueTask UpdateAsync(VectorF position, Angle yaw, Angle pitch, MovementFlags movementFlags) { - await base.UpdateAsync(position, yaw, pitch, onGround); + await base.UpdateAsync(position, yaw, pitch, movementFlags); HeadY = position.Y + 1.62f; @@ -689,9 +613,9 @@ internal async override ValueTask UpdateAsync(VectorF position, Angle yaw, Angle await PickupNearbyItemsAsync(); } - internal async override ValueTask UpdateAsync(Angle yaw, Angle pitch, bool onGround) + internal async override ValueTask UpdateAsync(Angle yaw, Angle pitch, MovementFlags movementFlags) { - await base.UpdateAsync(yaw, pitch, onGround); + await base.UpdateAsync(yaw, pitch, movementFlags); await PickupNearbyItemsAsync(); } @@ -750,9 +674,8 @@ internal async Task UpdateChunksAsync(bool unloadAll = false, int distance var chunk = await world.GetChunkAsync(x, z); if (chunk is not null && chunk.IsGenerated) { - await client.QueuePacketAsync(new ChunkDataAndUpdateLightPacket(chunk)); + await client.QueuePacketAsync(new LevelChunkWithLightPacket(chunk)); - LoadedChunks.Add(NumericsHelper.IntsToLong(chunk.X, chunk.Z)); } else diff --git a/Obsidian/Net/ClientHandlers/ClientHandler.cs b/Obsidian/Net/ClientHandlers/ClientHandler.cs index 87c42f04..4657dbd5 100644 --- a/Obsidian/Net/ClientHandlers/ClientHandler.cs +++ b/Obsidian/Net/ClientHandlers/ClientHandler.cs @@ -20,8 +20,9 @@ internal abstract class ClientHandler var success = true; try { - packet.Populate(data); - await packet.HandleAsync(this.Server, this.Player!); + using var mcStream = new MinecraftStream(data); + packet.Populate(mcStream); + await packet.HandleAsync(this.Server, this.Player); } catch (Exception e) { diff --git a/Obsidian/Net/ClientHandlers/ConfigurationClientHandler.cs b/Obsidian/Net/ClientHandlers/ConfigurationClientHandler.cs index f2c79748..cc290bd5 100644 --- a/Obsidian/Net/ClientHandlers/ConfigurationClientHandler.cs +++ b/Obsidian/Net/ClientHandlers/ConfigurationClientHandler.cs @@ -1,8 +1,5 @@ using Microsoft.Extensions.Logging; -using Obsidian.Net.Packets; -using Obsidian.Net.Packets.Configuration; -using Obsidian.Net.Packets.Play; -using Obsidian.Net.Packets.Play.Clientbound; +using Obsidian.Net.Packets.Common; namespace Obsidian.Net.ClientHandlers; internal sealed class ConfigurationClientHandler : ClientHandler @@ -13,20 +10,16 @@ public async override ValueTask HandleAsync(PacketData packetData) switch (id) { - case 0x00: - return await this.HandleFromPoolAsync(data); - case 0x01://Cookies - break; - case 0x02: - return await HandleFromPoolAsync(data); - case 0x03: - return await HandleFromPoolAsync(data); - case 0x04: + case 0: + return await HandleFromPoolAsync(data); + case 1: + return await HandleFromPoolAsync(data); + case 3: + return await HandleFromPoolAsync(data); + case 4: return await HandleFromPoolAsync(data); - case 0x05://pong useless - break; - case 0x06: - return await HandleFromPoolAsync(data); + case 6: + return await HandleFromPoolAsync(data); default: this.Client.Logger.LogWarning("Packet with id {id} is not being handled.", id); break; diff --git a/Obsidian/Net/ClientHandlers/LoginClientHandler.cs b/Obsidian/Net/ClientHandlers/LoginClientHandler.cs index 8b0aecf1..f9ebb507 100644 --- a/Obsidian/Net/ClientHandlers/LoginClientHandler.cs +++ b/Obsidian/Net/ClientHandlers/LoginClientHandler.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.Logging; using Obsidian.Net.Packets; +using Obsidian.Net.Packets.Common; using Obsidian.Net.Packets.Configuration.Clientbound; -using Obsidian.Net.Packets.Configuration; -using Obsidian.Net.Packets.Login; +using Obsidian.Net.Packets.Login.Serverbound; using Obsidian.Registries; using Obsidian.WorldData; @@ -61,6 +61,11 @@ public async override ValueTask HandleAsync(PacketData packetData) private void Configure() { + this.SendPacket(new SelectKnownPacksPacket + { + KnownPacks = [new() { Id = "core", Version = "1.21.3", Namespace = "minecraft" }] + }); + //This is very inconvenient this.SendPacket(new RegistryDataPacket(CodecRegistry.Biomes.CodecKey, CodecRegistry.Biomes.All.ToDictionary(x => x.Key, x => (ICodec)x.Value))); this.SendPacket(new RegistryDataPacket(CodecRegistry.Dimensions.CodecKey, CodecRegistry.Dimensions.All.ToDictionary(x => x.Key, x => (ICodec)x.Value))); @@ -68,25 +73,28 @@ private void Configure() this.SendPacket(new RegistryDataPacket(CodecRegistry.DamageType.CodecKey, CodecRegistry.DamageType.All.ToDictionary(x => x.Key, x => (ICodec)x.Value))); this.SendPacket(new RegistryDataPacket(CodecRegistry.TrimPattern.CodecKey, CodecRegistry.TrimPattern.All.ToDictionary(x => x.Key, x => (ICodec)x.Value))); this.SendPacket(new RegistryDataPacket(CodecRegistry.TrimMaterial.CodecKey, CodecRegistry.TrimMaterial.All.ToDictionary(x => x.Key, x => (ICodec)x.Value))); - this.SendPacket(new RegistryDataPacket(CodecRegistry.WolfVariant.CodecKey, CodecRegistry.WolfVariant.All.ToDictionary(x => x.Key, x => (ICodec)x.Value))); + this.SendPacket(new RegistryDataPacket(CodecRegistry.WolfVariant.CodecKey, new Dictionary() + { + { CodecRegistry.WolfVariant.Woods.Name, CodecRegistry.WolfVariant.Woods }, + })); this.SendPacket(new RegistryDataPacket(CodecRegistry.PaintingVariant.CodecKey, CodecRegistry.PaintingVariant.All.ToDictionary(x => x.Key, x => (ICodec)x.Value))); - this.SendPacket(UpdateTagsPacket.FromRegistry); + this.SendPacket(UpdateTagsPacket.ClientboundConfiguration with { Tags = TagsRegistry.Categories }); this.SendPacket(FinishConfigurationPacket.Default); } private async Task HandleLoginStartAsync(byte[] data) { - var loginStart = LoginStart.Deserialize(data); + var loginStart = Packets.Login.Serverbound.HelloPacket.Deserialize(data); + var username = this.Server.Configuration.Network.MulitplayerDebugMode ? $"Player{Globals.Random.Next(1, 999)}" : loginStart.Username; var world = (World)this.Server.DefaultWorld; this.Logger.LogDebug("Received login request from user {Username}", username); await this.Server.DisconnectIfConnectedAsync(username); - if (this.Server.Configuration.OnlineMode && - await this.Client.TrySetCachedProfileAsync(username)) + if (this.Server.Configuration.OnlineMode && await this.Client.TrySetCachedProfileAsync(username)) { this.Client.Initialize(world); @@ -108,7 +116,7 @@ private async Task HandleEncryptionResponseAsync(byte[] data) this.Client.ThrowIfInvalidEncryptionRequest(); // Decrypt the shared secret and verify the token - var encryptionResponse = EncryptionResponse.Deserialize(data); + var encryptionResponse = KeyPacket.Deserialize(data); await this.Client.TryValidateEncryptionResponseAsync(encryptionResponse.SharedSecret, encryptionResponse.VerifyToken); } diff --git a/Obsidian/Net/ClientHandlers/PlayClientHandler.cs b/Obsidian/Net/ClientHandlers/PlayClientHandler.cs index 9ec1e968..c6632c98 100644 --- a/Obsidian/Net/ClientHandlers/PlayClientHandler.cs +++ b/Obsidian/Net/ClientHandlers/PlayClientHandler.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.Logging; using Obsidian.Net.Packets; -using Obsidian.Net.Packets.Play; -using Obsidian.Net.Packets.Play.Clientbound; +using Obsidian.Net.Packets.Common; using Obsidian.Net.Packets.Play.Serverbound; using System.Collections.Frozen; @@ -10,13 +9,12 @@ internal sealed class PlayClientHandler : ClientHandler { private FrozenDictionary Packets { get; } = new Dictionary() { - { 0x1A, new SetPlayerPositionPacket() }, - { 0x1B, new SetPlayerPositionAndRotationPacket() }, - { 0x1C, new SetPlayerRotationPacket() }, - { 0x20, new PlayerAbilitiesPacket(false) }, - { 0x2F, new SetHeldItemPacket(false) }, - { 0x38, new UseItemOnPacket() }, - { 0x39, new UseItemPacket() } + { 28, new MovePlayerPosPacket() }, + { 29, new MovePlayerPosRotPacket() }, + { 30, new MovePlayerRotPacket() }, + { 37, new PlayerAbilitiesPacket() }, + { 58, new UseItemOnPacket() }, + { 59, new UseItemPacket() }, }.ToFrozenDictionary(); public async override ValueTask HandleAsync(PacketData packetData) @@ -24,61 +22,86 @@ public async override ValueTask HandleAsync(PacketData packetData) var (id, data) = packetData; switch (id) { - case 0x00: - return await HandleFromPoolAsync(data); - case 0x04: - return await HandleFromPoolAsync(data); - case 0x06: - return await HandleFromPoolAsync(data); - case 0x07: - return await HandleFromPoolAsync(data); - case 0x08: - return await HandleFromPoolAsync(data); - case 0x09: - return await HandleFromPoolAsync(data); - case 0x0A: - return await HandleFromPoolAsync(data); - case 0x0C: - return await HandleFromPoolAsync(data); - case 0x0D: - return await HandleFromPoolAsync(data); - case 0x0E: - return await HandleFromPoolAsync(data); - case 0x0F: - return await HandleFromPoolAsync(data); - case 0x12: - return await HandleFromPoolAsync(data); - case 0x16: - return await HandleFromPoolAsync(data); - case 0x18: - return await HandleFromPoolAsync(data); - case 0x20: - return await HandleFromPoolAsync(data); - case 0x22: - return await HandleFromPoolAsync(data); - case 0x24: - return await HandleFromPoolAsync(data); - case 0x25: - return await HandleFromPoolAsync(data); - case 0x29: - return await HandleFromPoolAsync(data); - case 0x2A: - return await HandleFromPoolAsync(data); - case 0x32: - return await HandleFromPoolAsync(data); - case 0x36: - return await HandleFromPoolAsync(data); - case 0x38: - return await HandleFromPoolAsync(data); - case 0x39: - return await HandleFromPoolAsync(data); + case 0: + await HandleFromPoolAsync(data); + break; + case 5: + await HandleFromPoolAsync(data); + break; + case 7: + await HandleFromPoolAsync(data); + break; + case 8: + await HandleFromPoolAsync(data); + break; + case 9: + await HandleFromPoolAsync(data); + break; + case 10: + await HandleFromPoolAsync(data); + break; + case 12: + await HandleFromPoolAsync(data); + break; + case 14: + await HandleFromPoolAsync(data); + break; + case 15: + await HandleFromPoolAsync(data); + break; + case 16: + await HandleFromPoolAsync(data); + break; + case 17: + await HandleFromPoolAsync(data); + break; + case 20: + await HandleFromPoolAsync(data); + break; + case 24: + await HandleFromPoolAsync(data); + break; + case 26: + await HandleFromPoolAsync(data); + break; + case 34: + await HandleFromPoolAsync(data); + break; + case 36: + await HandleFromPoolAsync(data); + break; + case 38: + await HandleFromPoolAsync(data); + break; + case 39: + await HandleFromPoolAsync(data); + break; + case 43: + await HandleFromPoolAsync(data); + break; + case 44: + await HandleFromPoolAsync(data); + break; + case 52: + await HandleFromPoolAsync(data); + break; + case 56: + await HandleFromPoolAsync(data); + break; + case 58: + await HandleFromPoolAsync(data); + break; + case 59: + await HandleFromPoolAsync(data); + break; default: if (!Packets.TryGetValue(id, out var packet)) return false; try { - packet.Populate(data); + using var mcStream = new MinecraftStream(data); + packet.Populate(mcStream); await packet.HandleAsync(this.Server, this.Client.Player!); } catch (Exception e) diff --git a/Obsidian/Net/Packets/Common/KeepAlivePacket.cs b/Obsidian/Net/Packets/Common/KeepAlivePacket.cs index 28cfda27..d459bad2 100644 --- a/Obsidian/Net/Packets/Common/KeepAlivePacket.cs +++ b/Obsidian/Net/Packets/Common/KeepAlivePacket.cs @@ -1,4 +1,5 @@ -using Obsidian.Entities; +using Microsoft.Extensions.Logging; +using Obsidian.Entities; using Obsidian.Serialization.Attributes; namespace Obsidian.Net.Packets.Common; @@ -24,8 +25,50 @@ public override void Serialize(INetStreamWriter writer) writer.WriteLong(KeepAliveId); } + public async override ValueTask HandleAsync(Client client) + { + var time = DateTimeOffset.Now; + var player = client.Player!; + var server = client.server; + + long keepAliveId = time.ToUnixTimeMilliseconds(); + if (keepAliveId - client.lastKeepAliveId > server.Configuration.Network.KeepAliveTimeoutInterval) + { + await client.DisconnectAsync("Timed out.."); + return; + } + + client.Logger.LogDebug("Doing KeepAlive ({keepAliveId}) with {Username} ({Uuid})", keepAliveId, player.Username, player.Uuid); + + this.KeepAliveId = keepAliveId; + + client.SendPacket(this); + + client.lastKeepAliveId = keepAliveId; + } + public async override ValueTask HandleAsync(Server server, Player player) { - await player.client.HandleKeepAliveAsync(this); + ArgumentNullException.ThrowIfNull(player); + + var client = player.client; + + if (this.KeepAliveId != client.lastKeepAliveId) + { + client.Logger.LogWarning("Received invalid KeepAlive from {Username}?? Naughty???? ({Uuid})", player.Username, player.Uuid); + await client.DisconnectAsync("Kicked for invalid KeepAlive."); + return; + } + + // from now on we know this keepalive is VALID and WITHIN BOUNDS + decimal ping = DateTimeOffset.Now.ToUnixTimeMilliseconds() - this.KeepAliveId; + ping = Math.Min(int.MaxValue, ping); // convert within integer bounds + ping = Math.Max(0, ping); // negative ping is impossible. + + client.Ping = (int)ping; + client.Logger.LogDebug("Valid KeepAlive ({KeepAliveId}) handled from {Username} ({Uuid})", this.KeepAliveId, player.Username, player.Uuid); + // KeepAlive is handled. + + client.lastKeepAliveId = null; } } diff --git a/Obsidian/Net/Packets/CommonPacket.cs b/Obsidian/Net/Packets/CommonPacket.cs index 8fb35faa..656d547d 100644 --- a/Obsidian/Net/Packets/CommonPacket.cs +++ b/Obsidian/Net/Packets/CommonPacket.cs @@ -9,4 +9,5 @@ public virtual void Serialize(INetStreamWriter writer) { } public virtual void Populate(INetStreamReader reader) { } public virtual ValueTask HandleAsync(Server server, Player player) => default; + public virtual ValueTask HandleAsync(Client client) => default; } diff --git a/Obsidian/Net/Packets/Configuration/Serverbound/FinishConfigurationPacket.cs b/Obsidian/Net/Packets/Configuration/Serverbound/FinishConfigurationPacket.cs index 5caa536a..6a92f49c 100644 --- a/Obsidian/Net/Packets/Configuration/Serverbound/FinishConfigurationPacket.cs +++ b/Obsidian/Net/Packets/Configuration/Serverbound/FinishConfigurationPacket.cs @@ -1,14 +1,67 @@ using Microsoft.Extensions.Logging; +using Obsidian.API.Events; using Obsidian.Entities; +using Obsidian.Net.Packets.Common; +using Obsidian.Net.Packets.Play.Clientbound; +using Obsidian.Registries; +using System.Diagnostics; namespace Obsidian.Net.Packets.Configuration.Serverbound; public sealed partial class FinishConfigurationPacket { - //TODO move connect logic into here public async override ValueTask HandleAsync(Server server, Player player) { - player.client.Logger.LogDebug("Got finished configuration"); + var client = player.client; - await player.client.ConnectAsync(); + client.Logger.LogDebug("Got finished configuration"); + + client.SetState(ClientState.Play); + await player.LoadAsync(); + if (!server.OnlinePlayers.TryAdd(player.Uuid, player)) + client.Logger.LogWarning("Failed to add player {Username} to online players. Undefined behavior ahead!", player.Username); + + if (!CodecRegistry.TryGetDimension(player.World.DimensionName, out var codec) || !CodecRegistry.TryGetDimension("minecraft:overworld", out codec)) + throw new UnreachableException("Failed to retrieve proper dimension for player."); + + await client.QueuePacketAsync(new LoginPacket + { + EntityId = player.EntityId, + DimensionNames = CodecRegistry.Dimensions.All.Keys.ToList(), + CommonPlayerSpawnInfo = new() + { + Gamemode = player.Gamemode, + DimensionType = codec.Id, + DimensionName = codec.Name, + HashedSeed = 0, + Flat = false + }, + ReducedDebugInfo = false, + EnableRespawnScreen = true, + }); + + await client.QueuePacketAsync(new SetDefaultSpawnPositionPacket(player.world.LevelData.SpawnPosition, 0)); + await client.QueuePacketAsync(new SetTimePacket(player.world.LevelData.Time, player.world.LevelData.DayTime, true)); + await client.QueuePacketAsync(new GameEventPacket(player.world.LevelData.Raining ? ChangeGameStateReason.BeginRaining : ChangeGameStateReason.EndRaining)); + + await client.QueuePacketAsync(CustomPayloadPacket.ClientboundPlay with { Channel = "minecraft:brand", PluginData = server.BrandData }); + await client.QueuePacketAsync(CommandsRegistry.Packet); + + await player.UpdatePlayerInfoAsync(); + await player.SendPlayerInfoAsync(); + + await client.QueuePacketAsync(new GameEventPacket(ChangeGameStateReason.StartWaitingForLevelChunks)); + + player.TeleportId = Globals.Random.Next(0, 999); + await client.QueuePacketAsync(new PlayerPositionPacket + { + Position = player.Position, + Yaw = 0, + Pitch = 0, + Flags = PositionFlags.None, + TeleportId = player.TeleportId + }); + + await player.UpdateChunksAsync(distance: 7); + await server.EventDispatcher.ExecuteEventAsync(new PlayerJoinEventArgs(player, server, DateTimeOffset.Now)); } } diff --git a/Obsidian/Net/Packets/Handshake/IntentionPacket.cs b/Obsidian/Net/Packets/Handshake/IntentionPacket.cs index 526bb895..3c31e58d 100644 --- a/Obsidian/Net/Packets/Handshake/IntentionPacket.cs +++ b/Obsidian/Net/Packets/Handshake/IntentionPacket.cs @@ -1,4 +1,7 @@ +using Microsoft.Extensions.Logging; +using Obsidian.API.Utilities; using Obsidian.Serialization.Attributes; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace Obsidian.Net.Packets.Handshake.Serverbound; @@ -23,4 +26,35 @@ public override void Populate(INetStreamReader reader) this.ServerPort = reader.ReadUnsignedShort(); this.NextState = (ClientState)reader.ReadVarInt(); } + + public async override ValueTask HandleAsync(Client client) + { + var nextState = this.NextState; + + if (nextState == ClientState.Login) + { + if ((int)this.Version > (int)Server.DefaultProtocol) + { + await client.DisconnectAsync($"Outdated server! I'm still on {Server.DefaultProtocol.GetDescription()}."); + } + else if ((int)this.Version < (int)Server.DefaultProtocol) + { + await client.DisconnectAsync($"Outdated client! Please use {Server.DefaultProtocol.GetDescription()}."); + } + } + else if (nextState is not ClientState.Status or ClientState.Login or ClientState.Handshaking) + { + client.Logger.LogWarning("Client sent unexpected state ({RedText}{ClientState}{WhiteText}), forcing it to disconnect.", ChatColor.Red, nextState, ChatColor.White); + await client.DisconnectAsync($"Invalid client state! Expected Status or Login, received {nextState}."); + } + + client.SetState(nextState == ClientState.Login && this.Version != Server.DefaultProtocol ? ClientState.Closed : nextState); + + + var versionDesc = this.Version.GetDescription(); + if (versionDesc is null) + return;//No need to log if version description is null + + client.Logger.LogInformation("Handshaking with client (protocol: {YellowText}{VersionDescription}{WhiteText} [{YellowText}{Version}{WhiteText}], server: {YellowText}{ServerAddress}:{ServerPort}{WhiteText})", ChatColor.Yellow, versionDesc, ChatColor.White, ChatColor.Yellow, this.Version, ChatColor.White, ChatColor.Yellow, this.ServerAddress, this.ServerPort, ChatColor.White); + } } diff --git a/Obsidian/Net/Packets/IServerboundPacket.cs b/Obsidian/Net/Packets/IServerboundPacket.cs index c39e382c..5ea993a0 100644 --- a/Obsidian/Net/Packets/IServerboundPacket.cs +++ b/Obsidian/Net/Packets/IServerboundPacket.cs @@ -6,4 +6,5 @@ public interface IServerboundPacket : IPacket { public void Populate(INetStreamReader reader); public ValueTask HandleAsync(Server server, Player player); + public ValueTask HandleAsync(Client client); } diff --git a/Obsidian/Net/Packets/ServerboundPacket.cs b/Obsidian/Net/Packets/ServerboundPacket.cs index 677a433c..216c917a 100644 --- a/Obsidian/Net/Packets/ServerboundPacket.cs +++ b/Obsidian/Net/Packets/ServerboundPacket.cs @@ -8,4 +8,5 @@ public abstract class ServerboundPacket : IServerboundPacket public virtual void Populate(INetStreamReader reader) { } public virtual ValueTask HandleAsync(Server server, Player player) => default; + public virtual ValueTask HandleAsync(Client client) => default; } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 898a3afa..06b8827c 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -574,7 +574,10 @@ private async Task LoopAsync() { foreach (var client in _clients.Where(x => x.State == ClientState.Play || x.State == ClientState.Configuration)) { - await new KeepAlivePacket().HandleAsync(client); + if (client.State == ClientState.Play) + await KeepAlivePacket.ClientboundPlay.HandleAsync(client); + else + await KeepAlivePacket.ClientboundConfiguration.HandleAsync(client); } keepAliveTicks = 0;