Skip to content

Commit

Permalink
Fix chat messages (#449)
Browse files Browse the repository at this point in the history
* Re-implement chat message reading/writing

* Side quest: Fix chat commands
  • Loading branch information
Tides authored Sep 11, 2024
1 parent b0bda22 commit 9f6e353
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 123 deletions.
5 changes: 4 additions & 1 deletion Obsidian/Commands/Framework/CommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ private async Task ExecuteCommand(string[] args, CommandContext ctx)
await cmd.ExecuteAsync(ctx, args);
}
else
throw new CommandNotFoundException("No such command was found!");
{
await ctx.Sender.SendMessageAsync("No such command was found!");
this.logger.LogError(new CommandNotFoundException("No such command was found!"), "An error has occured while trying to execute command: {args}", args);
}
}
}
11 changes: 9 additions & 2 deletions Obsidian/Net/MinecraftStream.Reading.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Obsidian.API.Utilities;
using Obsidian.API;
using Obsidian.API.Utilities;
using Obsidian.Commands;
using Obsidian.Nbt;
using Obsidian.Registries;
Expand Down Expand Up @@ -535,7 +536,13 @@ public async Task<VectorF> ReadPositionFAsync()
public async Task<Angle> ReadAngleAsync() => new Angle(await this.ReadUnsignedByteAsync());

[ReadMethod]
public ChatMessage ReadChat() => this.ReadString().FromJson<ChatMessage>();
public ChatMessage ReadChat()
{
var reader = new NbtReader(this);
var chatMessage = ChatMessage.Empty;

return !reader.TryReadNextTag<NbtCompound>(out var root) ? chatMessage : chatMessage.FromNbt(root);
}

[ReadMethod]
public byte[] ReadByteArray()
Expand Down
45 changes: 1 addition & 44 deletions Obsidian/Net/MinecraftStream.Writing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,55 +418,12 @@ public void WriteChat(ChatMessage chatMessage)

var writer = new NbtWriter(this, true);

this.WriteChatNbt(writer, chatMessage);
writer.WriteChatMessage(chatMessage);

writer.EndCompound();
writer.TryFinish();
}

private void WriteChatNbt(NbtWriter writer, ChatMessage chatMessage)
{
if (!chatMessage.Text.IsNullOrEmpty())
writer.WriteString("text", chatMessage.Text);
if (!chatMessage.Translate.IsNullOrEmpty())
writer.WriteString("translate", chatMessage.Translate);
if (chatMessage.Color.HasValue)
writer.WriteString("color", chatMessage.Color.Value.ToString());
if (!chatMessage.Insertion.IsNullOrEmpty())
writer.WriteString("insertion", chatMessage.Insertion);

writer.WriteBool("bold", chatMessage.Bold);
writer.WriteBool("italic", chatMessage.Italic);
writer.WriteBool("underlined", chatMessage.Underlined);
writer.WriteBool("strikethrough", chatMessage.Strikethrough);
writer.WriteBool("obfuscated", chatMessage.Obfuscated);

if (chatMessage.ClickEvent != null)
writer.WriteTag(chatMessage.ClickEvent.ToNbt());
if (chatMessage.HoverEvent != null)
writer.WriteTag(chatMessage.HoverEvent.ToNbt());

if (chatMessage.Extra is List<ChatMessage> extras)
{
var list = new NbtList(NbtTagType.Compound, "extra");

foreach (var item in extras)
list.Add(item.ToNbt());

writer.WriteTag(list);
}

if (chatMessage.With is List<ChatMessage> extraChatComponents)
{
var list = new NbtList(NbtTagType.Compound, "with");

foreach (var item in extraChatComponents)
list.Add(item.ToNbt());

writer.WriteTag(list);
}
}

[WriteMethod]
public void WriteEquipment(Equipment equipment)
{
Expand Down
14 changes: 1 addition & 13 deletions Obsidian/Net/Packets/Play/Serverbound/ChatCommandPacket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,7 @@ namespace Obsidian.Net.Packets.Play.Serverbound;
public partial class ChatCommandPacket : IServerboundPacket
{
[Field(0)]
public string Command { get; private set; }

[Field(1)]
public DateTimeOffset Timestamp { get; private set; }

[Field(2)]
public long Salt { get; private set; }

[Field(3)]
public List<ArgumentSignature> ArgumentSignatures { get; private set; }

[Field(4)]
public bool SignedPreview { get; private set; }
public string Command { get; private set; } = default!;

public int Id => 0x04;

Expand Down
41 changes: 41 additions & 0 deletions Obsidian/Net/Packets/Play/Serverbound/SignedChatCommandPacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Microsoft.Extensions.Logging;
using Obsidian.Commands;
using Obsidian.Commands.Framework.Entities;
using Obsidian.Entities;
using Obsidian.Serialization.Attributes;

namespace Obsidian.Net.Packets.Play.Serverbound;

//TODO finish full impl
public sealed partial class SignedChatCommandPacket : IServerboundPacket
{
[Field(0)]
public string Command { get; private set; } = default!;

[Field(1)]
public DateTimeOffset Timestamp { get; private set; }

[Field(2)]
public long Salt { get; private set; }

[Field(3)]
public List<ArgumentSignature> ArgumentSignatures { get; private set; } = default!;

[Field(4)]
public bool SignedPreview { get; private set; }

public int Id => 0x05;

public async ValueTask HandleAsync(Server server, Player player)
{
var context = new CommandContext($"/{this.Command}", new CommandSender(CommandIssuers.Client, player, server._logger), player, server);
try
{
await server.CommandsHandler.ProcessCommand(context);
}
catch (Exception e)
{
server._logger.LogError(e, e.Message);
}
}
}
191 changes: 191 additions & 0 deletions Obsidian/Utilities/Extensions.Nbt.Chat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
using Obsidian.API.Utilities;
using Obsidian.Nbt;
using System.Text.Json;

namespace Obsidian.Utilities;

public partial class Extensions
{
public static void WriteChatMessage(this NbtWriter writer, ChatMessage chatMessage)
{
if (!chatMessage.Text.IsNullOrEmpty())
writer.WriteString("text", chatMessage.Text);
if (!chatMessage.Translate.IsNullOrEmpty())
writer.WriteString("translate", chatMessage.Translate);
if (chatMessage.Color.HasValue)
writer.WriteString("color", chatMessage.Color.Value.ToString());
if (!chatMessage.Insertion.IsNullOrEmpty())
writer.WriteString("insertion", chatMessage.Insertion);

writer.WriteBool("bold", chatMessage.Bold);
writer.WriteBool("italic", chatMessage.Italic);
writer.WriteBool("underlined", chatMessage.Underlined);
writer.WriteBool("strikethrough", chatMessage.Strikethrough);
writer.WriteBool("obfuscated", chatMessage.Obfuscated);

if (chatMessage.ClickEvent != null)
writer.WriteTag(chatMessage.ClickEvent.ToNbt());
if (chatMessage.HoverEvent != null)
writer.WriteTag(chatMessage.HoverEvent.ToNbt());

if (chatMessage.Extra is List<ChatMessage> extras)
{
var list = new NbtList(NbtTagType.Compound, "extra");

foreach (var item in extras)
list.Add(item.ToNbt());

writer.WriteTag(list);
}

if (chatMessage.With is List<ChatMessage> extraChatComponents)
{
var list = new NbtList(NbtTagType.Compound, "with");

foreach (var item in extraChatComponents)
list.Add(item.ToNbt());

writer.WriteTag(list);
}
}

public static NbtCompound ToNbt(this ChatMessage chatMessage, string name = "")
{
var compound = new NbtCompound(name)
{
new NbtTag<bool>("bold", chatMessage.Bold),
new NbtTag<bool>("italic", chatMessage.Italic),
new NbtTag<bool>("underlined", chatMessage.Underlined),
new NbtTag<bool>("strikethrough", chatMessage.Strikethrough),
new NbtTag<bool>("obfuscated", chatMessage.Obfuscated)
};

if (!chatMessage.Text.IsNullOrEmpty())
compound.Add(new NbtTag<string>("text", chatMessage.Text!));
if (!chatMessage.Translate.IsNullOrEmpty())
compound.Add(new NbtTag<string>("translate", chatMessage.Translate!));
if (chatMessage.Color.HasValue)
compound.Add(new NbtTag<string>("color", chatMessage.Color.Value.ToString()));
if (!chatMessage.Insertion.IsNullOrEmpty())
compound.Add(new NbtTag<string>("insertion", chatMessage.Insertion!));

if (chatMessage.ClickEvent != null)
compound.Add(chatMessage.ClickEvent.ToNbt());
if (chatMessage.HoverEvent != null)
compound.Add(chatMessage.HoverEvent.ToNbt());

return compound;
}

public static ChatMessage FromNbt(this ChatMessage chatMessage, NbtCompound root)
{
if (root.TryGetTagValue<string>("text", out var text))
chatMessage.Text = text;

if (root.TryGetTagValue<string>("translate", out var translate))
chatMessage.Translate = translate;

if (root.TryGetTagValue<string>("color", out var color))
chatMessage.Color = new HexColor(color);

if (root.TryGetTagValue<string>("insertion", out var insertion))
chatMessage.Insertion = insertion;

chatMessage.Bold = root.GetBool("bold");
chatMessage.Italic = root.GetBool("italic");
chatMessage.Underlined = root.GetBool("underlined");
chatMessage.Strikethrough = root.GetBool("strikethrough");
chatMessage.Obfuscated = root.GetBool("obfuscated");

if (root.TryGetTag<NbtCompound>("click_event", out var clickEventCompound))
chatMessage.ClickEvent = clickEventCompound.ToClickComponent();
if (root.TryGetTag<NbtCompound>("hover_event", out var hoverEventCompound))
chatMessage.HoverEvent = hoverEventCompound.ToHoverComponent();

if (root.TryGetTag<NbtList>("extra", out var extrasList))
{
var extra = new List<ChatMessage>();

foreach (var extraCompound in extrasList)
extra.Add(ChatMessage.Empty.FromNbt((NbtCompound)extraCompound));

chatMessage.AddExtra(extra);
}

if (root.TryGetTag<NbtList>("with", out var withList))
{
var with = new List<ChatMessage>();

foreach (var withCompound in withList)
with.Add(ChatMessage.Empty.FromNbt((NbtCompound)withCompound));

chatMessage.AddChatComponent(with);
}

return chatMessage;
}

public static ClickComponent ToClickComponent(this NbtCompound compound)
{
return new ClickComponent
{
Action = Enum.Parse<ClickAction>(compound.GetString("action")!.ToPascalCase()),
Value = compound.GetString("value")!
};
}

public static HoverComponent? ToHoverComponent(this NbtCompound compound)
{
if (!compound.TryGetTag<NbtCompound>("contents", out var contentsCompound))
return null;

IHoverContent contents = default!;

if (contentsCompound.TryGetTag<NbtCompound>("chat_message", out var chatMessageCompound))
contents = new HoverChatContent { ChatMessage = ChatMessage.Empty.FromNbt(chatMessageCompound) };
else if (contentsCompound.TryGetTag<NbtCompound>("item", out var itemCompound))
contents = new HoverItemContent { Item = itemCompound.ItemFromNbt()! };
else if (contentsCompound.TryGetTag<NbtCompound>("entity", out var entityCompound))
throw new NotImplementedException("Entity deserialization not supported for chat message.");

return new HoverComponent
{
Action = Enum.Parse<HoverAction>(compound.GetString("action")!.ToPascalCase()),
Contents = contents
};
}

public static NbtCompound ToNbt(this HoverComponent hoverComponent)
{
var compound = new NbtCompound("hoverEvent")
{
new NbtTag<string>("action", JsonNamingPolicy.SnakeCaseLower.ConvertName(hoverComponent.Action.ToString())),
};


if (hoverComponent.Contents is HoverChatContent chatContent)
compound.Add(chatContent.ChatMessage.ToNbt("contents"));
else if (hoverComponent.Contents is HoverItemContent)
throw new NotImplementedException("Missing properties from ItemStack can't implement.");
else if (hoverComponent.Contents is HoverEntityComponent entityComponent)
{
var entityCompound = new NbtCompound("contents")
{
new NbtTag<string>("id", entityComponent.Entity.Uuid.ToString()),
};

if (entityComponent.Entity.CustomName is ChatMessage name)
entityCompound.Add(name.ToNbt("name"));
else
entityCompound.Add(new NbtTag<string>("name", entityComponent.Entity.Type.ToString()));
}

return compound;
}

public static NbtCompound ToNbt(this ClickComponent clickComponent) => new("clickEvent")
{
new NbtTag<string>("action", JsonNamingPolicy.SnakeCaseLower.ConvertName(clickComponent.Action.ToString())),
new NbtTag<string>("value", clickComponent.Value)
};
}
Loading

0 comments on commit 9f6e353

Please sign in to comment.