diff --git a/MinecraftClient/Dialogs/DialogFormatter.cs b/MinecraftClient/Dialogs/DialogFormatter.cs index f3001aaa..d7fe9ffa 100644 --- a/MinecraftClient/Dialogs/DialogFormatter.cs +++ b/MinecraftClient/Dialogs/DialogFormatter.cs @@ -18,7 +18,7 @@ public static class DialogFormatter { 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, instance.Definition.Type)); + builder.AppendLine(string.Format(Translations.dialog_render_type, DisplayType(instance.Definition.Type))); foreach (var body in instance.Definition.Body.Where(static body => !string.IsNullOrWhiteSpace(body.Text))) builder.AppendLine(string.Format(Translations.dialog_render_body, body.Text)); @@ -47,6 +47,19 @@ public static class DialogFormatter return builder.ToString(); } + public static string DisplayType(string rawType) + { + return rawType switch + { + "minecraft:notice" => Translations.dialog_type_notice, + "minecraft:confirmation" => Translations.dialog_type_confirmation, + "minecraft:multi_action" => Translations.dialog_type_multi_action, + "minecraft:dialog_list" => Translations.dialog_type_dialog_list, + "minecraft:server_links" => Translations.dialog_type_server_links, + _ => string.IsNullOrEmpty(rawType) ? Translations.dialog_type_unknown : rawType + }; + } + private static string DescribeInput(DialogInput input) { return input.Kind switch diff --git a/MinecraftClient/Resources/Translations/Translations.Designer.cs b/MinecraftClient/Resources/Translations/Translations.Designer.cs index 01936daf..fcc8bcad 100644 --- a/MinecraftClient/Resources/Translations/Translations.Designer.cs +++ b/MinecraftClient/Resources/Translations/Translations.Designer.cs @@ -8077,5 +8077,29 @@ namespace MinecraftClient { get { return ResourceManager.GetString("dialog.action_desc_unknown", resourceCulture); } } + internal static string dialog_type_notice { + get { return ResourceManager.GetString("dialog.type.notice", resourceCulture); } + } + + internal static string dialog_type_confirmation { + get { return ResourceManager.GetString("dialog.type.confirmation", resourceCulture); } + } + + internal static string dialog_type_multi_action { + get { return ResourceManager.GetString("dialog.type.multi_action", resourceCulture); } + } + + internal static string dialog_type_dialog_list { + get { return ResourceManager.GetString("dialog.type.dialog_list", resourceCulture); } + } + + internal static string dialog_type_server_links { + get { return ResourceManager.GetString("dialog.type.server_links", resourceCulture); } + } + + internal static string dialog_type_unknown { + get { return ResourceManager.GetString("dialog.type.unknown", resourceCulture); } + } + } } diff --git a/MinecraftClient/Resources/Translations/Translations.resx b/MinecraftClient/Resources/Translations/Translations.resx index a0fa7014..90c2f04a 100644 --- a/MinecraftClient/Resources/Translations/Translations.resx +++ b/MinecraftClient/Resources/Translations/Translations.resx @@ -3034,4 +3034,22 @@ see item details. unknown + + Notice + + + Confirmation + + + Multi-action + + + Dialog list + + + Server links + + + Unknown + diff --git a/docs/guide/usage.md b/docs/guide/usage.md index 5d4d0768..8b84e9bb 100644 --- a/docs/guide/usage.md +++ b/docs/guide/usage.md @@ -1647,6 +1647,120 @@ In scripts and remote control, no slash is needed to perform the command, eg. `q +
+dialog + +- **Description:** + + Browse and interact with dialogs sent by the server. Dialogs are popups with a title, body text, and buttons. TUI mode gives the best experience -- use `/dialog open` to view the dialog in a full-screen overlay. + + When a server shows a dialog, MCC prints: + + ``` + [MCC] Server showed custom dialog: Server Notice. Use dialog show. + ``` + + Run `/dialog show` to see the full dialog. Buttons show up like this: + + ``` + Actions: + [1] OK (close) + [2] Visit (command) + [3] Rules (show dialog) + ``` + + The text in parentheses tells you what the button does: `(close)` closes the dialog, `(command)` sends a chat command, `(show dialog)` opens another dialog. + +- **Dialog types:** + + Dialogs come in a few shapes. You might see these in the output of `/dialog show`: + + **Notice** -- A popup with a title, optional body, and one button. + + ``` + Type: Notice + + Welcome to the server! + + Body: Read the rules before playing. + + Actions: + [1] OK (close) + ``` + + **Confirmation** -- A choice between two buttons. + + ``` + Type: Confirmation + + Reset your progress? + + Actions: + [1] Yes (close) + [2] No (close) + ``` + + **Multi-action** -- A grid of buttons. + + ``` + Type: Multi-action + + Choose a destination + + Actions: + [1] Spawn (close) + [2] Shop (close) + [3] Arena (close) + ``` + + **Dialog list** -- A list of sub-dialogs. Clicking one opens another dialog. + + ``` + Type: Dialog list + + Help Topics + + Actions: + [1] Rules (show dialog) + [2] Commands (show dialog) + ``` + + **Server links** -- Shows the server's configured links as buttons. May be empty. + + ``` + Type: Server links + + Server Links + ``` + +- **Usage:** + + ``` + /dialog + /dialog show + /dialog open + /dialog click + /dialog click-label
+
debug diff --git a/tools/run-dialog-test.sh b/tools/run-dialog-test.sh new file mode 100755 index 00000000..12aad1b1 --- /dev/null +++ b/tools/run-dialog-test.sh @@ -0,0 +1,238 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +source "$REPO_ROOT/tools/mcc-env.sh" +source "$REPO_ROOT/.skills/mcc-integration-testing/scripts/common.sh" + +usage() { + cat <<'EOF' +Usage: tools/run-dialog-test.sh + +Integration test for MCC dialog system against a real local server. +Tests all 5 dialog types, button actions, cancel/dismiss, and body content. + +Examples: + tools/run-dialog-test.sh 26.1 + tools/run-dialog-test.sh 1.21.11 +EOF +} + +MC_VERSION="${1:-}" +if [[ -z "$MC_VERSION" ]]; then + usage >&2 + exit 1 +fi + +SESSION_NAME="dialog-test-${MC_VERSION//[^a-zA-Z0-9]/_}" +TEST_ROOT="${TMPDIR:-/tmp}/mcc-dialog-test/${MC_VERSION//\//_}" +CFG="$TEST_ROOT/custom.ini" +MCC_LOG="$TEST_ROOT/mcc-output.log" +INPUT_FILE="$TEST_ROOT/mcc_input.txt" +SERVER_PORT="25565" +PASS=0 +FAIL=0 + +cleanup() { + tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true +} +trap cleanup EXIT + +header() { + echo "" + echo "===== $* =====" +} + +# Strip ANSI escape codes for grep matching +ansi_strip() { + sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' +} + +assert_log() { + local label="$1" + local pattern="$2" + local timeout="${3:-5}" + local elapsed=0 + while (( elapsed < timeout )); do + if [[ -f "$MCC_LOG" ]] && ansi_strip < "$MCC_LOG" | grep -Fq "$pattern" 2>/dev/null; then + echo " PASS: $label" + PASS=$((PASS + 1)) + return 0 + fi + sleep 1 + ((elapsed += 1)) + done + echo " FAIL: $label (expected: '$pattern')" + FAIL=$((FAIL + 1)) +} + +wait_for_pattern() { + local file="$1" + local pattern="$2" + local timeout="${3:-60}" + local elapsed=0 + while (( elapsed < timeout )); do + if [[ -f "$file" ]] && ansi_strip < "$file" | grep -Fq "$pattern" 2>/dev/null; then + return 0 + fi + sleep 1 + ((elapsed += 1)) + done + return 1 +} + +write_input() { + echo "$1" >> "$INPUT_FILE" + sleep 1 +} + +assert_dialog_shown() { + assert_log "$1 received" "Server showed custom dialog: $2" 10 +} + +# ---- Setup ---- + +mkdir -p "$TEST_ROOT" +rm -f "$MCC_LOG" "$INPUT_FILE" + +echo "[Dialog Test] Version: $MC_VERSION, Session: $SESSION_NAME" +echo "[Dialog Test] Log: $MCC_LOG" + +# Ensure server is running +if ! server_running "$MC_VERSION"; then + echo "[Setup] Starting server..." + bash "$REPO_ROOT/tools/start-server.sh" "$MC_VERSION" 2>&1 | tail -1 + wait_for_server_ready "$MC_VERSION" 120 +fi + +echo "[Setup] Server ready." + +# Prepare temp config +echo "[Setup] Preparing MCC config..." +bash "$REPO_ROOT/.skills/mcc-integration-testing/scripts/prepare_offline_mcc_config.sh" \ + "$CFG" "$MC_VERSION" "MCCBot" >/dev/null + +# Disable packet debug (fix in-place to avoid dup sections) +sed_in_place \ + -e '/^\[Debug\]/,/^\s*$/d' \ + "$CFG" + +cat >> "$CFG" < "$INPUT_FILE" +tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true +sleep 1 + +cd "$REPO_ROOT" +# FileInputBot ignores config and uses MCC_INPUT_FILE env var only +INPUT_FILE_ABS="$(realpath "$INPUT_FILE")" +tmux new-session -d -s "$SESSION_NAME" \ + "bash -c 'export MCC_FILE_INPUT=1; export MCC_INPUT_FILE=\"$INPUT_FILE_ABS\"; exec dotnet run --no-build --project MinecraftClient -c Release -- \"$CFG\" \"MCCBot\" \"-\" \"localhost:$SERVER_PORT\"' > '$MCC_LOG' 2>&1" + +echo "[Setup] Waiting for MCC to join..." +if ! wait_for_pattern "$MCC_LOG" "Server was successfully joined" 90; then + echo "ERROR: MCC did not join the server. Check $MCC_LOG" + tail -10 "$MCC_LOG" | ansi_strip + exit 1 +fi +echo "[Setup] MCC joined." +sleep 2 + +# ---- Tests ---- + +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 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 "yes button" "Yes (close)" 3 +assert_log "no button" "No (close)" 3 + +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 button 1" "Alpha (close)" 3 +assert_log "multi_action button 2" "Beta (close)" 3 +assert_log "multi_action button 3" "Gamma (close)" 3 + +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 sub 1" "Sub One (show dialog)" 3 +assert_log "dialog_list sub 2" "Sub Two (show dialog)" 3 + +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 + +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 "^$" +assert_dialog_shown "body dialog" "With Body" +write_input "dialog show" +assert_log "body content" "Hello from body" 10 + +header "7. Custom run_command Action" +mc-rcon 'dialog show MCCBot {type:"minecraft:notice", title:{text:"Run Cmd"}, action:{label:{text:"/list"}, action:{type:"minecraft:run_command", command:"/list"}}}' 2>&1 | ansi_strip | grep -v "^$" +assert_dialog_shown "command action" "Run Cmd" +write_input "dialog show" +assert_log "command action button" "/list (command)" 10 +write_input "dialog click 1" +assert_log "command executed" "There are " 10 + +header "8. show_dialog Action (nested)" +mc-rcon 'dialog show MCCBot {type:"minecraft:notice", title:{text:"First"}, action:{label:{text:"Next"}, action:{type:"minecraft:show_dialog", dialog:{type:"minecraft:notice", title:{text:"Second"}}}}}' 2>&1 | ansi_strip | grep -v "^$" +assert_dialog_shown "first dialog" "First" +write_input "dialog click 1" +assert_dialog_shown "nested dialog" "Second" + +header "9. Dialog Cancel" +mc-rcon 'dialog show MCCBot {type:"minecraft:notice", title:{text:"Cancel Me"}}' 2>&1 | ansi_strip | grep -v "^$" +assert_dialog_shown "cancel test dialog" "Cancel Me" +write_input "dialog cancel" +assert_log "cancel closed dialog" "Dialog action closed locally" 10 + +header "10. Dialog Click-Label" +mc-rcon 'dialog show MCCBot {type:"minecraft:multi_action", title:{text:"Label Test"}, actions:[{label:{text:"Pick Me"}}, {label:{text:"Leave Me"}}]}' 2>&1 | ansi_strip | grep -v "^$" +assert_dialog_shown "click-label dialog" "Label Test" +write_input "dialog click-label Pick Me" +assert_log "click-label worked" "Dialog action closed locally" 10 + +# ---- Results ---- + +echo "" +echo "==========================================" +echo " Dialog Integration Test Results" +echo "==========================================" +echo " PASS: $PASS" +echo " FAIL: $FAIL" +echo "------------------------------------------" + +if [[ $FAIL -gt 0 ]]; then + echo "FAILURES DETECTED. Full log: $MCC_LOG" + echo "Last 20 lines:" + ansi_strip < "$MCC_LOG" | tail -20 + exit 1 +else + echo "ALL TESTS PASSED." + exit 0 +fi