using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using MinecraftClient.Protocol.Handlers; using MinecraftClient.Protocol.Handlers.StructuredComponents.Components._1_20_6; using MinecraftClient.Protocol.Message; namespace MinecraftClient.Inventory; public enum BookHand { Main = 0, Off = 1 } public sealed record BookLimits(int MaxPages, int MaxPageLength, int MaxTitleLength) { public static BookLimits ForProtocol(int protocolVersion) { int maxPageLength = protocolVersion switch { >= Protocol18Handler.MC_1_21_2_Version => 1024, >= Protocol18Handler.MC_1_17_Version => 8192, _ => 32767 }; int maxTitleLength = protocolVersion switch { >= Protocol18Handler.MC_1_21_2_Version => 32, >= Protocol18Handler.MC_1_17_Version => 128, _ => 16 }; return new BookLimits(100, maxPageLength, maxTitleLength); } } public sealed record BookContent( IReadOnlyList Pages, string? Title, string? Author, int Generation, bool IsSigned) { public static BookContent EmptyWritable { get; } = new([string.Empty], null, null, 0, false); } public static class BookContentHelper { public static bool IsBook(Item? item) => item?.Type is ItemType.WritableBook or ItemType.WrittenBook; public static bool IsWritableBook(Item? item) => item?.Type == ItemType.WritableBook; public static bool TryRead(Item? item, out BookContent content) { content = BookContent.EmptyWritable; if (item is null || item.IsEmpty) return false; return item.Type switch { ItemType.WritableBook => TryReadWritable(item, out content), ItemType.WrittenBook => TryReadWritten(item, out content), _ => false }; } public static Item CreateWritablePayload(Item currentBook, IReadOnlyList pages) { return new Item(ItemType.WritableBook, 1, currentBook.Data, new Dictionary { ["pages"] = pages.Cast().ToArray() }); } public static Item CreateWrittenPayload(Item currentBook, IReadOnlyList pages, string title, string author, bool encodePagesAsJson) { object[] encodedPages = pages .Select(page => encodePagesAsJson ? ToJsonTextComponent(page) : page) .Cast() .ToArray(); return new Item(ItemType.WrittenBook, 1, currentBook.Data, new Dictionary { ["author"] = author, ["title"] = title, ["pages"] = encodedPages }); } public static IReadOnlyList NormalizePages(IEnumerable pages) { string[] normalized = pages.Select(page => page ?? string.Empty).ToArray(); return normalized.Length == 0 ? [string.Empty] : normalized; } private static bool TryReadWritable(Item item, out BookContent content) { if (item.Components is not null) { var component = item.Components.OfType().FirstOrDefault(); if (component is not null) { content = new BookContent( NormalizePages(component.Pages.Select(page => page.RawContent)), null, null, 0, IsSigned: false); return true; } } content = new BookContent(ReadStringList(item.NBT, "pages", parseJson: false), null, null, 0, IsSigned: false); return true; } private static bool TryReadWritten(Item item, out BookContent content) { if (item.Components is not null) { var component = item.Components.OfType().FirstOrDefault(); if (component is not null) { content = new BookContent( NormalizePages(component.Pages.Select(page => page.RawContent)), component.RawTitle, component.Author, component.Generation, IsSigned: true); return true; } } string? title = ReadString(item.NBT, "title"); string? author = ReadString(item.NBT, "author"); int generation = ReadInt(item.NBT, "generation"); content = new BookContent(ReadStringList(item.NBT, "pages", parseJson: true), title, author, generation, IsSigned: true); return true; } private static IReadOnlyList ReadStringList(Dictionary? nbt, string key, bool parseJson) { if (nbt is null || !nbt.TryGetValue(key, out object? value) || value is not object[] values) return [string.Empty]; string[] pages = values .Select(value => value?.ToString() ?? string.Empty) .Select(value => parseJson ? ChatParser.ParseText(value) : value) .ToArray(); return pages.Length == 0 ? [string.Empty] : pages; } private static string? ReadString(Dictionary? nbt, string key) { return nbt is not null && nbt.TryGetValue(key, out object? value) ? value?.ToString() : null; } private static int ReadInt(Dictionary? nbt, string key) { if (nbt is null || !nbt.TryGetValue(key, out object? value) || value is null) return 0; return value switch { int i => i, short s => s, byte b => b, _ when int.TryParse(value.ToString(), out int parsed) => parsed, _ => 0 }; } private static string ToJsonTextComponent(string text) { return JsonSerializer.Serialize(new Dictionary { ["text"] = text }); } }