diff --git a/.skills/mcc-integration-testing/SKILL.md b/.skills/mcc-integration-testing/SKILL.md new file mode 100644 index 00000000..f6fe207f --- /dev/null +++ b/.skills/mcc-integration-testing/SKILL.md @@ -0,0 +1,56 @@ +--- +name: mcc-integration-testing +description: Repeatable local offline integration testing for Minecraft Console Client against a local Minecraft Java server. Use this whenever the user wants to validate MCC end-to-end against a local server, switch the server to persistent offline mode, run chat or server commands through FileInputBot, exercise inventory/entity handling, or perform deeper smoke testing with mobs, particles, sounds, TNT, and operator actions. +--- + +# MCC Integration Testing + +Use this skill for local MCC validation against the user's `mc-*` and `mcc-*` shell helpers. + +## Workflow + +1. Source `~/.zshrc` in command invocations. +2. Do not read `~/.zshrc` directly. +3. Ensure the target server is configured for persistent offline testing: + - `online-mode=false` + - `enforce-secure-profile=false` + - `enable-rcon=true` + - `rcon.password=test123` +4. Build with `mcc-build`. +5. Run the scripted scenario with `scripts/run_full_spectrum_test.sh`. +6. Summarize the evidence with `scripts/summarize_test_run.sh`. + +## Required Preconditions + +- Server jar exists under `~/Minecraft/Servers//server.jar` +- `eula.txt` contains `eula=true` +- Repo root `MinecraftClient.ini` is the offline MCC test profile +- The MCC config round-trip issue must be fixed before relying on repeated launches + +## Scripts + +- `scripts/ensure_offline_server.sh` + - Generates `server.properties` if missing + - Applies persistent offline and RCON settings +- `scripts/run_full_spectrum_test.sh` + - Builds MCC + - Starts server and MCC + - Runs the full-spectrum scenario + - Verifies key MCC and server log assertions +- `scripts/summarize_test_run.sh` + - Prints the most relevant evidence from the latest run directory + +## Scenario Coverage + +The scripted run should cover: + +- offline join +- MCC-originated chat +- MCC-originated slash command +- internal MCC commands such as `health`, `list`, `inventory`, and `entity` +- OP + creative mode +- passive and hostile mob spawns +- representative sound and particle events +- TNT / explosion handling + +If a command syntax needs to be checked, use `references/command-matrix.md`. diff --git a/.skills/mcc-integration-testing/evals/evals.json b/.skills/mcc-integration-testing/evals/evals.json new file mode 100644 index 00000000..1eb5a870 --- /dev/null +++ b/.skills/mcc-integration-testing/evals/evals.json @@ -0,0 +1,39 @@ +{ + "skill_name": "mcc-integration-testing", + "evals": [ + { + "id": 1, + "prompt": "Configure the local 1.21.11 MCC test server for persistent offline mode, then run a deep MCC integration test covering chat, operator actions, creative inventory, entity tracking, sounds, particles, and TNT.", + "expected_output": "The server is left in offline mode with RCON enabled, MCC joins successfully, and the run reports clear pass or fail evidence from both MCC and server logs.", + "files": [], + "expectations": [ + "The workflow sources ~/.zshrc without reading it directly.", + "The server is configured with online-mode=false and enable-rcon=true.", + "The run uses both mc-rcon and mcc-cmd.", + "The result includes evidence from MCC output and server logs." + ] + }, + { + "id": 2, + "prompt": "Validate that MCC still works after a runtime or framework change by performing a repeatable offline smoke test against the local vanilla server and exercising entity and inventory handling.", + "expected_output": "The response runs the repeatable local workflow, checks for a successful join, verifies inventory and entity commands, and calls out any configuration or protocol regression.", + "files": [], + "expectations": [ + "The workflow builds MCC before running the server scenario.", + "The workflow checks inventory and entity handling explicitly.", + "The response flags config reload failures as regressions." + ] + }, + { + "id": 3, + "prompt": "Use the local MCC testing workflow to run a full-spectrum client/server exercise and summarize the important evidence only.", + "expected_output": "The response runs the scripted scenario and returns a concise summary with pass/fail status, affected commands, and log evidence.", + "files": [], + "expectations": [ + "The workflow uses the scripted test runner.", + "The summary includes join status, MCC command coverage, and server-side effects.", + "The summary points to the saved log locations." + ] + } + ] +} diff --git a/.skills/mcc-integration-testing/references/command-matrix.md b/.skills/mcc-integration-testing/references/command-matrix.md new file mode 100644 index 00000000..9880f2f8 --- /dev/null +++ b/.skills/mcc-integration-testing/references/command-matrix.md @@ -0,0 +1,53 @@ +# Command Matrix + +This skill uses a fixed set of stable commands for local offline integration testing. + +## MCC-side commands via `mcc-cmd` + +- `health` +- `list` +- `inventory player list` +- `/gamemode creative` +- `inventory creativegive 36 Diamond 16` +- `entity` +- `/time query daytime` +- `smoke_test_from_mcc_full_spectrum` + +Notes: +- Lines starting with `/` are sent to the server as chat/commands. +- Non-slash lines are treated as MCC internal commands first, then fall back to chat. + +## Server-side commands via `mc-rcon` + +- `op CursorBot` +- `gamerule sendCommandFeedback true` +- `gamerule logAdminCommands true` +- `time set day` +- `weather clear` + +## Representative entity coverage + +- `execute as CursorBot at @s run summon minecraft:cow ~2 ~ ~` +- `execute as CursorBot at @s run summon minecraft:zombie ~4 ~ ~` +- `execute as CursorBot at @s run summon minecraft:creeper ~6 ~ ~` +- `execute as CursorBot at @s run summon minecraft:skeleton ~8 ~ ~` +- `execute as CursorBot at @s run summon minecraft:villager ~-2 ~ ~` +- `execute as CursorBot at @s run summon minecraft:allay ~-4 ~ ~` +- `execute as CursorBot at @s run summon minecraft:armor_stand ~ ~ ~2` + +## Representative particle coverage + +- `execute as CursorBot at @s run particle minecraft:happy_villager ~ ~1 ~ 0.5 0.5 0.5 0 12 force` +- `execute as CursorBot at @s run particle minecraft:end_rod ~ ~1 ~ 0.5 0.5 0.5 0.01 20 force` +- `execute as CursorBot at @s run particle minecraft:explosion ~ ~1 ~ 0 0 0 0 1 force` +- `execute as CursorBot at @s run particle minecraft:totem_of_undying ~ ~1 ~ 0.5 0.5 0.5 0.1 20 force` + +## Representative sound coverage + +- `execute as CursorBot at @s run playsound minecraft:entity.lightning_bolt.thunder master CursorBot ~ ~ ~ 1 1 0` +- `execute as CursorBot at @s run playsound minecraft:block.note_block.bell master CursorBot ~ ~ ~ 1 1 0` + +## Explosion coverage + +- `execute as CursorBot at @s run summon minecraft:tnt ~3 ~ ~` +- `execute as CursorBot at @s run summon minecraft:tnt ~6 ~ ~` diff --git a/.skills/mcc-integration-testing/scripts/ensure_offline_server.sh b/.skills/mcc-integration-testing/scripts/ensure_offline_server.sh new file mode 100755 index 00000000..2aab4267 --- /dev/null +++ b/.skills/mcc-integration-testing/scripts/ensure_offline_server.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env zsh +set -euo pipefail + +set +eu +source ~/.zshrc +set -eu + +VERSION="${1:-1.21.11-Vanilla}" +SERVER_DIR="${MCC_SERVERS:?}/$VERSION" +PROPS_FILE="$SERVER_DIR/server.properties" +SESSION_NAME="mc-${VERSION//./_}" + +if [[ ! -d "$SERVER_DIR" ]]; then + echo "Server directory not found: $SERVER_DIR" >&2 + exit 1 +fi + +if [[ ! -f "$SERVER_DIR/eula.txt" ]] || ! grep -Eq '^eula=true$' "$SERVER_DIR/eula.txt"; then + echo "Missing accepted EULA in $SERVER_DIR/eula.txt" >&2 + exit 1 +fi + +server_running() { + mc-list | grep -Fq "$SESSION_NAME" +} + +wait_for_server_ready() { + local timeout="${1:-60}" + local elapsed=0 + while (( elapsed < timeout )); do + if mc-log "$VERSION" 200 2>/dev/null | grep -Fq "Done ("; then + return 0 + fi + sleep 1 + ((elapsed += 1)) + done + echo "Timed out waiting for $VERSION to become ready" >&2 + return 1 +} + +wait_for_server_stop() { + local timeout="${1:-60}" + local elapsed=0 + while (( elapsed < timeout )); do + if ! server_running; then + return 0 + fi + sleep 1 + ((elapsed += 1)) + done + echo "Timed out waiting for $VERSION to stop" >&2 + return 1 +} + +upsert_property() { + local key="$1" + local value="$2" + + if grep -Eq "^${key}=" "$PROPS_FILE"; then + sed -i "s#^${key}=.*#${key}=${value}#" "$PROPS_FILE" + else + printf '%s=%s\n' "$key" "$value" >> "$PROPS_FILE" + fi +} + +if [[ ! -f "$PROPS_FILE" ]]; then + mc-start "$VERSION" + wait_for_server_ready + mc-stop "$VERSION" + wait_for_server_stop +fi + +if server_running; then + mc-stop "$VERSION" + wait_for_server_stop +fi + +upsert_property "online-mode" "false" +upsert_property "enforce-secure-profile" "false" +upsert_property "enable-rcon" "true" +upsert_property "rcon.port" "25575" +upsert_property "rcon.password" "test123" + +echo "Configured $VERSION for persistent offline testing" diff --git a/.skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh b/.skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh new file mode 100755 index 00000000..6648771a --- /dev/null +++ b/.skills/mcc-integration-testing/scripts/run_full_spectrum_test.sh @@ -0,0 +1,219 @@ +#!/usr/bin/env zsh +set -euo pipefail + +set +eu +source ~/.zshrc +set -eu + +SCRIPT_DIR="${0:A:h}" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +VERSION="${1:-1.21.11-Vanilla}" +RUN_ROOT="${TMPDIR:-/tmp}/mcc-integration-testing" +RUN_ID="$(date +%Y%m%d-%H%M%S)" +RUN_DIR="$RUN_ROOT/$RUN_ID" +SERVER_LOG_FILE="$MCC_SERVERS/$VERSION/logs/latest.log" +MCC_LOG="$RUN_DIR/mcc.log" +BUILD_LOG="$RUN_DIR/build.log" +SERVER_TMUX_LOG="$RUN_DIR/server-tmux.log" +SERVER_FILE_LOG="$RUN_DIR/server-latest.log" +INPUT_FILE="$REPO_ROOT/mcc_input.txt" +MCC_PID="" + +mkdir -p "$RUN_DIR" + +cleanup() { + if [[ -n "${MCC_PID:-}" ]] && kill -0 "$MCC_PID" 2>/dev/null; then + mcc-cmd "quit" >/dev/null 2>&1 || true + sleep 2 + kill "$MCC_PID" 2>/dev/null || true + wait "$MCC_PID" 2>/dev/null || true + fi + + mc-stop "$VERSION" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +wait_for_file_pattern() { + local file="$1" + local pattern="$2" + local description="$3" + local timeout="${4:-60}" + local elapsed=0 + + while (( elapsed < timeout )); do + if [[ -f "$file" ]] && grep -Fq "$pattern" "$file"; then + return 0 + fi + sleep 1 + ((elapsed += 1)) + done + + echo "Timed out waiting for: $description" >&2 + return 1 +} + +wait_for_server_ready() { + local timeout="${1:-60}" + local elapsed=0 + + while (( elapsed < timeout )); do + if mc-log "$VERSION" 250 2>/dev/null | grep -Fq "Done ("; then + return 0 + fi + sleep 1 + ((elapsed += 1)) + done + + echo "Timed out waiting for server readiness" >&2 + return 1 +} + +wait_for_server_log_pattern() { + local pattern="$1" + local description="$2" + local timeout="${3:-60}" + local elapsed=0 + + while (( elapsed < timeout )); do + if [[ -f "$SERVER_LOG_FILE" ]] && grep -Fq "$pattern" "$SERVER_LOG_FILE"; then + return 0 + fi + sleep 1 + ((elapsed += 1)) + done + + echo "Timed out waiting for server log: $description" >&2 + return 1 +} + +capture_server_logs() { + mc-log "$VERSION" 400 > "$SERVER_TMUX_LOG" 2>/dev/null || true + if [[ -f "$SERVER_LOG_FILE" ]]; then + cp "$SERVER_LOG_FILE" "$SERVER_FILE_LOG" + fi +} + +fail() { + capture_server_logs + echo "FAIL: $1" >&2 + echo "Run directory: $RUN_DIR" >&2 + exit 1 +} + +assert_contains() { + local file="$1" + local pattern="$2" + local description="$3" + + grep -Fq "$pattern" "$file" || fail "$description" +} + +assert_not_contains() { + local file="$1" + local pattern="$2" + local description="$3" + + if grep -Fq "$pattern" "$file"; then + fail "$description" + fi +} + +run_server_command() { + local cmd="$1" + echo "SERVER> $cmd" + mc-rcon "$cmd" >/dev/null || fail "Server command failed: $cmd" +} + +run_mcc_command() { + local cmd="$1" + echo "MCC> $cmd" + mcc-cmd "$cmd" + sleep 2 +} + +"$SCRIPT_DIR/ensure_offline_server.sh" "$VERSION" + +: > "$INPUT_FILE" + +echo "Building MCC..." +mcc-build > "$BUILD_LOG" 2>&1 || fail "mcc-build failed" + +echo "Starting server..." +mc-start "$VERSION" >/dev/null +wait_for_server_ready || fail "Server did not become ready" + +echo "Starting MCC..." +mcc-run 25565 > "$MCC_LOG" 2>&1 & +MCC_PID=$! + +wait_for_file_pattern "$MCC_LOG" "Server was successfully joined." "MCC join success" 90 || fail "MCC failed to join" +wait_for_server_log_pattern "CursorBot joined the game" "server join entry" 30 || fail "Server never logged the join" + +run_server_command "op CursorBot" +run_server_command "gamerule sendCommandFeedback true" +run_server_command "gamerule logAdminCommands true" +run_server_command "time set day" +run_server_command "weather clear" +sleep 2 + +run_mcc_command "health" +run_mcc_command "list" +run_mcc_command "inventory player list" +run_mcc_command "/gamemode creative" +run_mcc_command "inventory creativegive 36 Diamond 16" +run_mcc_command "inventory player list" +run_mcc_command "entity" +run_mcc_command "/time query daytime" +run_mcc_command "smoke_test_from_mcc_full_spectrum" + +run_server_command "execute as CursorBot at @s run summon minecraft:cow ~2 ~ ~" +run_server_command "execute as CursorBot at @s run summon minecraft:zombie ~4 ~ ~" +run_server_command "execute as CursorBot at @s run summon minecraft:creeper ~6 ~ ~" +run_server_command "execute as CursorBot at @s run summon minecraft:skeleton ~8 ~ ~" +run_server_command "execute as CursorBot at @s run summon minecraft:villager ~-2 ~ ~" +run_server_command "execute as CursorBot at @s run summon minecraft:allay ~-4 ~ ~" +run_server_command "execute as CursorBot at @s run summon minecraft:armor_stand ~ ~ ~2" + +sleep 2 +run_mcc_command "entity" + +run_server_command "execute as CursorBot at @s run particle minecraft:happy_villager ~ ~1 ~ 0.5 0.5 0.5 0 12 force" +run_server_command "execute as CursorBot at @s run particle minecraft:end_rod ~ ~1 ~ 0.5 0.5 0.5 0.01 20 force" +run_server_command "execute as CursorBot at @s run particle minecraft:explosion ~ ~1 ~ 0 0 0 0 1 force" +run_server_command "execute as CursorBot at @s run particle minecraft:totem_of_undying ~ ~1 ~ 0.5 0.5 0.5 0.1 20 force" + +run_server_command "execute as CursorBot at @s run playsound minecraft:entity.lightning_bolt.thunder master CursorBot ~ ~ ~ 1 1 0" +run_server_command "execute as CursorBot at @s run playsound minecraft:block.note_block.bell master CursorBot ~ ~ ~ 1 1 0" + +run_server_command "execute as CursorBot at @s run summon minecraft:tnt ~3 ~ ~" +sleep 2 +run_server_command "execute as CursorBot at @s run summon minecraft:tnt ~6 ~ ~" + +sleep 6 +capture_server_logs + +assert_contains "$MCC_LOG" "Server was successfully joined." "MCC never joined the server" +assert_contains "$MCC_LOG" "[FileInput] > inventory player list" "Inventory command was not executed" +assert_contains "$MCC_LOG" "[FileInput] > entity" "Entity command was not executed" +assert_contains "$MCC_LOG" "[FileInput] > /gamemode creative" "Creative mode command was not executed from MCC" +assert_contains "$MCC_LOG" "Requested Diamond x16 in slot #36" "Creative inventory give did not succeed" +assert_contains "$MCC_LOG" "smoke_test_from_mcc_full_spectrum" "Client-originated chat was not observed" +assert_not_contains "$MCC_LOG" "Please enable InventoryHandling" "Inventory handling is still disabled" +assert_not_contains "$MCC_LOG" "Please enable EntityHandling" "Entity handling is still disabled" +assert_not_contains "$MCC_LOG" "You must be in Creative gamemode" "Creative mode was not active when creativegive ran" +assert_not_contains "$MCC_LOG" "Failed to load settings" "MCC failed to reload its config" + +assert_contains "$SERVER_FILE_LOG" "CursorBot joined the game" "Server never saw CursorBot join" +assert_contains "$SERVER_FILE_LOG" "smoke_test_from_mcc_full_spectrum" "Server never received the client chat message" +assert_contains "$SERVER_FILE_LOG" "Displaying particle minecraft:happy_villager" "Particle events were not recorded on the server" +assert_contains "$SERVER_FILE_LOG" "Played sound minecraft:block.note_block.bell to CursorBot" "Sound events were not recorded on the server" +assert_contains "$SERVER_FILE_LOG" "Summoned new Primed TNT" "TNT summon did not occur on the server" +assert_not_contains "$SERVER_FILE_LOG" "Sending unknown packet 'clientbound/minecraft:disconnect'" "Server hit the disconnect packet regression during the test" + +cat <&2 + exit 1 +fi + +MCC_LOG="$RUN_DIR/mcc.log" +SERVER_LOG="$RUN_DIR/server-latest.log" +BUILD_LOG="$RUN_DIR/build.log" + +echo "Run directory: $RUN_DIR" +echo +echo "Build result:" +grep -E "Warning\(s\)|Error\(s\)|Time Elapsed" "$BUILD_LOG" || true +echo +echo "MCC highlights:" +grep -E "Server was successfully joined|FileInput|smoke_test_from_mcc_full_spectrum|There are [0-9]+ of a max|health|Creative" "$MCC_LOG" || true +echo +echo "Server highlights:" +grep -E "joined the game|Made CursorBot a server operator|game mode|smoke_test_from_mcc_full_spectrum|summon|particle|playsound|tnt" "$SERVER_LOG" || true diff --git a/MinecraftClient/ChatBots/AntiAFK.cs b/MinecraftClient/ChatBots/AntiAFK.cs index 30d47189..055416a0 100644 --- a/MinecraftClient/ChatBots/AntiAFK.cs +++ b/MinecraftClient/ChatBots/AntiAFK.cs @@ -64,6 +64,12 @@ namespace MinecraftClient.ChatBots { public double min, max; + public Range() + { + min = 0; + max = 0; + } + public Range(int value) { min = max = value; @@ -180,4 +186,4 @@ namespace MinecraftClient.ChatBots currentLocation.Z + random.Next(range * -1, range)); } } -} \ No newline at end of file +} diff --git a/MinecraftClient/ChatBots/AutoCraft.cs b/MinecraftClient/ChatBots/AutoCraft.cs index 52d691ab..861bc2be 100644 --- a/MinecraftClient/ChatBots/AutoCraft.cs +++ b/MinecraftClient/ChatBots/AutoCraft.cs @@ -106,6 +106,13 @@ namespace MinecraftClient.ChatBots { public double X, Y, Z; + public LocationConfig() + { + X = 0; + Y = 0; + Z = 0; + } + public LocationConfig(double X, double Y, double Z) { this.X = X; diff --git a/MinecraftClient/ChatBots/AutoDig.cs b/MinecraftClient/ChatBots/AutoDig.cs index 17c27895..e1da7a51 100644 --- a/MinecraftClient/ChatBots/AutoDig.cs +++ b/MinecraftClient/ChatBots/AutoDig.cs @@ -85,6 +85,13 @@ namespace MinecraftClient.ChatBots { public double x, y, z; + public Coordination() + { + x = 0; + y = 0; + z = 0; + } + public Coordination(double x, double y, double z) { this.x = x; this.y = y; this.z = z; diff --git a/MinecraftClient/ChatBots/AutoFishing.cs b/MinecraftClient/ChatBots/AutoFishing.cs index 09ee140f..f2ab906d 100644 --- a/MinecraftClient/ChatBots/AutoFishing.cs +++ b/MinecraftClient/ChatBots/AutoFishing.cs @@ -103,6 +103,12 @@ namespace MinecraftClient.ChatBots public Coordination? XYZ; public Facing? facing; + public LocationConfig() + { + XYZ = null; + facing = null; + } + public LocationConfig(double yaw, double pitch) { this.XYZ = null; @@ -125,6 +131,13 @@ namespace MinecraftClient.ChatBots { public double x, y, z; + public Coordination() + { + x = 0; + y = 0; + z = 0; + } + public Coordination(double x, double y, double z) { this.x = x; this.y = y; this.z = z; @@ -135,6 +148,12 @@ namespace MinecraftClient.ChatBots { public double yaw, pitch; + public Facing() + { + yaw = 0; + pitch = 0; + } + public Facing(double yaw, double pitch) { this.yaw = yaw; this.pitch = pitch; diff --git a/MinecraftClient/ChatBots/AutoRelog.cs b/MinecraftClient/ChatBots/AutoRelog.cs index 24e6570a..06dd4cb1 100644 --- a/MinecraftClient/ChatBots/AutoRelog.cs +++ b/MinecraftClient/ChatBots/AutoRelog.cs @@ -57,6 +57,12 @@ namespace MinecraftClient.ChatBots { public double min, max; + public Range() + { + min = 0; + max = 0; + } + public Range(int value) { min = max = value; diff --git a/MinecraftClient/ChatBots/ScriptScheduler.cs b/MinecraftClient/ChatBots/ScriptScheduler.cs index 0e15949d..9db48c09 100644 --- a/MinecraftClient/ChatBots/ScriptScheduler.cs +++ b/MinecraftClient/ChatBots/ScriptScheduler.cs @@ -116,6 +116,12 @@ namespace MinecraftClient.ChatBots public bool Enable = false; public TimeSpan[] Times; + public TriggerOnTimeConfig() + { + Enable = false; + Times = Array.Empty(); + } + public TriggerOnTimeConfig(bool Enable, TimeSpan[] Time) { this.Enable = Enable; @@ -134,6 +140,13 @@ namespace MinecraftClient.ChatBots public bool Enable = false; public double MinTime, MaxTime; + public TriggerOnIntervalConfig() + { + Enable = false; + MinTime = 0; + MaxTime = 0; + } + public TriggerOnIntervalConfig(double value) { this.Enable = true; diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 684e5a98..8a7ca0c1 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -658,6 +658,12 @@ namespace MinecraftClient { public string Login = string.Empty, Password = string.Empty; + public AccountInfoConfig() + { + Login = string.Empty; + Password = string.Empty; + } + public AccountInfoConfig(string Login) { this.Login = Login; @@ -676,6 +682,12 @@ namespace MinecraftClient public string Host = string.Empty; public ushort? Port = null; + public ServerInfoConfig() + { + Host = string.Empty; + Port = null; + } + public ServerInfoConfig(string Host) { string[] sip = Host.Split(new[] { ":", ":" }, StringSplitOptions.None); @@ -699,6 +711,12 @@ namespace MinecraftClient public string Host = string.Empty; public int Port = 443; + public AuthlibServer() + { + Host = string.Empty; + Port = 443; + } + public AuthlibServer(string Host) { string[] sip = Host.Split(new[] { ":", ":" }, StringSplitOptions.None);