diff --git a/Components/MineSharp.ChatComponent/Chat.cs b/Components/MineSharp.ChatComponent/Chat.cs index d1b4046c..03165c63 100644 --- a/Components/MineSharp.ChatComponent/Chat.cs +++ b/Components/MineSharp.ChatComponent/Chat.cs @@ -3,6 +3,8 @@ using System.Text.RegularExpressions; using MineSharp.Data; using Newtonsoft.Json; +using fNbt; +using System.Diagnostics; namespace MineSharp.ChatComponent; @@ -16,12 +18,17 @@ namespace MineSharp.ChatComponent; /// /// Represents a Chat Message object /// -public class Chat +public partial class Chat { /// /// The raw Json message /// - public string Json { get; } + public string? Json { get; private set; } + + /// + /// The raw NBT Tag message + /// + public NbtTag? NbtTag { get; private set; } /// /// The message without any styling @@ -36,6 +43,9 @@ public class Chat private readonly MinecraftData data; + [GeneratedRegex(@"\\§[0-9a-fk-r]")] + private static partial Regex FormatTagRegex(); + /// /// Create a new ChatComponent /// @@ -49,7 +59,7 @@ public Chat(string json, MinecraftData data) try { this.StyledMessage = this.ParseComponent(JToken.Parse(this.Json)); - this.Message = Regex.Replace(this.StyledMessage, "\\$[0-9a-fk-r]", ""); + this.Message = FormatTagRegex().Replace(this.StyledMessage, ""); } catch (JsonReaderException) { @@ -58,6 +68,27 @@ public Chat(string json, MinecraftData data) } } + /// + /// Create a new ChatComponent with nbt tag + /// + /// + /// + public Chat(NbtTag nbt, MinecraftData data) + { + this.NbtTag = nbt; + this.data = data; + + try + { + this.StyledMessage = this.ParseComponent(nbt); + this.Message = Regex.Replace(this.StyledMessage, "\\§[0-9a-fk-r]", ""); + } catch + { + this.StyledMessage = this.NbtTag.ToString(); + this.Message = this.StyledMessage; + } + } + private string ParseComponent(JToken token, string styleCode = "") { return token.Type switch @@ -70,6 +101,18 @@ private string ParseComponent(JToken token, string styleCode = "") }; } + private string ParseComponent(NbtTag nbt, string styleCode = "") + { + return nbt.TagType switch + { + NbtTagType.List => ParseArray((NbtList)nbt, styleCode), + NbtTagType.Compound => ParseObject((NbtCompound) nbt, styleCode), + NbtTagType.String => nbt.StringValue, + NbtTagType.Int => nbt.StringValue, + _ => throw new Exception($"Type {nbt.TagType} is not supported") + }; ; + } + private string ParseObject(JObject jObject, string styleCode = "") { var sb = new StringBuilder(); @@ -125,6 +168,67 @@ private string ParseObject(JObject jObject, string styleCode = "") + sb.ToString(); } + private string ParseObject(NbtCompound nbt, string styleCode = "") + { + if (nbt.Names.First() == "") + { + return nbt[""].StringValue; + } + + var sb = new StringBuilder(); + + var colorProp = nbt["color"]; + if (colorProp != null) + { + var color = ParseComponent(colorProp.StringValue); + var style = TextStyle.GetTextStyle(color); + styleCode = style != null + ? style.ToString() + : string.Empty; + } + + var extraProp = nbt["extra"]; + if (extraProp != null) + { + var extras = (NbtList)extraProp!; + + foreach (var item in extras) + + sb.Append(this.ParseComponent(item, styleCode) + "§r"); + } + + var textProp = nbt["text"]; + var translateProp = nbt["translate"]; + + if (textProp != null) + { + return styleCode + ParseComponent(textProp, styleCode) + sb.ToString(); + } + + if (translateProp == null) + return sb.ToString(); + + var usingData = new List(); + + var usingProp = nbt["using"]; + var withProp = nbt["with"]; + if (usingProp != null && withProp == null) + withProp = usingProp; + + if (withProp != null) + { + var array = (NbtList)withProp; + for (int i = 0; i < array.Count; i++) + { + usingData.Add(this.ParseComponent(array[i], styleCode)); + } + } + + var ruleName = this.ParseComponent(translateProp); + return styleCode + TranslateString(ruleName, usingData.ToArray(), this.data) + + sb.ToString(); + } + private string ParseArray(JArray jArray, string styleCode = "") { var sb = new StringBuilder(); @@ -136,6 +240,17 @@ private string ParseArray(JArray jArray, string styleCode = "") return sb.ToString(); } + private string ParseArray(NbtList nbtList, string styleCode = "") + { + var sb = new StringBuilder(); + foreach (var token in nbtList) + { + sb.Append(ParseComponent(token, styleCode)); + } + + return sb.ToString(); + } + /// /// Translate a string using the given rule and format strings. /// @@ -158,5 +273,5 @@ public static string TranslateString(string ruleName, string[] usings, Minecraft } /// - public override string ToString() => this.Json; + public override string ToString() => this.Json ?? this.NbtTag!.ToString(); } diff --git a/Components/MineSharp.Protocol/Packets/Clientbound/Play/SetPassengersPacket.cs b/Components/MineSharp.Protocol/Packets/Clientbound/Play/SetPassengersPacket.cs new file mode 100644 index 00000000..1365db0a --- /dev/null +++ b/Components/MineSharp.Protocol/Packets/Clientbound/Play/SetPassengersPacket.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MineSharp.Core.Common; +using MineSharp.Data; +using MineSharp.Data.Protocol; + +namespace MineSharp.Protocol.Packets.Clientbound.Play; + +#pragma warning disable CS1591 +public class SetPassengersPacket : IPacket +{ + public PacketType Type => PacketType.CB_Play_SetPassengers; + + public int EntityId { get; set; } + + public int[] PassengersId { get; set; } + + public SetPassengersPacket(int entityId, int[] passengersId) + { + EntityId = entityId; + PassengersId = passengersId; + } + + public void Write(PacketBuffer buffer, MinecraftData version) + { + buffer.WriteVarInt(this.EntityId); + buffer.WriteVarIntArray(this.PassengersId, (buf, elem) => buffer.WriteVarInt(elem)); + } + + public static IPacket Read(PacketBuffer buffer, MinecraftData version) + { + return new SetPassengersPacket( + buffer.ReadVarInt(), + buffer.ReadVarIntArray(buf => buf.ReadVarInt())); + } +} +#pragma warning restore CS1591 diff --git a/Components/MineSharp.Protocol/Packets/Clientbound/Play/SystemChatMessagePacket.cs b/Components/MineSharp.Protocol/Packets/Clientbound/Play/SystemChatMessagePacket.cs index 8e13f266..1191833f 100644 --- a/Components/MineSharp.Protocol/Packets/Clientbound/Play/SystemChatMessagePacket.cs +++ b/Components/MineSharp.Protocol/Packets/Clientbound/Play/SystemChatMessagePacket.cs @@ -1,3 +1,4 @@ +using MineSharp.ChatComponent; using MineSharp.Core.Common; using MineSharp.Data; using MineSharp.Data.Protocol; @@ -9,6 +10,7 @@ public class SystemChatMessagePacket : IPacket public PacketType Type => PacketType.CB_Play_SystemChat; public string Content { get; set; } + public Chat? Message { get; set; } public int? ChatType { get; set; } public bool? IsOverlay { get; set; } @@ -28,11 +30,19 @@ public SystemChatMessagePacket(string content, int chatType) /// /// /// - public SystemChatMessagePacket(string content, bool isOverlay) + /// + public SystemChatMessagePacket(string content, bool isOverlay, Chat? message = null) { + this.Message = message; this.Content = content; this.IsOverlay = isOverlay; } + public SystemChatMessagePacket(Chat message, bool isOverlay) + { + this.Message = message; + this.Content = message.StyledMessage; + this.IsOverlay = isOverlay; + } public void Write(PacketBuffer buffer, MinecraftData version) { @@ -45,11 +55,25 @@ public void Write(PacketBuffer buffer, MinecraftData version) public static IPacket Read(PacketBuffer buffer, MinecraftData version) { - var content = buffer.ReadString(); - if (version.Version.Protocol >= ProtocolVersion.V_1_19_2) - return new SystemChatMessagePacket(content, buffer.ReadBool()); + if (version.Version.Protocol < ProtocolVersion.V_1_20_3) + { + var content = buffer.ReadString(); + if (version.Version.Protocol >= ProtocolVersion.V_1_19_2) + return new SystemChatMessagePacket(content, buffer.ReadBool()); - return new SystemChatMessagePacket(content, buffer.ReadVarInt()); + return new SystemChatMessagePacket(content, buffer.ReadVarInt()); + } else + { + var content = buffer.ReadNbt(); + try + { + var message = new Chat(content!, version); + return new SystemChatMessagePacket(message, buffer.ReadBool()); + } catch + { + return new SystemChatMessagePacket(content!.ToString(), buffer.ReadBool()); + } + } } } #pragma warning restore CS1591 diff --git a/Components/MineSharp.Protocol/Packets/PacketPalette.cs b/Components/MineSharp.Protocol/Packets/PacketPalette.cs index dc33f4df..eac037fa 100644 --- a/Components/MineSharp.Protocol/Packets/PacketPalette.cs +++ b/Components/MineSharp.Protocol/Packets/PacketPalette.cs @@ -151,7 +151,8 @@ private static void InitializePackets() RegisterPacket(PacketType.CB_Play_ChunkBatchFinished); RegisterPacket(PacketType.CB_Play_Ping); RegisterPacket(PacketType.CB_Play_KickDisconnect); - + RegisterPacket(PacketType.CB_Play_SetPassengers); + RegisterPacket(PacketType.SB_Play_KeepAlive); RegisterPacket(PacketType.SB_Play_Position); RegisterPacket(PacketType.SB_Play_PositionLook); diff --git a/MineSharp.Bot/Plugins/ChatPlugin.cs b/MineSharp.Bot/Plugins/ChatPlugin.cs index be142fc0..5e9a2ac1 100644 --- a/MineSharp.Bot/Plugins/ChatPlugin.cs +++ b/MineSharp.Bot/Plugins/ChatPlugin.cs @@ -1,4 +1,4 @@ -using MineSharp.Bot.Chat; +using MineSharp.Bot.Chat; using MineSharp.Commands; using MineSharp.Core.Common; using MineSharp.Protocol.Packets.Clientbound.Play; @@ -469,7 +469,7 @@ private Task HandleChatMessagePacket(PlayerChatPacket packet) (UUID sender, string message, int type) = packet.Body switch { PlayerChatPacket.V1_19Body v19 => (v19.Sender, v19.SignedChat, v19.MessageType), - PlayerChatPacket.V1_19_2_3Body v19_2 => (v19_2.Sender, v19_2.PlainMessage, v19_2.Type), + PlayerChatPacket.V1_19_2_3Body v19_2 => (v19_2.Sender, v19_2.UnsignedContent ?? v19_2.FormattedMessage ?? v19_2.PlainMessage, v19_2.Type), _ => throw new UnreachableException() }; @@ -497,8 +497,14 @@ private Task HandleSystemChatMessage(SystemChatMessagePacket packet) ? ChatMessageType.GameInfo : ChatMessageType.SystemMessage; } - - this.HandleChatInternal(null, packet.Content, type); + if (packet.Message != null) + { + this.HandleChatInternal(null, packet.Message, type); + } + else + { + this.HandleChatInternal(null, packet.Content, type); + } return Task.CompletedTask; } @@ -528,6 +534,11 @@ private void HandleChatInternal(UUID? sender, string message, ChatMessageType ty this.OnChatMessageReceived?.Invoke(this.Bot, sender, new ChatComponent.Chat(message, this.Bot.Data), type, senderName); } + private void HandleChatInternal(UUID? sender, ChatComponent.Chat message, ChatMessageType type, string? senderName = null) + { + this.OnChatMessageReceived?.Invoke(this.Bot, sender, message, type, senderName); + } + private ChatMessageType GetChatMessageTypeFromRegistry(int index) { var val = this.Bot.Registry["minecraft:chat_type"]["value"][index]["name"]!.StringValue!; diff --git a/MineSharp.Bot/Plugins/EntityPlugin.cs b/MineSharp.Bot/Plugins/EntityPlugin.cs index d70bd231..3f3bfe80 100644 --- a/MineSharp.Bot/Plugins/EntityPlugin.cs +++ b/MineSharp.Bot/Plugins/EntityPlugin.cs @@ -1,4 +1,4 @@ -using MineSharp.Bot.Utils; +using MineSharp.Bot.Utils; using MineSharp.Core.Common.Effects; using MineSharp.Core.Common.Entities; using MineSharp.Protocol.Packets.Clientbound.Play; @@ -53,6 +53,7 @@ public EntityPlugin(MineSharpBot bot) : base(bot) this.Bot.Client.On(this.HandleTeleportEntityPacket); this.Bot.Client.On(this.HandleUpdateAttributesPacket); this.Bot.Client.On(this.HandleSynchronizePlayerPosition); + this.Bot.Client.On(this.HandleSetPassengersPacket); } /// @@ -72,6 +73,36 @@ internal void AddEntity(Entity entity) } } + private void MountEntity(Entity? vehicle, Entity passenger) + { + DismountEntity(passenger); + + passenger.Vehicle = vehicle; + if (vehicle != null) + { + vehicle.Passengers.Add(passenger); + } + } + + private void DismountEntity(Entity passenger) + { + Entity? originalVehicle = passenger.Vehicle; + if (originalVehicle != null) + { + originalVehicle.Passengers.Remove(passenger); + } + passenger.Vehicle = null; + } + + private void DismountPassengers(Entity vehicle) + { + foreach (var passenger in vehicle.Passengers) + { + DismountEntity(passenger); + } + vehicle.Passengers.Clear(); + } + private Task HandleSpawnLivingEntityPacket(SpawnLivingEntityPacket packet) { if (!this.IsEnabled) @@ -126,6 +157,7 @@ private Task HandleRemoveEntitiesPacket(RemoveEntitiesPacket packet) if (!this.Entities.Remove(entityId, out var entity)) continue; + DismountPassengers(entity); this.OnEntityDespawned?.Invoke(this.Bot, entity); } @@ -142,7 +174,7 @@ private Task HandleSetEntityVelocityPacket(SetEntityVelocityPacket packet) return Task.CompletedTask; } - (entity.Position as MutableVector3)!.Set( + (entity.Position as MutableVector3)!.Add( NetUtils.ConvertToVelocity(packet.VelocityX), NetUtils.ConvertToVelocity(packet.VelocityY), NetUtils.ConvertToVelocity(packet.VelocityZ) @@ -159,7 +191,7 @@ private Task HandleUpdateEntityPositionPacket(EntityPositionPacket packet) if (!this.Entities.TryGetValue(packet.EntityId, out var entity)) return Task.CompletedTask; - (entity.Position as MutableVector3)!.Add( + (entity.Position as MutableVector3)!.Set( NetUtils.ConvertDeltaPosition(packet.DeltaX), NetUtils.ConvertDeltaPosition(packet.DeltaY), NetUtils.ConvertDeltaPosition(packet.DeltaZ) @@ -284,4 +316,23 @@ private async Task HandleSynchronizePlayerPosition(PlayerPositionPacket packet) await this.Bot.Client.SendPacket(new ConfirmTeleportPacket(packet.TeleportId)); } + + private Task HandleSetPassengersPacket(SetPassengersPacket packet) + { + if (packet.EntityId != -1 || !this.Entities.TryGetValue(packet.EntityId, out var vehicle)) + { + return Task.CompletedTask; + } + vehicle = packet.EntityId == -1 ? null : vehicle; + + foreach (int passengerId in packet.PassengersId) + { + if (!this.Entities.TryGetValue(packet.EntityId, out var passenger)) + { + continue; + } + MountEntity(vehicle, passenger); + } + return Task.CompletedTask; + } } diff --git a/MineSharp.Core/Common/Entities/Entity.cs b/MineSharp.Core/Common/Entities/Entity.cs index 31480ae4..a83c4ffc 100644 --- a/MineSharp.Core/Common/Entities/Entity.cs +++ b/MineSharp.Core/Common/Entities/Entity.cs @@ -1,4 +1,4 @@ -using MineSharp.Core.Common.Effects; +using MineSharp.Core.Common.Effects; using System.Collections.Concurrent; using MineSharp.Core.Geometry; using Attribute = MineSharp.Core.Common.Entities.Attributes.Attribute; @@ -76,6 +76,10 @@ public class Entity( /// public Entity? Vehicle { get; set; } = null; + /// + /// The Passengers of an entity (for example a boat can takes 2 passengers) + /// + public List Passengers = []; /// /// Returns the attribute with the given name.