diff --git a/MinecraftClient/ClassicConsoleBackend.cs b/MinecraftClient/ClassicConsoleBackend.cs index 02712459..c771d273 100644 --- a/MinecraftClient/ClassicConsoleBackend.cs +++ b/MinecraftClient/ClassicConsoleBackend.cs @@ -1,12 +1,81 @@ using System; +using System.Text.RegularExpressions; namespace MinecraftClient { /// /// Console backend wrapping the ConsoleInteractive library (existing behavior). /// - public class ClassicConsoleBackend : IConsoleBackend + public partial class ClassicConsoleBackend : IConsoleBackend { + private static readonly (byte R, byte G, byte B, char Code)[] McStandardColors = + [ + (0, 0, 0, '0'), // black + (0, 0, 170, '1'), // dark_blue + (0, 170, 0, '2'), // dark_green + (0, 170, 170, '3'), // dark_aqua + (170, 0, 0, '4'), // dark_red + (170, 0, 170, '5'), // dark_purple + (255, 170, 0, '6'), // gold + (170, 170, 170, '7'), // gray + (85, 85, 85, '8'), // dark_gray + (85, 85, 255, '9'), // blue + (85, 255, 85, 'a'), // green + (85, 255, 255, 'b'), // aqua + (255, 85, 85, 'c'), // red + (255, 85, 255, 'd'), // light_purple + (255, 255, 85, 'e'), // yellow + (255, 255, 255, 'f'), // white + ]; + + [GeneratedRegex("§#([0-9a-fA-F]{6})")] + private static partial Regex HexColorRegex(); + + private static char NearestMcColor(byte r, byte g, byte b) + { + int bestIdx = 0; + long bestDist = long.MaxValue; + + for (int i = 0; i < McStandardColors.Length; i++) + { + var (sr, sg, sb, _) = McStandardColors[i]; + long dr = r - sr; + long dg = g - sg; + long db = b - sb; + long dist = dr * dr + dg * dg + db * db; + if (dist < bestDist) + { + bestDist = dist; + bestIdx = i; + } + } + + return McStandardColors[bestIdx].Code; + } + + private static string ResolveHexColors(string text) + { + if (string.IsNullOrEmpty(text) || !text.Contains("§#", StringComparison.Ordinal)) + return text; + + return HexColorRegex().Replace(text, match => + { + ReadOnlySpan hex = match.Groups[1].ValueSpan; + byte r = (byte)((HexVal(hex[0]) << 4) | HexVal(hex[1])); + byte g = (byte)((HexVal(hex[2]) << 4) | HexVal(hex[3])); + byte b = (byte)((HexVal(hex[4]) << 4) | HexVal(hex[5])); + return $"§{NearestMcColor(r, g, b)}"; + }); + } + + private static int HexVal(char c) => c switch + { + >= '0' and <= '9' => c - '0', + >= 'a' and <= 'f' => c - 'a' + 10, + >= 'A' and <= 'F' => c - 'A' + 10, + _ => 0 + }; + public event EventHandler? MessageReceived; public event EventHandler? OnInputChange; @@ -28,7 +97,7 @@ namespace MinecraftClient public void WriteLineFormatted(string text) { - ConsoleInteractive.ConsoleWriter.WriteLineFormatted(text); + ConsoleInteractive.ConsoleWriter.WriteLineFormatted(ResolveHexColors(text)); } public void BeginReadThread() diff --git a/MinecraftClient/Commands/Dialog.cs b/MinecraftClient/Commands/Dialog.cs index 682b468a..826fa592 100644 --- a/MinecraftClient/Commands/Dialog.cs +++ b/MinecraftClient/Commands/Dialog.cs @@ -53,9 +53,11 @@ public class Dialog : Command { var handler = CmdResult.currentHandler!; var current = handler.Dialogs.Current; - return current is null - ? r.SetAndReturn(CmdResult.Status.Fail, Translations.dialog_none) - : r.SetAndReturn(CmdResult.Status.Done, DialogFormatter.Render(current)); + if (current is null) + return r.SetAndReturn(CmdResult.Status.Fail, Translations.dialog_none); + + ConsoleIO.WriteLineFormatted(DialogFormatter.Render(current), acceptnewlines: true); + return r.SetAndReturn(CmdResult.Status.Done); } private static int Open(CmdResult r) diff --git a/MinecraftClient/Dialogs/DialogFormatter.cs b/MinecraftClient/Dialogs/DialogFormatter.cs index d7fe9ffa..c24fca44 100644 --- a/MinecraftClient/Dialogs/DialogFormatter.cs +++ b/MinecraftClient/Dialogs/DialogFormatter.cs @@ -11,17 +11,21 @@ public static class DialogFormatter if (!string.IsNullOrWhiteSpace(definition.ExternalTitle)) return definition.ExternalTitle!; - return string.IsNullOrWhiteSpace(definition.Title) ? definition.Type : definition.Title; + return string.IsNullOrWhiteSpace(definition.Title) ? DisplayType(definition.Type) : definition.Title; } + private const int BoxWidth = 50; + public static string Render(DialogInstance instance) { StringBuilder builder = new(); - builder.AppendLine(string.Format(Translations.dialog_render_header, instance.Revision, instance.Phase, instance.Definition.DisplayTitle())); - builder.AppendLine(string.Format(Translations.dialog_render_type, DisplayType(instance.Definition.Type))); + string border = new('-', BoxWidth); + builder.AppendLine(border); + builder.AppendLine(" " + string.Format(Translations.dialog_render_header, instance.Revision, instance.Phase, instance.Definition.DisplayTitle())); + builder.AppendLine(border); foreach (var body in instance.Definition.Body.Where(static body => !string.IsNullOrWhiteSpace(body.Text))) - builder.AppendLine(string.Format(Translations.dialog_render_body, body.Text)); + builder.AppendLine(body.Text); if (instance.Definition.Inputs.Count > 0) { @@ -30,7 +34,7 @@ public static class DialogFormatter { instance.Values.TryGetValue(input.Key, out var value); value ??= input.InitialValue; - builder.AppendLine(string.Format(Translations.dialog_render_input, input.Key, input.Kind, input.Label, value, DescribeInput(input))); + builder.AppendLine(string.Format(Translations.dialog_render_input, input.Key, DescribeKind(input.Kind), input.Label, value, DescribeInput(input))); } } @@ -41,9 +45,7 @@ public static class DialogFormatter builder.AppendLine(string.Format(Translations.dialog_render_action, action.Index, action.Label, DescribeAction(action.Action))); } - if (instance.Definition.CancelAction is not null || instance.Definition.CanCloseWithEscape) - builder.AppendLine(Translations.dialog_render_cancel_hint); - + builder.Append(border); return builder.ToString(); } @@ -60,6 +62,18 @@ public static class DialogFormatter }; } + private static string DescribeKind(DialogInputKind kind) + { + return kind switch + { + DialogInputKind.Text => Translations.dialog_input_kind_text, + DialogInputKind.Boolean => Translations.dialog_input_kind_boolean, + DialogInputKind.SingleOption => Translations.dialog_input_kind_options, + DialogInputKind.NumberRange => Translations.dialog_input_kind_number, + _ => Translations.dialog_input_kind_unknown + }; + } + private static string DescribeInput(DialogInput input) { return input.Kind switch diff --git a/MinecraftClient/Dialogs/DialogManager.cs b/MinecraftClient/Dialogs/DialogManager.cs index ca34b281..40876b61 100644 --- a/MinecraftClient/Dialogs/DialogManager.cs +++ b/MinecraftClient/Dialogs/DialogManager.cs @@ -72,7 +72,7 @@ public sealed class DialogManager _current = instance; } - _client.Log.Info(string.Format(Translations.dialog_received, instance.Definition.DisplayTitle())); + _client.Log.Info("§e" + string.Format(Translations.dialog_received, instance.Definition.DisplayTitle())); DialogShown?.Invoke(instance); return instance; } diff --git a/MinecraftClient/Protocol/Dialogs/DialogNbtParser.cs b/MinecraftClient/Protocol/Dialogs/DialogNbtParser.cs index cfed430f..2636b5be 100644 --- a/MinecraftClient/Protocol/Dialogs/DialogNbtParser.cs +++ b/MinecraftClient/Protocol/Dialogs/DialogNbtParser.cs @@ -79,9 +79,6 @@ public sealed class DialogNbtParser var body = ParseBody(GetValue(nbt, "body")); var inputs = ParseInputs(GetValue(nbt, "inputs")); - if (string.IsNullOrWhiteSpace(title)) - title = type; - return new DialogCommon(title, externalTitle, canCloseWithEscape, pause, afterAction, body, inputs); } diff --git a/MinecraftClient/Resources/Translations/Translations.Designer.cs b/MinecraftClient/Resources/Translations/Translations.Designer.cs index fcc8bcad..c2803581 100644 --- a/MinecraftClient/Resources/Translations/Translations.Designer.cs +++ b/MinecraftClient/Resources/Translations/Translations.Designer.cs @@ -8101,5 +8101,25 @@ namespace MinecraftClient { get { return ResourceManager.GetString("dialog.type.unknown", resourceCulture); } } + internal static string dialog_input_kind_text { + get { return ResourceManager.GetString("dialog.input_kind.text", resourceCulture); } + } + + internal static string dialog_input_kind_boolean { + get { return ResourceManager.GetString("dialog.input_kind.boolean", resourceCulture); } + } + + internal static string dialog_input_kind_options { + get { return ResourceManager.GetString("dialog.input_kind.options", resourceCulture); } + } + + internal static string dialog_input_kind_number { + get { return ResourceManager.GetString("dialog.input_kind.number", resourceCulture); } + } + + internal static string dialog_input_kind_unknown { + get { return ResourceManager.GetString("dialog.input_kind.unknown", resourceCulture); } + } + } } diff --git a/MinecraftClient/Resources/Translations/Translations.resx b/MinecraftClient/Resources/Translations/Translations.resx index 90c2f04a..34ce6a62 100644 --- a/MinecraftClient/Resources/Translations/Translations.resx +++ b/MinecraftClient/Resources/Translations/Translations.resx @@ -2834,7 +2834,7 @@ see item details. View and interact with the current server custom dialog. - Server showed custom dialog: {0}. Use dialog show. + Server showed custom dialog: {0}. Use /dialog show. Server cleared the custom dialog. @@ -2993,13 +2993,13 @@ see item details. [{0}] {1} ({2}) - Use dialog cancel to close or run the cancel action. + Use /dialog cancel to close or run the cancel action. max {0} chars - true={0}, false={1} + Yes={0}, No={1} options: {0} @@ -3041,7 +3041,7 @@ see item details. Confirmation - Multi-action + Multi Action Dialog list @@ -3052,4 +3052,19 @@ see item details. Unknown + + Text + + + Check box + + + Options + + + Number + + + Unknown + diff --git a/MinecraftClient/Tui/DialogTuiHost.cs b/MinecraftClient/Tui/DialogTuiHost.cs index f926b35e..8b0708a7 100644 --- a/MinecraftClient/Tui/DialogTuiHost.cs +++ b/MinecraftClient/Tui/DialogTuiHost.cs @@ -59,20 +59,27 @@ public static class DialogTuiHost internal sealed class DialogView : Border, IOverlayCloseHandler { + private static readonly Color AccentColor = Color.FromRgb(80, 180, 255); + private static readonly Color BorderColor = Color.FromRgb(70, 70, 70); + private static readonly Color SectionBg = Color.FromRgb(20, 20, 20); + private static readonly Color InputBg = Color.FromRgb(35, 35, 35); + private readonly McClient _handler; private readonly DialogInstance _instance; private readonly TextBlock _status; private readonly Dictionary _inputControls = new(StringComparer.Ordinal); + private readonly StackPanel _inputsPanel; + private readonly WrapPanel _buttonsPanel; public DialogView(McClient handler, DialogInstance instance) { _handler = handler; _instance = instance; - BorderBrush = Brushes.White; + BorderBrush = new SolidColorBrush(BorderColor); BorderThickness = new Thickness(1); - Background = Brushes.Black; - Padding = new Thickness(1); + Background = new SolidColorBrush(Color.FromRgb(12, 12, 12)); + Padding = new Thickness(2); HorizontalAlignment = HorizontalAlignment.Stretch; VerticalAlignment = VerticalAlignment.Stretch; Focusable = true; @@ -81,14 +88,18 @@ internal sealed class DialogView : Border, IOverlayCloseHandler { Foreground = Brushes.Gray, TextWrapping = TextWrapping.Wrap, - Margin = new Thickness(1, 0) + Margin = new Thickness(0, 1, 0, 0) }; + _inputsPanel = new StackPanel { Spacing = 0, Margin = new Thickness(0) }; + _buttonsPanel = new WrapPanel { Orientation = Orientation.Horizontal }; + Child = BuildContent(); AttachedToVisualTree += (_, _) => { AddHandler(KeyDownEvent, OnTunnelKeyDown, RoutingStrategies.Tunnel, handledEventsToo: true); + FocusFirstInput(); Focus(); }; DetachedFromVisualTree += (_, _) => RemoveHandler(KeyDownEvent, OnTunnelKeyDown); @@ -112,75 +123,97 @@ internal sealed class DialogView : Border, IOverlayCloseHandler private Control BuildContent() { - var main = new StackPanel + var root = new DockPanel { Margin = new Thickness(0) }; + + var scroll = new ScrollViewer { - Spacing = 1, - Margin = new Thickness(1) + HorizontalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Disabled, + VerticalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto }; - main.Children.Add(new TextBlock + var main = new StackPanel { Spacing = 0, Margin = new Thickness(0) }; + + // Title + main.Children.Add(McColorParser.CreateColoredTextBlock(_instance.Definition.DisplayTitle())); + + // Separator + main.Children.Add(new Border { - Text = _instance.Definition.DisplayTitle(), - Foreground = Brushes.Yellow, - FontWeight = FontWeight.Bold, - TextWrapping = TextWrapping.Wrap + Height = 1, + Background = new SolidColorBrush(BorderColor), + Margin = new Thickness(0, 1, 0, 1) }); + // Body foreach (var body in _instance.Definition.Body) { if (string.IsNullOrWhiteSpace(body.Text)) continue; + main.Children.Add(McColorParser.CreateColoredTextBlock(body.Text)); + } + // Build action buttons + EnsureActionButtons(); + + // Inputs + foreach (var input in _instance.Definition.Inputs) + main.Children.Add(BuildInput(input)); + + // Action buttons + if (_buttonsPanel.Children.Count > 0) + main.Children.Add(_buttonsPanel); + + // Cancel hint + if (_instance.Definition.CancelAction is not null || _instance.Definition.CanCloseWithEscape) + { main.Children.Add(new TextBlock { - Text = body.Text, - Foreground = Brushes.White, + Text = Translations.dialog_render_cancel_hint, + Foreground = new SolidColorBrush(Color.FromRgb(120, 120, 120)), TextWrapping = TextWrapping.Wrap }); } - foreach (var input in _instance.Definition.Inputs) - main.Children.Add(BuildInput(input)); + main.Children.Add(_status); + scroll.Content = main; + root.Children.Add(scroll); - var buttons = new WrapPanel - { - Orientation = Orientation.Horizontal - }; + return root; + } + + private void EnsureActionButtons() + { + if (_buttonsPanel.Children.Count > 0) + return; foreach (var action in _instance.Definition.Actions) { - var button = new Button + var btn = new Button { - Content = action.Label, - Margin = new Thickness(0, 0, 1, 1), - MinWidth = Math.Max(8, Math.Min(action.Label.Length + 4, 32)) + Content = McColorParser.CreateColoredTextBlock(action.Label), + Padding = new Thickness(1), + BorderThickness = new Thickness(1), + BorderBrush = new SolidColorBrush(Color.FromRgb(60, 60, 60)), + Background = new SolidColorBrush(Color.FromRgb(40, 40, 40)), + Margin = new Thickness(0, 0, 1, 1) }; - button.Click += (_, _) => Click(action.Index); - buttons.Children.Add(button); + btn.Click += (_, _) => Click(action.Index); + _buttonsPanel.Children.Add(btn); } if (_instance.Definition.CancelAction is not null || _instance.Definition.CanCloseWithEscape) { var cancel = new Button { - Content = Translations.tui_dialog_cancel, - Margin = new Thickness(0, 0, 1, 1) + Content = McColorParser.CreateColoredTextBlock(Translations.tui_dialog_cancel), + Padding = new Thickness(1), + BorderThickness = new Thickness(1), + BorderBrush = new SolidColorBrush(Color.FromRgb(80, 40, 40)), + Background = new SolidColorBrush(Color.FromRgb(50, 25, 25)) }; cancel.Click += (_, _) => TryCloseByUser(); - buttons.Children.Add(cancel); + _buttonsPanel.Children.Add(cancel); } - - if (buttons.Children.Count > 0) - main.Children.Add(buttons); - - main.Children.Add(_status); - - return new ScrollViewer - { - Content = main, - HorizontalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Disabled, - VerticalScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility.Auto - }; } private Control BuildInput(DialogInput input) @@ -188,84 +221,125 @@ internal sealed class DialogView : Border, IOverlayCloseHandler _instance.Values.TryGetValue(input.Key, out var value); value ??= input.InitialValue; - var panel = new StackPanel - { - Spacing = 0, - Margin = new Thickness(0, 1) - }; + var panel = new StackPanel { Spacing = 0, Margin = new Thickness(0) }; if (input.LabelVisible && !string.IsNullOrWhiteSpace(input.Label)) - { - panel.Children.Add(new TextBlock - { - Text = input.Label, - Foreground = Brushes.LightGray, - TextWrapping = TextWrapping.Wrap - }); - } + panel.Children.Add(McColorParser.CreateColoredTextBlock(input.Label)); - Control control = input.Kind switch + Control inner = input.Kind switch { - DialogInputKind.Boolean => BuildBooleanInput(value), + DialogInputKind.Boolean => BuildBooleanInput(value, input), DialogInputKind.SingleOption => BuildOptionInput(input, value), DialogInputKind.NumberRange => BuildNumberInput(input, value), _ => BuildTextInput(input, value) }; - _inputControls[input.Key] = control; - panel.Children.Add(control); + _inputControls[input.Key] = inner; + + if (input.Kind == DialogInputKind.Boolean) + { + panel.Children.Add(inner); + } + else + { + panel.Children.Add(new Border + { + Background = new SolidColorBrush(InputBg), + BorderBrush = new SolidColorBrush(Color.FromRgb(55, 55, 55)), + BorderThickness = new Thickness(1), + Padding = new Thickness(1), + Child = inner + }); + } + return panel; } - private static Control BuildTextInput(DialogInput input, string value) + private Control BuildTextInput(DialogInput input, string value) { - return new TextBox + var tb = new TextBox { Text = value, AcceptsReturn = input.Multiline, - TextWrapping = TextWrapping.Wrap, + TextWrapping = input.Multiline ? TextWrapping.Wrap : TextWrapping.NoWrap, MaxLength = input.MaxLength, Foreground = Brushes.White, - Background = Brushes.Black, - BorderBrush = Brushes.Gray + Background = new SolidColorBrush(InputBg), + BorderThickness = new Thickness(0), + Padding = new Thickness(0) }; + + return tb; } - private static Control BuildBooleanInput(string value) + private Control BuildBooleanInput(string value, DialogInput input) { return new CheckBox { IsChecked = value.Equals("true", StringComparison.OrdinalIgnoreCase), - Foreground = Brushes.White + Foreground = Brushes.White, + Padding = new Thickness(0) }; } - private static Control BuildOptionInput(DialogInput input, string value) + private Control BuildOptionInput(DialogInput input, string value) { var combo = new ComboBox { ItemsSource = input.Options ?? [], - Foreground = Brushes.White + Foreground = Brushes.White, + Background = new SolidColorBrush(InputBg), + BorderThickness = new Thickness(0), + Padding = new Thickness(1, 0) }; - combo.SelectionBoxItemTemplate = null; + combo.SelectedItem = input.Options?.FirstOrDefault(option => option.Id.Equals(value, StringComparison.Ordinal)) ?? input.Options?.FirstOrDefault(); + return combo; } - private static Control BuildNumberInput(DialogInput input, string value) + private Control BuildNumberInput(DialogInput input, string value) { + double numValue = double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed) + ? parsed + : input.InitialNumber ?? input.Start; + + double min = Math.Min(input.Start, input.End); + double max = Math.Max(input.Start, input.End); + + var panel = new DockPanel { LastChildFill = true }; + var slider = new Slider { - Minimum = Math.Min(input.Start, input.End), - Maximum = Math.Max(input.Start, input.End), - Value = double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var number) - ? number - : input.InitialNumber ?? input.Start, + Minimum = min, + Maximum = max, + Value = numValue, TickFrequency = input.Step ?? 1, - IsSnapToTickEnabled = input.Step is not null + IsSnapToTickEnabled = input.Step is not null, + Foreground = new SolidColorBrush(AccentColor) }; - return slider; + + var label = new TextBlock + { + Text = numValue.ToString(CultureInfo.InvariantCulture), + Foreground = Brushes.White, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(2, 0, 0, 0), + MinWidth = 16 + }; + + slider.PropertyChanged += (_, e) => + { + if (e.Property == Slider.ValueProperty) + label.Text = ((float)slider.Value).ToString(CultureInfo.InvariantCulture); + }; + + DockPanel.SetDock(label, Dock.Right); + panel.Children.Add(slider); + panel.Children.Add(label); + + return panel; } private void Click(int index) @@ -306,6 +380,20 @@ internal sealed class DialogView : Border, IOverlayCloseHandler return true; } + private void FocusFirstInput() + { + var first = _inputControls.Values.FirstOrDefault(); + if (first is TextBox tb) + { + tb.Focus(); + tb.SelectAll(); + } + else + { + first?.Focus(); + } + } + private void CloseIfInactive() { var current = _handler.Dialogs.Current; @@ -326,11 +414,22 @@ internal sealed class DialogView : Border, IOverlayCloseHandler private void OnTunnelKeyDown(object? sender, KeyEventArgs e) { - if (e.Key != Key.Escape) + if (e.Key == Key.Escape) + { + TryCloseByUser(); + e.Handled = true; return; + } - TryCloseByUser(); - e.Handled = true; + if (e.Key == Key.Enter) + { + var focused = TopLevel.GetTopLevel(this)?.FocusManager?.GetFocusedElement(); + if (focused is TextBox && _instance.Definition.Actions.Count > 0) + { + Click(_instance.Definition.Actions[0].Index); + e.Handled = true; + } + } } private static string NumberToString(float value) diff --git a/tools/run-dialog-test.sh b/tools/run-dialog-test.sh index 12aad1b1..134fe31f 100755 --- a/tools/run-dialog-test.sh +++ b/tools/run-dialog-test.sh @@ -151,14 +151,14 @@ header "1. Notice Dialog" mc-rcon 'dialog show MCCBot {type:"minecraft:notice", title:{text:"Notice Title"}}' 2>&1 | ansi_strip | grep -v "^$" assert_dialog_shown "notice dialog" "Notice Title" write_input "dialog show" -assert_log "notice type" "Type: Notice" 10 +assert_log "notice render" "Dialog #1" 10 assert_log "notice OK button" "OK (close)" 3 header "2. Confirmation Dialog" mc-rcon 'dialog show MCCBot {type:"minecraft:confirmation", title:{text:"Confirm?"}, yes:{label:{text:"Yes"}}, no:{label:{text:"No"}}}' 2>&1 | ansi_strip | grep -v "^$" assert_dialog_shown "confirmation" "Confirm?" write_input "dialog show" -assert_log "confirmation type" "Type: Confirmation" 10 +assert_log "confirmation render" "Dialog #1" 10 assert_log "yes button" "Yes (close)" 3 assert_log "no button" "No (close)" 3 @@ -166,7 +166,7 @@ header "3. Multi-Action Dialog" mc-rcon 'dialog show MCCBot {type:"minecraft:multi_action", title:{text:"Choose"}, actions:[{label:{text:"Alpha"}}, {label:{text:"Beta"}}, {label:{text:"Gamma"}}]}' 2>&1 | ansi_strip | grep -v "^$" assert_dialog_shown "multi_action" "Choose" write_input "dialog show" -assert_log "multi_action type" "Type: Multi-action" 10 +assert_log "multi_action render" "Dialog #1" 10 assert_log "multi_action button 1" "Alpha (close)" 3 assert_log "multi_action button 2" "Beta (close)" 3 assert_log "multi_action button 3" "Gamma (close)" 3 @@ -175,7 +175,7 @@ header "4. Dialog-List Dialog" mc-rcon 'dialog show MCCBot {type:"minecraft:dialog_list", title:{text:"List"}, dialogs:[{type:"minecraft:notice", title:{text:"Sub One"}}, {type:"minecraft:notice", title:{text:"Sub Two"}}]}' 2>&1 | ansi_strip | grep -v "^$" assert_dialog_shown "dialog_list" "List" write_input "dialog show" -assert_log "dialog_list type" "Type: Dialog list" 10 +assert_log "dialog_list render" "Dialog #1" 10 assert_log "dialog_list sub 1" "Sub One (show dialog)" 3 assert_log "dialog_list sub 2" "Sub Two (show dialog)" 3 @@ -183,7 +183,7 @@ header "5. Server-Links Dialog" mc-rcon 'dialog show MCCBot {type:"minecraft:server_links", title:{text:"Links"}}' 2>&1 | ansi_strip | grep -v "^$" assert_dialog_shown "server_links" "Links" write_input "dialog show" -assert_log "server_links type" "Type: Server links" 10 +assert_log "server_links render" "Dialog #1" 10 header "6. Body Content" mc-rcon 'dialog show MCCBot {type:"minecraft:notice", title:{text:"With Body"}, body:[{type:"minecraft:plain_message", contents:{text:"Hello from body"}}]}' 2>&1 | ansi_strip | grep -v "^$"