mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Fix legacy achievements and add test harness
This commit is contained in:
parent
962c8b1ab2
commit
0881cbaa1c
7 changed files with 785 additions and 3 deletions
|
|
@ -6,6 +6,14 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||||
# shellcheck source=tools/mcc-env.sh
|
# shellcheck source=tools/mcc-env.sh
|
||||||
source "$REPO_ROOT/tools/mcc-env.sh"
|
source "$REPO_ROOT/tools/mcc-env.sh"
|
||||||
|
|
||||||
|
sed_in_place() {
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
sed -i '' "$@"
|
||||||
|
else
|
||||||
|
sed -i "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
VERSION="${1:-1.21.11-Vanilla}"
|
VERSION="${1:-1.21.11-Vanilla}"
|
||||||
SERVER_DIR="${MCC_SERVERS:?}/$VERSION"
|
SERVER_DIR="${MCC_SERVERS:?}/$VERSION"
|
||||||
PROPS_FILE="$SERVER_DIR/server.properties"
|
PROPS_FILE="$SERVER_DIR/server.properties"
|
||||||
|
|
@ -49,6 +57,15 @@ wait_for_server_stop() {
|
||||||
sleep 1
|
sleep 1
|
||||||
((elapsed += 1))
|
((elapsed += 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Legacy servers can leave the tmux session around after stdin stop.
|
||||||
|
# Fall back to force-killing the session so the harness can continue.
|
||||||
|
mc-kill "$VERSION" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
if ! server_running; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Timed out waiting for $VERSION to stop" >&2
|
echo "Timed out waiting for $VERSION to stop" >&2
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +75,7 @@ upsert_property() {
|
||||||
local value="$2"
|
local value="$2"
|
||||||
|
|
||||||
if grep -Eq "^${key}=" "$PROPS_FILE"; then
|
if grep -Eq "^${key}=" "$PROPS_FILE"; then
|
||||||
sed -i "s#^${key}=.*#${key}=${value}#" "$PROPS_FILE"
|
sed_in_place "s#^${key}=.*#${key}=${value}#" "$PROPS_FILE"
|
||||||
else
|
else
|
||||||
printf '%s=%s\n' "$key" "$value" >> "$PROPS_FILE"
|
printf '%s=%s\n' "$key" "$value" >> "$PROPS_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
sed_in_place() {
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
sed -i '' "$@"
|
||||||
|
else
|
||||||
|
sed -i "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
if [[ $# -lt 3 || $# -gt 4 ]]; then
|
if [[ $# -lt 3 || $# -gt 4 ]]; then
|
||||||
echo "Usage: $0 <template-ini> <output-ini> <mc-version> [login]" >&2
|
echo "Usage: $0 <template-ini> <output-ini> <mc-version> [login]" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -28,7 +36,7 @@ fi
|
||||||
|
|
||||||
cp "$TEMPLATE_INI" "$OUTPUT_INI"
|
cp "$TEMPLATE_INI" "$OUTPUT_INI"
|
||||||
|
|
||||||
sed -i \
|
sed_in_place \
|
||||||
-e "s#^Account = .*#Account = { Login = \"$LOGIN_NAME\", Password = \"$PASSWORD_VALUE\" }#" \
|
-e "s#^Account = .*#Account = { Login = \"$LOGIN_NAME\", Password = \"$PASSWORD_VALUE\" }#" \
|
||||||
-e "s#^AccountType = .*#AccountType = \"$ACCOUNT_TYPE\"#" \
|
-e "s#^AccountType = .*#AccountType = \"$ACCOUNT_TYPE\"#" \
|
||||||
-e "s#^MinecraftVersion = \"[^\"]*\"\\(.*\\)\$#MinecraftVersion = \"$MC_VERSION\"\\1#" \
|
-e "s#^MinecraftVersion = \"[^\"]*\"\\(.*\\)\$#MinecraftVersion = \"$MC_VERSION\"\\1#" \
|
||||||
|
|
|
||||||
157
.skills/mcc-integration-testing/scripts/run_achievements_matrix.sh
Executable file
157
.skills/mcc-integration-testing/scripts/run_achievements_matrix.sh
Executable file
|
|
@ -0,0 +1,157 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||||
|
# shellcheck source=tools/mcc-env.sh
|
||||||
|
source "$REPO_ROOT/tools/mcc-env.sh"
|
||||||
|
|
||||||
|
RUN_ROOT="${TMPDIR:-/tmp}/mcc-achievements/matrix"
|
||||||
|
RUN_ID="$(date +%Y%m%d-%H%M%S)"
|
||||||
|
MATRIX_DIR="$RUN_ROOT/$RUN_ID"
|
||||||
|
RESULTS_TSV="$MATRIX_DIR/results.tsv"
|
||||||
|
BUILD_LOG="$MATRIX_DIR/build.log"
|
||||||
|
REPORT_MD="$MATRIX_DIR/report.md"
|
||||||
|
PRECHECK_TXT="$MATRIX_DIR/preflight.txt"
|
||||||
|
|
||||||
|
mkdir -p "$MATRIX_DIR"
|
||||||
|
|
||||||
|
write_row() {
|
||||||
|
local fields=("$@")
|
||||||
|
|
||||||
|
while (( ${#fields[@]} < 14 )); do
|
||||||
|
fields+=("")
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||||
|
"${fields[0]}" "${fields[1]}" "${fields[2]}" "${fields[3]}" "${fields[4]}" "${fields[5]}" "${fields[6]}" \
|
||||||
|
"${fields[7]}" "${fields[8]}" "${fields[9]}" "${fields[10]}" "${fields[11]}" "${fields[12]}" \
|
||||||
|
"${fields[13]}" >> "$RESULTS_TSV"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_server_dir() {
|
||||||
|
local version="$1"
|
||||||
|
local candidate
|
||||||
|
|
||||||
|
for candidate in "$version" "$version-Vanilla"; do
|
||||||
|
if [[ -d "$MCC_SERVERS/$candidate" ]]; then
|
||||||
|
printf '%s\n' "$candidate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run_version() {
|
||||||
|
local version="$1"
|
||||||
|
local profile="$2"
|
||||||
|
local family="$3"
|
||||||
|
local server_dir="$4"
|
||||||
|
local summary_env
|
||||||
|
|
||||||
|
if bash "$SCRIPT_DIR/run_achievements_test.sh" --no-build "$server_dir" "$version" "$profile"; then
|
||||||
|
:
|
||||||
|
fi
|
||||||
|
|
||||||
|
summary_env="${TMPDIR:-/tmp}/mcc-achievements/$server_dir/latest/summary.env"
|
||||||
|
if [[ ! -f "$summary_env" ]]; then
|
||||||
|
write_row "$version" "$server_dir" "unknown" "$family" "❌" "❌" "❌" "❌" "❌ Fail" \
|
||||||
|
"Summary file was not produced." "" "" ""
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$summary_env"
|
||||||
|
|
||||||
|
write_row "$VERSION" "$SERVER_DIR" "$PORT" "$FAMILY" "$INITIAL_STATUS" "$GRANT_STATUS" "$REVOKE_STATUS" \
|
||||||
|
"$API_STATUS" "$VERDICT" "$NOTE" "$RUN_DIR" "$MCC_LOG" "$COPIED_SERVER_LOG" "$COMMAND_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
printf 'MCC_SERVERS=%s\n' "$MCC_SERVERS"
|
||||||
|
printf 'RUN_DIR=%s\n' "$MATRIX_DIR"
|
||||||
|
printf 'DATE=%s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||||
|
} > "$PRECHECK_TXT"
|
||||||
|
|
||||||
|
printf 'Version\tServerDir\tPort\tFamily\tInitial\tGrant\tRevoke\tAPI\tVerdict\tNote\tRunDir\tMccLog\tServerLog\tCommandLog\n' > "$RESULTS_TSV"
|
||||||
|
|
||||||
|
JAVA_OK="yes"
|
||||||
|
TMUX_OK="yes"
|
||||||
|
DOTNET_OK="yes"
|
||||||
|
BUILD_OK="yes"
|
||||||
|
|
||||||
|
if ! command -v dotnet >/dev/null 2>&1; then
|
||||||
|
DOTNET_OK="no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v java >/dev/null 2>&1 || ! java -version >/dev/null 2>&1; then
|
||||||
|
JAVA_OK="no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v tmux >/dev/null 2>&1; then
|
||||||
|
TMUX_OK="no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DOTNET_OK" == "yes" ]]; then
|
||||||
|
if ! dotnet build "$REPO_ROOT/MinecraftClient.sln" -c Release > "$BUILD_LOG" 2>&1; then
|
||||||
|
BUILD_OK="no"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
: > "$BUILD_LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
printf 'MCC_SERVERS=%s\n' "$MCC_SERVERS"
|
||||||
|
printf 'RUN_DIR=%s\n' "$MATRIX_DIR"
|
||||||
|
printf 'DATE=%s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||||
|
printf 'dotnet=%s\n' "$DOTNET_OK"
|
||||||
|
printf 'java=%s\n' "$JAVA_OK"
|
||||||
|
printf 'tmux=%s\n' "$TMUX_OK"
|
||||||
|
printf 'build=%s\n' "$BUILD_OK"
|
||||||
|
} > "$PRECHECK_TXT"
|
||||||
|
|
||||||
|
while IFS='|' read -r version profile family; do
|
||||||
|
[[ -z "$version" ]] && continue
|
||||||
|
|
||||||
|
if [[ "$DOTNET_OK" != "yes" ]]; then
|
||||||
|
write_row "$version" "" "" "$family" "❌" "❌" "❌" "❌" "❌ Fail" \
|
||||||
|
"dotnet is not available on PATH."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$BUILD_OK" != "yes" ]]; then
|
||||||
|
write_row "$version" "" "" "$family" "❌" "❌" "❌" "❌" "❌ Fail" \
|
||||||
|
"dotnet build failed. See $BUILD_LOG."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$JAVA_OK" != "yes" || "$TMUX_OK" != "yes" ]]; then
|
||||||
|
write_row "$version" "" "" "$family" "❌" "❌" "❌" "❌" "❌ Fail" \
|
||||||
|
"java or tmux is not available, so live server execution was blocked."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! server_dir="$(resolve_server_dir "$version")"; then
|
||||||
|
write_row "$version" "" "" "$family" "❌" "❌" "❌" "❌" "⚠️ Partial" \
|
||||||
|
"Server directory for $version was not found under $MCC_SERVERS."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_version "$version" "$profile" "$family" "$server_dir"
|
||||||
|
done <<'EOF'
|
||||||
|
1.8|legacy|Legacy 🧱
|
||||||
|
1.11.2|legacy|Legacy 🧱
|
||||||
|
1.12.2|modern|First advancements 🌱
|
||||||
|
1.19.4|modern|Stable modern ✅
|
||||||
|
1.20|modern|Telemetry edge 1 ⚠️
|
||||||
|
1.20.2|modern|Telemetry edge 2 ⚠️
|
||||||
|
1.20.4|modern|End of 1.20.x ⚠️
|
||||||
|
1.20.6|modern|Post-1.20.6 🔧
|
||||||
|
1.21.2|modern|1.21.2 family 🔧
|
||||||
|
1.21.11|modern|showAdvancements 🆕
|
||||||
|
26.1|modern|Latest supported 🚀
|
||||||
|
EOF
|
||||||
|
|
||||||
|
bash "$SCRIPT_DIR/summarize_achievements_matrix.sh" "$MATRIX_DIR" > "$REPORT_MD"
|
||||||
|
printf '%s\n' "$MATRIX_DIR"
|
||||||
443
.skills/mcc-integration-testing/scripts/run_achievements_test.sh
Executable file
443
.skills/mcc-integration-testing/scripts/run_achievements_test.sh
Executable file
|
|
@ -0,0 +1,443 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||||
|
# shellcheck source=tools/mcc-env.sh
|
||||||
|
source "$REPO_ROOT/tools/mcc-env.sh"
|
||||||
|
|
||||||
|
sed_in_place() {
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
sed -i '' "$@"
|
||||||
|
else
|
||||||
|
sed -i "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: run_achievements_test.sh [--no-build] <server-dir> <mc-version> <legacy|modern>
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
.skills/mcc-integration-testing/scripts/run_achievements_test.sh --no-build 1.8 1.8 legacy
|
||||||
|
.skills/mcc-integration-testing/scripts/run_achievements_test.sh --no-build 1.21.11-Vanilla 1.21.11 modern
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
DO_BUILD=true
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--no-build) DO_BUILD=false; shift ;;
|
||||||
|
--build) DO_BUILD=true; shift ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $# -ne 3 ]]; then
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SERVER_DIR="$1"
|
||||||
|
MC_VERSION="$2"
|
||||||
|
PROFILE="$3"
|
||||||
|
|
||||||
|
if [[ "$PROFILE" != "legacy" && "$PROFILE" != "modern" ]]; then
|
||||||
|
echo "Unsupported profile: $PROFILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUN_ROOT="${TMPDIR:-/tmp}/mcc-achievements"
|
||||||
|
RUN_ID="$(date +%Y%m%d-%H%M%S)"
|
||||||
|
RUN_DIR="$RUN_ROOT/$SERVER_DIR/$RUN_ID"
|
||||||
|
LATEST_LINK="$RUN_ROOT/$SERVER_DIR/latest"
|
||||||
|
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"
|
||||||
|
COMMAND_LOG="$RUN_DIR/commands.log"
|
||||||
|
SUMMARY_ENV="$RUN_DIR/summary.env"
|
||||||
|
PROBE_SCRIPT="$RUN_DIR/achievement_probe.cs"
|
||||||
|
CFG="$RUN_DIR/MinecraftClient.$MC_VERSION.ini"
|
||||||
|
INPUT_FILE="$REPO_ROOT/mcc_input.txt"
|
||||||
|
SERVER_LOG_FILE="$MCC_SERVERS/$SERVER_DIR/logs/latest.log"
|
||||||
|
TARGET_ID="minecraft:story/root"
|
||||||
|
TARGET_COMMAND_GRANT="advancement grant CursorBot only minecraft:story/root"
|
||||||
|
TARGET_COMMAND_REVOKE="advancement revoke CursorBot only minecraft:story/root"
|
||||||
|
TARGET_TYPE="Modern 🌱"
|
||||||
|
PORT="unknown"
|
||||||
|
MCC_PID=""
|
||||||
|
|
||||||
|
INITIAL_STATUS="❌"
|
||||||
|
GRANT_STATUS="❌"
|
||||||
|
REVOKE_STATUS="❌"
|
||||||
|
API_STATUS="❌"
|
||||||
|
VERDICT="❌ Fail"
|
||||||
|
NOTE="Run did not complete."
|
||||||
|
EXECUTED="yes"
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "legacy" ]]; then
|
||||||
|
TARGET_ID="achievement.openInventory"
|
||||||
|
TARGET_COMMAND_GRANT="achievement give achievement.openInventory CursorBot"
|
||||||
|
TARGET_COMMAND_REVOKE="achievement take achievement.openInventory CursorBot"
|
||||||
|
TARGET_TYPE="Legacy 🧱"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$RUN_DIR"
|
||||||
|
|
||||||
|
write_summary() {
|
||||||
|
{
|
||||||
|
printf 'VERSION=%q\n' "$MC_VERSION"
|
||||||
|
printf 'SERVER_DIR=%q\n' "$SERVER_DIR"
|
||||||
|
printf 'PROFILE=%q\n' "$PROFILE"
|
||||||
|
printf 'FAMILY=%q\n' "$TARGET_TYPE"
|
||||||
|
printf 'PORT=%q\n' "$PORT"
|
||||||
|
printf 'RUN_DIR=%q\n' "$RUN_DIR"
|
||||||
|
printf 'MCC_LOG=%q\n' "$MCC_LOG"
|
||||||
|
printf 'SERVER_LOG=%q\n' "$RUN_DIR/server-latest.log"
|
||||||
|
printf 'SERVER_FILE_LOG=%q\n' "$SERVER_LOG_FILE"
|
||||||
|
printf 'SERVER_TMUX_LOG=%q\n' "$SERVER_TMUX_LOG"
|
||||||
|
printf 'COPIED_SERVER_LOG=%q\n' "$RUN_DIR/server-latest.log"
|
||||||
|
printf 'COMMAND_LOG=%q\n' "$COMMAND_LOG"
|
||||||
|
printf 'SUMMARY_ENV=%q\n' "$SUMMARY_ENV"
|
||||||
|
printf 'TARGET_ID=%q\n' "$TARGET_ID"
|
||||||
|
printf 'INITIAL_STATUS=%q\n' "$INITIAL_STATUS"
|
||||||
|
printf 'GRANT_STATUS=%q\n' "$GRANT_STATUS"
|
||||||
|
printf 'REVOKE_STATUS=%q\n' "$REVOKE_STATUS"
|
||||||
|
printf 'API_STATUS=%q\n' "$API_STATUS"
|
||||||
|
printf 'VERDICT=%q\n' "$VERDICT"
|
||||||
|
printf 'NOTE=%q\n' "$NOTE"
|
||||||
|
printf 'EXECUTED=%q\n' "$EXECUTED"
|
||||||
|
} > "$SUMMARY_ENV"
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_server_logs() {
|
||||||
|
mc-log "$SERVER_DIR" 400 > "$SERVER_TMUX_LOG" 2>/dev/null || true
|
||||||
|
if [[ -f "$SERVER_LOG_FILE" ]]; then
|
||||||
|
cp "$SERVER_LOG_FILE" "$RUN_DIR/server-latest.log" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
capture_server_logs
|
||||||
|
|
||||||
|
if [[ -n "${MCC_PID:-}" ]] && kill -0 "$MCC_PID" 2>/dev/null; then
|
||||||
|
echo "quit" >> "$INPUT_FILE" 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
kill "$MCC_PID" 2>/dev/null || true
|
||||||
|
wait "$MCC_PID" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
mc-stop "$SERVER_DIR" >/dev/null 2>&1 || true
|
||||||
|
ln -sfn "$RUN_DIR" "$LATEST_LINK"
|
||||||
|
write_summary
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$1" | tee -a "$COMMAND_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
NOTE="$1"
|
||||||
|
VERDICT="❌ Fail"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "$SERVER_DIR" 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
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_noisy_bots() {
|
||||||
|
sed_in_place '/^\[ChatBot.ScriptScheduler\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
|
||||||
|
sed_in_place '/^\[ChatBot.DiscordRpc\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
|
||||||
|
sed_in_place '/^\[ChatBot.AntiAFK\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
|
||||||
|
sed_in_place '/^\[ChatBot.AutoDig\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
|
||||||
|
sed_in_place '/^\[ChatBot.AutoAttack\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
|
||||||
|
sed_in_place '/^\[ChatBot.PlayerListLogger\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
|
||||||
|
sed_in_place '/^\[ChatBot.ReplayCapture\]/,/^\[/ { s/^Enabled = true/Enabled = false/; }' "$REPO_ROOT/MinecraftClient.ini"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_root_config() {
|
||||||
|
if [[ -f "$REPO_ROOT/MinecraftClient.ini" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
dotnet run --project MinecraftClient -c Release --no-build -- --help >/dev/null 2>&1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
write_probe_script() {
|
||||||
|
cat > "$PROBE_SCRIPT" <<EOF
|
||||||
|
//MCCScript 1.0
|
||||||
|
|
||||||
|
MCC.LoadBot(new AchievementProbeBot());
|
||||||
|
|
||||||
|
//MCCScript Extensions
|
||||||
|
|
||||||
|
public class AchievementProbeBot : ChatBot
|
||||||
|
{
|
||||||
|
private const string TargetId = "$TARGET_ID";
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
LogToConsole("[ACH_TEST] probe initialized");
|
||||||
|
DumpState("initialize");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AfterGameJoined()
|
||||||
|
{
|
||||||
|
LogToConsole("[ACH_TEST] after join");
|
||||||
|
DumpState("after_join");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnAchievementUpdate(IReadOnlyList<Achievement> updated, IReadOnlyList<string> removedIds, bool reset)
|
||||||
|
{
|
||||||
|
LogToConsole($"[ACH_TEST] event reset={reset} updated={updated.Count} removed={removedIds.Count}");
|
||||||
|
DumpState("event");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DumpState(string origin)
|
||||||
|
{
|
||||||
|
Achievement[] all = GetAchievements();
|
||||||
|
Achievement[] unlocked = GetUnlockedAchievements();
|
||||||
|
Achievement[] locked = GetLockedAchievements();
|
||||||
|
Achievement? target = null;
|
||||||
|
|
||||||
|
foreach (Achievement entry in all)
|
||||||
|
{
|
||||||
|
if (entry.Id == TargetId)
|
||||||
|
{
|
||||||
|
target = entry;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string titleState = "missing";
|
||||||
|
string completionState = "missing";
|
||||||
|
|
||||||
|
if (target is not null)
|
||||||
|
{
|
||||||
|
titleState = target.Title is null ? "null" : "present";
|
||||||
|
completionState = target.IsCompleted ? "done" : "todo";
|
||||||
|
}
|
||||||
|
|
||||||
|
LogToConsole($"[ACH_TEST] snapshot origin={origin} all={all.Length} unlocked={unlocked.Length} locked={locked.Length}");
|
||||||
|
LogToConsole($"[ACH_TEST] target_state origin={origin} id={TargetId} title={titleState} completed={completionState}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
run_server_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
local attempt
|
||||||
|
|
||||||
|
log_step "SERVER> $cmd"
|
||||||
|
for attempt in 1 2 3 4 5; do
|
||||||
|
if mc-rcon "$cmd" >/dev/null 2>&1; then
|
||||||
|
sleep 1
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
fail "Server command failed: $cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mcc_command() {
|
||||||
|
local name="$1"
|
||||||
|
local cmd="$2"
|
||||||
|
local delay="${3:-2}"
|
||||||
|
local start_line=0
|
||||||
|
local end_line=0
|
||||||
|
|
||||||
|
if [[ -f "$MCC_LOG" ]]; then
|
||||||
|
start_line="$(wc -l < "$MCC_LOG")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_step "MCC> $cmd"
|
||||||
|
echo "$cmd" >> "$INPUT_FILE"
|
||||||
|
sleep "$delay"
|
||||||
|
|
||||||
|
if [[ -f "$MCC_LOG" ]]; then
|
||||||
|
end_line="$(wc -l < "$MCC_LOG")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( end_line > start_line )); then
|
||||||
|
sed -n "$((start_line + 1)),$((end_line))p" "$MCC_LOG" > "$RUN_DIR/$name.mcc.log"
|
||||||
|
else
|
||||||
|
: > "$RUN_DIR/$name.mcc.log"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_pattern() {
|
||||||
|
local file="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
local description="$3"
|
||||||
|
|
||||||
|
grep -Fq "$pattern" "$file" || fail "$description"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! command -v java >/dev/null 2>&1 || ! java -version >/dev/null 2>&1; then
|
||||||
|
fail "java was not found on PATH."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v tmux >/dev/null 2>&1; then
|
||||||
|
fail "tmux was not found on PATH."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "$MCC_SERVERS/$SERVER_DIR" ]]; then
|
||||||
|
fail "Server directory not found: $MCC_SERVERS/$SERVER_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
PORT="$(bash "$SCRIPT_DIR/get_server_port.sh" "$SERVER_DIR")"
|
||||||
|
|
||||||
|
ensure_root_config
|
||||||
|
"$SCRIPT_DIR/ensure_offline_server.sh" "$SERVER_DIR"
|
||||||
|
disable_noisy_bots
|
||||||
|
write_probe_script
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "legacy" && -f "$MCC_SERVERS/$SERVER_DIR/server.properties" ]]; then
|
||||||
|
sed_in_place 's/^use-native-transport=.*/use-native-transport=false/' "$MCC_SERVERS/$SERVER_DIR/server.properties"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $DO_BUILD; then
|
||||||
|
log_step "BUILD> dotnet build MinecraftClient.sln -c Release"
|
||||||
|
mcc-build > "$BUILD_LOG" 2>&1 || fail "dotnet build failed."
|
||||||
|
else
|
||||||
|
: > "$BUILD_LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
: > "$INPUT_FILE"
|
||||||
|
rm -f "$MCC_LOG"
|
||||||
|
|
||||||
|
log_step "Starting server $SERVER_DIR on port $PORT"
|
||||||
|
mc-start "$SERVER_DIR" >/dev/null
|
||||||
|
wait_for_server_ready || fail "Server did not become ready."
|
||||||
|
|
||||||
|
log_step "Starting MCC for $MC_VERSION"
|
||||||
|
(
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
MCC_FILE_INPUT=1 dotnet run --project MinecraftClient -c Release --no-build -- \
|
||||||
|
CursorBot \
|
||||||
|
- \
|
||||||
|
"localhost:$PORT" \
|
||||||
|
"--accounttype=mojang" \
|
||||||
|
"--minecraftversion=$MC_VERSION" \
|
||||||
|
"--terrainandmovements=true" \
|
||||||
|
"--inventoryhandling=true" \
|
||||||
|
"--entityhandling=true" \
|
||||||
|
"--autorespawn=true" \
|
||||||
|
"--debugmessages=true" \
|
||||||
|
> "$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_file_pattern "$SERVER_LOG_FILE" "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"
|
||||||
|
if [[ "$PROFILE" == "modern" ]]; then
|
||||||
|
run_server_command "gamerule logAdminCommands true"
|
||||||
|
fi
|
||||||
|
run_server_command "time set day"
|
||||||
|
run_server_command "weather clear"
|
||||||
|
|
||||||
|
run_mcc_command "load_probe" "script $PROBE_SCRIPT" 3
|
||||||
|
wait_for_file_pattern "$MCC_LOG" "[ACH_TEST] probe initialized" "probe startup" 30 || fail "Probe script did not initialize."
|
||||||
|
|
||||||
|
run_mcc_command "baseline_debug" "debug state" 2
|
||||||
|
run_mcc_command "baseline_all" "achievement" 2
|
||||||
|
run_mcc_command "baseline_locked" "achievement locked" 2
|
||||||
|
run_mcc_command "baseline_unlocked" "achievement unlocked" 2
|
||||||
|
|
||||||
|
run_server_command "$TARGET_COMMAND_GRANT"
|
||||||
|
sleep 3
|
||||||
|
run_mcc_command "after_grant_all" "achievement" 2
|
||||||
|
run_mcc_command "after_grant_unlocked" "achievement unlocked" 2
|
||||||
|
|
||||||
|
run_server_command "$TARGET_COMMAND_REVOKE"
|
||||||
|
sleep 3
|
||||||
|
run_mcc_command "after_revoke_all" "achievement" 2
|
||||||
|
run_mcc_command "after_revoke_locked" "achievement locked" 2
|
||||||
|
|
||||||
|
assert_pattern "$MCC_LOG" "Achievements/Advancements:" "Achievement command header never appeared."
|
||||||
|
|
||||||
|
if ! grep -Fq "No achievements/advancements received yet." "$RUN_DIR/baseline_all.mcc.log"; then
|
||||||
|
INITIAL_STATUS="✅"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -Fq "$TARGET_ID" "$RUN_DIR/after_grant_unlocked.mcc.log" && grep -Fq "[DONE]" "$RUN_DIR/after_grant_unlocked.mcc.log"; then
|
||||||
|
GRANT_STATUS="✅"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$PROFILE" == "legacy" ]]; then
|
||||||
|
if grep -Fq "$TARGET_ID" "$RUN_DIR/after_revoke_locked.mcc.log" && grep -Fq "[TODO]" "$RUN_DIR/after_revoke_locked.mcc.log"; then
|
||||||
|
REVOKE_STATUS="✅"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if grep -Fq "$TARGET_ID" "$RUN_DIR/after_revoke_locked.mcc.log" && grep -Fq "[TODO]" "$RUN_DIR/after_revoke_locked.mcc.log"; then
|
||||||
|
REVOKE_STATUS="✅"
|
||||||
|
elif [[ "$GRANT_STATUS" == "✅" ]] && ! grep -Fq "$TARGET_ID" "$RUN_DIR/after_revoke_all.mcc.log"; then
|
||||||
|
REVOKE_STATUS="✅"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -Fq "[ACH_TEST] event" "$MCC_LOG" && grep -Fq "target_state origin=event id=$TARGET_ID title=" "$MCC_LOG"; then
|
||||||
|
API_STATUS="✅"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$INITIAL_STATUS|$GRANT_STATUS|$REVOKE_STATUS|$API_STATUS" in
|
||||||
|
"✅|✅|✅|✅")
|
||||||
|
VERDICT="✅ Pass"
|
||||||
|
NOTE="All planned achievement checks passed."
|
||||||
|
;;
|
||||||
|
*"✅"*)
|
||||||
|
VERDICT="⚠️ Partial"
|
||||||
|
NOTE="At least one achievement phase passed, but the matrix did not fully clear."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
VERDICT="❌ Fail"
|
||||||
|
NOTE="Achievement checks did not produce the expected evidence."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
run_mcc_command "quit" "quit" 2
|
||||||
|
NOTE="$NOTE Artifacts saved in $RUN_DIR."
|
||||||
57
.skills/mcc-integration-testing/scripts/summarize_achievements_matrix.sh
Executable file
57
.skills/mcc-integration-testing/scripts/summarize_achievements_matrix.sh
Executable file
|
|
@ -0,0 +1,57 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
echo "Usage: summarize_achievements_matrix.sh <matrix-run-dir>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MATRIX_DIR="$1"
|
||||||
|
RESULTS_TSV="$MATRIX_DIR/results.tsv"
|
||||||
|
PRECHECK_TXT="$MATRIX_DIR/preflight.txt"
|
||||||
|
BUILD_LOG="$MATRIX_DIR/build.log"
|
||||||
|
|
||||||
|
if [[ ! -f "$RESULTS_TSV" ]]; then
|
||||||
|
echo "Missing results file: $RESULTS_TSV" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "# Achievements Matrix Report"
|
||||||
|
echo
|
||||||
|
echo "## Executed"
|
||||||
|
echo
|
||||||
|
if [[ -f "$PRECHECK_TXT" ]]; then
|
||||||
|
echo '```text'
|
||||||
|
cat "$PRECHECK_TXT"
|
||||||
|
echo '```'
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "- Matrix artifacts: \`$MATRIX_DIR\`"
|
||||||
|
echo "- Results TSV: \`$RESULTS_TSV\`"
|
||||||
|
echo "- Build log: \`$BUILD_LOG\`"
|
||||||
|
echo "- Execution mode: sequential"
|
||||||
|
echo "- Auth mode: offline"
|
||||||
|
echo
|
||||||
|
echo "## Observed"
|
||||||
|
echo
|
||||||
|
echo "| Version | Port | Family | Initial snapshot | Grant | Revoke | API callback | Verdict |"
|
||||||
|
echo "|---|---:|---|---|---|---|---|---|"
|
||||||
|
awk -F '\t' 'NR > 1 {
|
||||||
|
printf("| `%s` | `%s` | %s | %s | %s | %s | %s | %s |\n",
|
||||||
|
$1, $3, $4, $5, $6, $7, $8, $9);
|
||||||
|
}' "$RESULTS_TSV"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "## Artifact Links"
|
||||||
|
echo
|
||||||
|
awk -F '\t' 'NR > 1 {
|
||||||
|
printf("- `%s`: run=`%s`, mcc=`%s`, server=`%s`, commands=`%s`\n", $1, $11, $12, $13, $14);
|
||||||
|
printf(" note: %s\n", $10);
|
||||||
|
}' "$RESULTS_TSV"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "## Inferred"
|
||||||
|
echo
|
||||||
|
echo "- Only rows with real MCC and server-log artifacts count as executed proof."
|
||||||
|
echo "- Rows blocked by missing Java, tmux, or server directories are environment-limited, not product pass results."
|
||||||
|
echo "- Legacy rows remain the highest-risk bucket because static inspection suggests pre-1.12 \`Statistics\` packets may not currently reach the achievements handler."
|
||||||
53
MinecraftClient/LegacyAchievementCatalog.cs
Normal file
53
MinecraftClient/LegacyAchievementCatalog.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MinecraftClient
|
||||||
|
{
|
||||||
|
internal static class LegacyAchievementCatalog
|
||||||
|
{
|
||||||
|
public static IReadOnlyList<string> Ids { get; } =
|
||||||
|
[
|
||||||
|
"achievement.openInventory",
|
||||||
|
"achievement.mineWood",
|
||||||
|
"achievement.buildWorkBench",
|
||||||
|
"achievement.buildPickaxe",
|
||||||
|
"achievement.buildFurnace",
|
||||||
|
"achievement.acquireIron",
|
||||||
|
"achievement.buildHoe",
|
||||||
|
"achievement.makeBread",
|
||||||
|
"achievement.bakeCake",
|
||||||
|
"achievement.buildBetterPickaxe",
|
||||||
|
"achievement.cookFish",
|
||||||
|
"achievement.onARail",
|
||||||
|
"achievement.buildSword",
|
||||||
|
"achievement.killEnemy",
|
||||||
|
"achievement.killCow",
|
||||||
|
"achievement.flyPig",
|
||||||
|
"achievement.snipeSkeleton",
|
||||||
|
"achievement.diamonds",
|
||||||
|
"achievement.diamondsToYou",
|
||||||
|
"achievement.portal",
|
||||||
|
"achievement.ghast",
|
||||||
|
"achievement.blazeRod",
|
||||||
|
"achievement.potion",
|
||||||
|
"achievement.theEnd",
|
||||||
|
"achievement.theEnd2",
|
||||||
|
"achievement.enchantments",
|
||||||
|
"achievement.overkill",
|
||||||
|
"achievement.bookcase",
|
||||||
|
"achievement.breedCow",
|
||||||
|
"achievement.spawnWither",
|
||||||
|
"achievement.killWither",
|
||||||
|
"achievement.fullBeacon",
|
||||||
|
"achievement.exploreAllBiomes",
|
||||||
|
"achievement.overpowered"
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly HashSet<string> s_idSet = new(Ids, StringComparer.Ordinal);
|
||||||
|
|
||||||
|
public static bool Contains(string id)
|
||||||
|
{
|
||||||
|
return s_idSet.Contains(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -91,6 +91,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
private int currentDimension;
|
private int currentDimension;
|
||||||
private bool isOnlineMode = false;
|
private bool isOnlineMode = false;
|
||||||
private readonly BlockingCollection<Tuple<int, Queue<byte>>> packetQueue = new();
|
private readonly BlockingCollection<Tuple<int, Queue<byte>>> packetQueue = new();
|
||||||
|
private readonly Dictionary<string, bool> legacyAchievementProgress = new(StringComparer.Ordinal);
|
||||||
private float LastYaw, LastPitch;
|
private float LastYaw, LastPitch;
|
||||||
private double lastSentX, lastSentY, lastSentZ;
|
private double lastSentX, lastSentY, lastSentZ;
|
||||||
private float lastSentYaw, lastSentPitch;
|
private float lastSentYaw, lastSentPitch;
|
||||||
|
|
@ -120,6 +121,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
Tuple<Thread, CancellationTokenSource>? netReader = null; // reader thread
|
Tuple<Thread, CancellationTokenSource>? netReader = null; // reader thread
|
||||||
readonly ILogger log;
|
readonly ILogger log;
|
||||||
readonly RandomNumberGenerator randomGen;
|
readonly RandomNumberGenerator randomGen;
|
||||||
|
private bool legacyAchievementsInitialized;
|
||||||
|
|
||||||
public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler,
|
public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler,
|
||||||
ForgeInfo? forgeInfo, int rawProtocolVersion = 0)
|
ForgeInfo? forgeInfo, int rawProtocolVersion = 0)
|
||||||
|
|
@ -3132,6 +3134,11 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
case PacketTypesIn.RecipeBookSettings:
|
case PacketTypesIn.RecipeBookSettings:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PacketTypesIn.Statistics:
|
||||||
|
if (protocolVersion < MC_1_12_Version)
|
||||||
|
HandleLegacyStatistics(packetData);
|
||||||
|
break;
|
||||||
|
|
||||||
case PacketTypesIn.Advancements:
|
case PacketTypesIn.Advancements:
|
||||||
HandleAdvancements(packetData);
|
HandleAdvancements(packetData);
|
||||||
break;
|
break;
|
||||||
|
|
@ -3147,9 +3154,39 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
return true; //Packet processed
|
return true; //Packet processed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle the Statistics packet for pre-1.12 legacy achievements.
|
||||||
|
/// </summary>
|
||||||
|
private void HandleLegacyStatistics(Queue<byte> packetData)
|
||||||
|
{
|
||||||
|
int statCount = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
|
||||||
|
for (int i = 0; i < statCount; i++)
|
||||||
|
{
|
||||||
|
string statId = dataTypes.ReadNextString(packetData);
|
||||||
|
int value = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
|
||||||
|
if (statId.StartsWith("achievement.", StringComparison.Ordinal))
|
||||||
|
legacyAchievementProgress[statId] = value > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Achievement> added = new(LegacyAchievementCatalog.Ids.Count + legacyAchievementProgress.Count);
|
||||||
|
|
||||||
|
foreach (string achievementId in LegacyAchievementCatalog.Ids)
|
||||||
|
added.Add(CreateLegacyAchievement(achievementId, legacyAchievementProgress.TryGetValue(achievementId, out bool completed) && completed));
|
||||||
|
|
||||||
|
foreach (var (achievementId, completed) in legacyAchievementProgress)
|
||||||
|
{
|
||||||
|
if (!LegacyAchievementCatalog.Contains(achievementId))
|
||||||
|
added.Add(CreateLegacyAchievement(achievementId, completed));
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.OnAchievementsUpdate(added, [], reset: !legacyAchievementsInitialized);
|
||||||
|
legacyAchievementsInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handle the Advancements packet (1.12+).
|
/// Handle the Advancements packet (1.12+).
|
||||||
/// Also handles the Statistics packet for pre-1.12 legacy achievements.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void HandleAdvancements(Queue<byte> packetData)
|
private void HandleAdvancements(Queue<byte> packetData)
|
||||||
{
|
{
|
||||||
|
|
@ -3282,6 +3319,16 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
handler.OnAchievementsUpdate(added, removedIds, reset);
|
handler.OnAchievementsUpdate(added, removedIds, reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Achievement CreateLegacyAchievement(string id, bool isCompleted)
|
||||||
|
{
|
||||||
|
Dictionary<string, bool> criteria = new(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
[id] = isCompleted
|
||||||
|
};
|
||||||
|
IReadOnlyList<string>[] requirements = [[id]];
|
||||||
|
return new Achievement(id, null, null, AchievementType.Legacy, false, isCompleted, requirements, criteria);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compute whether an advancement is completed based on AND-of-ORs requirements.
|
/// Compute whether an advancement is completed based on AND-of-ORs requirements.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue