diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index ca60bfdc4..331f3177b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -10,9 +10,9 @@ body: description: | What version of Minecraft are you using? If your version is not listed, please try to reproduce on one of the supported versions. options: - - 1.20.1 - - 1.21.1 - - 1.21.7 + - "1.20.1" + - "1.21.1" + - "26.1" validations: required: true - type: input diff --git a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts index 576b8a877..36282429a 100644 --- a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts +++ b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts @@ -55,7 +55,6 @@ repositories { includeGroup("me.shedaniel.cloth") includeGroup("me.shedaniel") includeGroup("mezz.jei") - includeGroup("org.teavm") includeModule("com.terraformersmc", "modmenu") } } diff --git a/doc/reference/feature_compat.md b/doc/reference/feature_compat.md index 1fc50c355..405d25cee 100644 --- a/doc/reference/feature_compat.md +++ b/doc/reference/feature_compat.md @@ -91,6 +91,12 @@ compatibility for these newer versions. | Remove `*` from `file:read` modes | ✔ | | | Metamethods respected in `table.*`, `ipairs` | ✔ | | +## Lua 5.5 +| Feature | Supported? | Notes | +|------------------------------------|------------|-------| +| `table.create` | ✔ | | +| `utf8.offset` returns end position | ✔ | | + ## Lua 5.0 | Feature | Supported? | Notes | |----------------------------------|------------|--------------------------------------------------| diff --git a/gradle.properties b/gradle.properties index 0512ef0e1..31fa1f3a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ neogradle.subsystems.conventions.runs.enabled=false # Mod properties isUnstable=true -modVersion=1.118.1 +modVersion=1.119.0 # Minecraft properties: We want to configure this here so we can read it in settings.gradle mcVersion=26.1.2 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d9e886b70..d7c87f326 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,14 +22,14 @@ slf4j = "2.0.17" # Core dependencies (independent of Minecraft) asm = "9.9.1" autoService = "1.1.1" -checkerFramework = "3.51.1" -cobalt = { strictly = "0.9.7" } -commonsCli = "1.10.0" -jetbrainsAnnotations = "26.0.2-1" +checkerFramework = "4.0.0" +cobalt = { strictly = "0.9.9" } +commonsCli = "1.11.0" +jetbrainsAnnotations = "26.1.0" jspecify = "1.0.0" -kotlin = "2.3.20" +kotlin = "2.3.21" kotlin-coroutines = "1.10.2" -nightConfig = "3.8.3" +nightConfig = "3.8.4" # Minecraft mods fabricPermissions = "0.3.3" @@ -47,29 +47,29 @@ create-fabric = "6.0.7.0+mc1.20.1-build.1716" # Testing hamcrest = "3.0" jqwik = "1.9.3" -junit = "6.0.1" -junitPlatform = "6.0.1" +junit = "6.0.3" +junitPlatform = "6.0.3" jmh = "1.37" # Build tools cctJavadoc = "1.9.0" -checkstyle = "12.1.1" -errorProne-core = "2.45.0" +checkstyle = "13.4.1" +errorProne-core = "2.49.0" errorProne-plugin = "4.3.0" fabric-loom = "1.16.1" githubRelease = "2.5.2" -gradleVersions = "0.53.0" +gradleVersions = "0.54.0" ideaExt = "1.3" illuaminate = "0.1.0-83-g1131f68" -lwjgl = "3.3.6" +lwjgl = "3.4.1" minotaur = "2.8.7" modDevGradle = "2.0.141" -nullAway = "0.12.14" -shadow = "9.2.2" -spotless = "8.0.0" -teavm = "0.14.0-SQUID.1" +nullAway = "0.13.4" +shadow = "9.4.1" +spotless = "8.4.0" +teavm = "0.14.0" vanillaExtract = "0.3.1" -versionCatalogUpdate = "1.0.1" +versionCatalogUpdate = "1.1.0" [libraries] # Normal dependencies diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 61285a659..b1b8ef56b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c61a118f7..b52fb7e71 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index adff685a0..b9bb139f7 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/gradlew.bat b/gradlew.bat index c4bdd3ab8..24c62d56f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -23,8 +23,8 @@ @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,7 +65,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line @@ -73,21 +73,10 @@ goto fail @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/package-lock.json b/package-lock.json index 64499e04d..08cfea40c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -971,9 +971,9 @@ } }, "node_modules/@swc/core": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.30.tgz", - "integrity": "sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.32.tgz", + "integrity": "sha512-/eWL0n43D64QWEUHLtTE+jDqjkJhyidjkDhv6f0uJohOUAhywxQ9wXYp845DNNds0JpCdI4Uo0a9bl+vbXf+ew==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -989,18 +989,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.30", - "@swc/core-darwin-x64": "1.15.30", - "@swc/core-linux-arm-gnueabihf": "1.15.30", - "@swc/core-linux-arm64-gnu": "1.15.30", - "@swc/core-linux-arm64-musl": "1.15.30", - "@swc/core-linux-ppc64-gnu": "1.15.30", - "@swc/core-linux-s390x-gnu": "1.15.30", - "@swc/core-linux-x64-gnu": "1.15.30", - "@swc/core-linux-x64-musl": "1.15.30", - "@swc/core-win32-arm64-msvc": "1.15.30", - "@swc/core-win32-ia32-msvc": "1.15.30", - "@swc/core-win32-x64-msvc": "1.15.30" + "@swc/core-darwin-arm64": "1.15.32", + "@swc/core-darwin-x64": "1.15.32", + "@swc/core-linux-arm-gnueabihf": "1.15.32", + "@swc/core-linux-arm64-gnu": "1.15.32", + "@swc/core-linux-arm64-musl": "1.15.32", + "@swc/core-linux-ppc64-gnu": "1.15.32", + "@swc/core-linux-s390x-gnu": "1.15.32", + "@swc/core-linux-x64-gnu": "1.15.32", + "@swc/core-linux-x64-musl": "1.15.32", + "@swc/core-win32-arm64-msvc": "1.15.32", + "@swc/core-win32-ia32-msvc": "1.15.32", + "@swc/core-win32-x64-msvc": "1.15.32" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -1012,9 +1012,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz", - "integrity": "sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.32.tgz", + "integrity": "sha512-/YWMvJDPu+AAwuUsM2G+DNQ/7zhodURGzdQyewEqcvgklAdDHs3LwQmLLnyn6SJl8DT8UOxkbzK+D1PmPeelRg==", "cpu": [ "arm64" ], @@ -1029,9 +1029,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz", - "integrity": "sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.32.tgz", + "integrity": "sha512-KOTXJXdAhWL+hZ77MYP3z+4pcMFaQhQ74yqyN1uz093q0YnbxpqMtYpPISbYvMHzVRNNx5kN+9RZAXEaadhWVA==", "cpu": [ "x64" ], @@ -1046,9 +1046,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz", - "integrity": "sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.32.tgz", + "integrity": "sha512-oOoxLweljlc0A4X8ybsgxV7cVaYTwBOg2iMDJcFR3Sr48C+lsv9VzSmqdK/IVIXF4W4GjLc3VqTAdSMXlfVLuQ==", "cpu": [ "arm" ], @@ -1063,9 +1063,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz", - "integrity": "sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.32.tgz", + "integrity": "sha512-oDzEkdl6D6BAWdMtU5KGO7y3HR5fJcvByNLyEk9+ugj8nP5Ovb7P4kBcStBXc4MPExFGQryehiINMlmY8HlclA==", "cpu": [ "arm64" ], @@ -1083,9 +1083,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz", - "integrity": "sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.32.tgz", + "integrity": "sha512-omcqjoZP/b8D8PuczVoRwJieC6ibj7qIxTftNYokz4/aSmKFHvsd7nIFfPk5ZvtzncbH4AY7+Dkr/Lp2gWxYeA==", "cpu": [ "arm64" ], @@ -1103,9 +1103,9 @@ } }, "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz", - "integrity": "sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.32.tgz", + "integrity": "sha512-KGkTMyz/Tbn3PBNu0AVZ4GTDFKnICrYcTiNPZq8DrvK42pnFsf3GNDrIG9E5AtQlTmC0YigkWKmu0eMcfTrmgA==", "cpu": [ "ppc64" ], @@ -1123,9 +1123,9 @@ } }, "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz", - "integrity": "sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.32.tgz", + "integrity": "sha512-G3Aa4tVS/3OGZBkoNIwUF9F6RAy+Osb4GOlo62SinLmDiErz/ykmM7KH0wkz6l9kM8jJq1HyAM6atJTUEbBk7g==", "cpu": [ "s390x" ], @@ -1143,9 +1143,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz", - "integrity": "sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.32.tgz", + "integrity": "sha512-ERsjfGcj6CBmj3vJnGDO8m8rTvw6RqMcWo1dogOtNx3/+/0+NNpJiXDobJrr1GwInI/BHAEkvSFIH6d2LqPcUQ==", "cpu": [ "x64" ], @@ -1163,9 +1163,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz", - "integrity": "sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.32.tgz", + "integrity": "sha512-N4Ggahe/8SUbTX50P6EdhbW9YWcgbZVb52R4cq6MK+zsoMjRq7rGvV5ztA05QnbaCYqMYx8rTY7KAIA3Crdo4Q==", "cpu": [ "x64" ], @@ -1183,9 +1183,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz", - "integrity": "sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.32.tgz", + "integrity": "sha512-01yN0o9jvo8xBTP12aPK2wW8b41jmOlGbDDlAnoynotc4pO6xA0zby9f1z6j++qXDpGBttLySq1omgVrlQKYcw==", "cpu": [ "arm64" ], @@ -1200,9 +1200,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz", - "integrity": "sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.32.tgz", + "integrity": "sha512-fLagI9XZYNpTcmlqAcp3KBtmj7E19WCmYD80Jxj1Kn5tGNa7yxNLd3NNdWxuZGUPl5iC0/KqZru7g08gF6Fsrw==", "cpu": [ "ia32" ], @@ -1217,9 +1217,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.30", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz", - "integrity": "sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==", + "version": "1.15.32", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.32.tgz", + "integrity": "sha512-gbc2bQ/T2CiR+w0OvcVKwLOFAcPZBvmWmolbwpg1E8UrpeC03DGtyMUApOHNXNYWA3SHFrYXCQtosrcMza1YFg==", "cpu": [ "x64" ], diff --git a/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java b/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java index 1336b3832..61ec717e6 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -165,6 +165,18 @@ public class CommandAPI implements ILuaAPI { return Collections.unmodifiableList(result); } + /** + * Get the name of the dimension the current command computer is in, such as {@code minecraft:overworld}. + * + * @return The dimension the computer is in. + * @see #getBlockPosition() + * @since 1.119.0 + */ + @LuaFunction + public final String getDimension() { + return computer.getLevel().dimension().identifier().toString(); + } + /** * Get the position of the current command computer. * @@ -173,10 +185,10 @@ public class CommandAPI implements ILuaAPI { * @cc.treturn number This computer's y position. * @cc.treturn number This computer's z position. * @cc.see gps.locate To get the position of a non-command computer. + * @see #getDimension() */ @LuaFunction public final Object[] getBlockPosition() { - // This is probably safe to do on the Lua thread. Probably. var pos = computer.getPosition(); return new Object[]{ pos.getX(), pos.getY(), pos.getZ() }; } diff --git a/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java b/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java index 77bf748c6..44828f0c6 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java @@ -43,6 +43,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; @@ -277,19 +278,17 @@ public interface PlatformHelper { /** * Interact with an entity, for instance feeding cows. *

- * Implementations should follow Minecraft behaviour - we try {@link Entity#interactAt(Player, Vec3, InteractionHand)} - * and then {@link Player#interactOn(Entity, InteractionHand)}. Loader-specific hooks should also be called. + * Implementations should call {@link Player#interactOn(Entity, InteractionHand, Vec3)} and any loader-specific + * hooks. * * @param player The player which is interacting with the entity. - * @param entity The entity we're interacting with. - * @param hitPos The position our ray trace hit the entity. This is a position in-world, unlike - * {@link Entity#interactAt(Player, Vec3, InteractionHand)} which is relative to the entity. + * @param hit The entity and position we're interacting with. Note the hit's position is world-relative, + * unlike {@link Entity#interact(Player, InteractionHand, Vec3)} which is relative to the entity. * @return Whether any interaction occurred. - * @see Entity#interactAt(Player, Vec3, InteractionHand) - * @see Player#interactOn(Entity, InteractionHand) + * @see Player#interactOn(Entity, InteractionHand, Vec3) * @see ServerGamePacketListenerImpl#handleInteract */ - boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos); + boolean interactWithEntity(ServerPlayer player, EntityHitResult hit); /** * The result of attempting to use an item on a block. diff --git a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java index 712892a7f..e859c8799 100644 --- a/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java +++ b/projects/common/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java @@ -102,11 +102,8 @@ public class TurtlePlaceCommand implements TurtleCommand { if (!(hit instanceof EntityHitResult entityHit)) return false; // Start claiming entity drops - var hitEntity = entityHit.getEntity(); - var hitPos = entityHit.getLocation(); - - DropConsumer.set(hitEntity); - var placed = PlatformHelper.get().interactWithEntity(turtlePlayer.player(), hitEntity, hitPos); + DropConsumer.set(entityHit.getEntity()); + var placed = PlatformHelper.get().interactWithEntity(turtlePlayer.player(), entityHit); TurtleUtil.stopConsumingPlayer(turtle, turtlePlayer); return placed; } diff --git a/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java b/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java index c5b00ebe9..f38f37a00 100644 --- a/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java +++ b/projects/common/src/test/java/dan200/computercraft/TestPlatformHelper.java @@ -43,7 +43,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.EntityHitResult; import org.jspecify.annotations.Nullable; import java.nio.file.Path; @@ -143,7 +143,7 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat } @Override - public boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos) { + public boolean interactWithEntity(ServerPlayer player, EntityHitResult hit) { throw new UnsupportedOperationException("Cannot interact with the world inside tests"); } diff --git a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt index af6ea57cc..993626f1a 100644 --- a/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt +++ b/projects/common/src/testMod/kotlin/dan200/computercraft/gametest/core/ManagedComputers.kt @@ -69,7 +69,9 @@ object ManagedComputers : ILuaMachine.Factory { val label = os.computerLabel return when { id != 1 -> CobaltLuaMachine(environment, bios) + label != null && label[0] != null -> KotlinMachine(environment, label[0] as String) + else -> { LOGGER.error("Kotlin Lua machine must have a label") CobaltLuaMachine(environment, bios) diff --git a/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaTable.java b/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaTable.java index fa42b1395..ecc091565 100644 --- a/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaTable.java +++ b/projects/core-api/src/main/java/dan200/computercraft/api/lua/LuaTable.java @@ -21,6 +21,19 @@ import static dan200.computercraft.api.lua.LuaValues.*; * @see ObjectArguments */ public interface LuaTable extends Map { + /** + * Return the value to which a specific integer key is mapped, or {@code null} if there is no mapping for the key. + *

+ * This should be used when accessing integer keys, as numeric keys within the table are typically normalised to + * doubles. + * + * @param index The key to look up. + * @return The corresponding value, or {@code null} if not present. + */ + default @Nullable Object get(int index) { + return get((double) index); + } + /** * Compute the length of the array part of this table. * @@ -42,7 +55,7 @@ public interface LuaTable extends Map { * @since 1.116 */ default double getDouble(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value)); return number.doubleValue(); } @@ -70,7 +83,7 @@ public interface LuaTable extends Map { * @throws LuaException If the value is not an integer. */ default long getLong(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value)); checkFiniteIndex(index, number.doubleValue()); return number.longValue(); @@ -145,7 +158,7 @@ public interface LuaTable extends Map { * @since 1.116 */ default boolean getBoolean(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (!(value instanceof Boolean bool)) throw badTableItem(index, "boolean", getType(value)); return bool; } @@ -173,7 +186,7 @@ public interface LuaTable extends Map { * @since 1.116 */ default String getString(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (!(value instanceof String string)) throw badTableItem(index, "string", getType(value)); return string; } @@ -204,7 +217,7 @@ public interface LuaTable extends Map { * @since 1.116 */ default Map getTable(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (!(value instanceof Map table)) throw badTableItem(index, "table", getType(value)); return table; } @@ -236,7 +249,7 @@ public interface LuaTable extends Map { * @since 1.116 */ default Optional optDouble(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (value == null) return Optional.empty(); if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value)); return Optional.of(number.doubleValue()); @@ -267,7 +280,7 @@ public interface LuaTable extends Map { * @since 1.116 */ default Optional optLong(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (value == null) return Optional.empty(); if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value)); checkFiniteIndex(index, number.doubleValue()); @@ -351,7 +364,7 @@ public interface LuaTable extends Map { * @since 1.116 */ default Optional optBoolean(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (value == null) return Optional.empty(); if (!(value instanceof Boolean bool)) throw badTableItem(index, "boolean", getType(value)); return Optional.of(bool); @@ -381,7 +394,7 @@ public interface LuaTable extends Map { * @since 1.116 */ default Optional optString(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (value == null) return Optional.empty(); if (!(value instanceof String string)) throw badTableItem(index, "string", getType(value)); return Optional.of(string); @@ -414,7 +427,7 @@ public interface LuaTable extends Map { * @since 1.116 */ default Optional> optTable(int index) throws LuaException { - Object value = get((double) index); + var value = get(index); if (value == null) return Optional.empty(); if (!(value instanceof Map table)) throw badTableItem(index, "table", getType(value)); return Optional.of(table); diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/LuaDateTime.java b/projects/core/src/main/java/dan200/computercraft/core/apis/LuaDateTime.java index d53f60795..d860f2eda 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/LuaDateTime.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/LuaDateTime.java @@ -77,8 +77,8 @@ final class LuaDateTime { var month = getField(table, "month", -1); var day = getField(table, "day", -1); var hour = getField(table, "hour", 12); - var minute = getField(table, "min", 12); - var second = getField(table, "sec", 12); + var minute = getField(table, "min", 0); + var second = getField(table, "sec", 0); var time = LocalDateTime.of(year, month, day, hour, minute, second); var isDst = getBoolField(table, "isdst"); diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java index 3d9fb3d30..558a07820 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java @@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; +import java.net.Inet6Address; import java.net.InetSocketAddress; import java.net.URI; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -124,6 +125,10 @@ public final class NetworkUtils { if (port < 0) port = ssl ? 443 : 80; var socketAddress = new InetSocketAddress(host, port); if (socketAddress.isUnresolved()) throw new HTTPRequestException("Unknown host"); + if (socketAddress.getAddress() instanceof Inet6Address inet6 && (inet6.getScopedInterface() != null || inet6.getScopeId() != 0)) { + throw new HTTPRequestException("Scoped address not permitted"); + } + return socketAddress; } diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java index 46bce96be..80de5e392 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/http/options/AddressPredicate.java @@ -72,6 +72,10 @@ interface AddressPredicate { )); } + return parse(address, prefixSize); + } + + public static HostRange parse(InetAddress address, int prefixSize) { // Mask the bytes of the IP address. byte[] minBytes = address.getAddress(), maxBytes = address.getAddress(); var size = prefixSize; @@ -126,6 +130,7 @@ interface AddressPredicate { || socketAddress.isMulticastAddress() // 224.0.0.0/4, ff00::/8 || isUniqueLocalAddress(socketAddress) // fd00::/8 || isCarrierGradeNatAddress(socketAddress) // 100.64.0.0/10 + || NAT64_RANGE.matches(socketAddress) // 64:ff9b::/96 || additionalAddresses.contains(socketAddress); } @@ -154,6 +159,12 @@ interface AddressPredicate { var bytes = address.getAddress(); return bytes[0] == 100 && ((bytes[1] & 0xFF) >= 64 && (bytes[1] & 0xFF) <= 127); } - } + /** + * The NAT64 address range (64:ff9b::/96). + * + * @see NAT64 on Wikipedia + */ + private static final HostRange NAT64_RANGE = HostRange.parse(InetAddresses.forString("64:ff9b::"), 96); + } } diff --git a/projects/core/src/main/java/dan200/computercraft/core/lua/TableImpl.java b/projects/core/src/main/java/dan200/computercraft/core/lua/TableImpl.java index 8c3fa6108..5497d16c0 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/lua/TableImpl.java +++ b/projects/core/src/main/java/dan200/computercraft/core/lua/TableImpl.java @@ -6,9 +6,11 @@ package dan200.computercraft.core.lua; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaValues; +import org.jetbrains.annotations.VisibleForTesting; import org.jspecify.annotations.Nullable; import org.squiddev.cobalt.*; +import java.nio.ByteBuffer; import java.util.*; import static dan200.computercraft.api.lua.LuaValues.badTableItem; @@ -37,6 +39,7 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable @Override public long getLong(int index) throws LuaException { + checkValid(); var value = table.rawget(index); if (!(value instanceof LuaNumber)) throw LuaValues.badTableItem(index, "number", value.typeName()); if (value instanceof LuaInteger) return value.toInteger(); @@ -58,9 +61,24 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable private LuaValue getImpl(Object o) { checkValid(); - if (o instanceof String s) return table.rawget(s); - if (o instanceof Integer i) return table.rawget(i); - return Constants.NIL; + var value = convertValue(o); + return value == null ? Constants.NIL : table.rawget(value); + } + + @VisibleForTesting + static @Nullable LuaValue convertValue(@Nullable Object object) { + if (object == null) return Constants.NIL; + if (object instanceof Boolean bool) return ValueFactory.valueOf(bool); + if (object instanceof Double num) return ValueFactory.valueOf(num); + if (object instanceof String str) return ValueFactory.valueOf(str); + if (object instanceof byte[] b) return ValueFactory.valueOf(Arrays.copyOf(b, b.length)); + if (object instanceof ByteBuffer b) { + var bytes = new byte[b.remaining()]; + b.get(bytes); + return ValueFactory.valueOf(bytes); + } + + return null; } @Override @@ -74,6 +92,12 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable return CobaltLuaMachine.toObject(getImpl(o), null); } + @Override + public @Nullable Object get(int index) { + checkValid(); + return CobaltLuaMachine.toObject(table.rawget(index), null); + } + private Map getBackingMap() { checkValid(); if (backingMap != null) return backingMap; diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md index e88a79c2b..fa235a1ba 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/changelog.md @@ -1,3 +1,17 @@ +# New features in CC: Tweaked 1.119.0 + +* Add `commands.getDimension()`. +* Add `cc.base64` module. +* Update Cobalt to 0.9.9, bringing in several Lua 5.5 changes: + * Floats are now printed with enough digits to round trip correctly. + * Add `table.create`. + * `utf8.offset` now returns the final position of the codepoint. + +Several bug fixes: +* Fix handling of integer indexes in `LuaTable`. +* Correct `min` and `sec` defaults in `os.time`. (sircfenner) +* Make HTTP IP filtering stricter. + # New features in CC: Tweaked 1.118.1 Several bug fixes: diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md index f49c48e91..2250571b3 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/help/whatsnew.md @@ -1,8 +1,15 @@ -New features in CC: Tweaked 1.118.1 +New features in CC: Tweaked 1.119.0 + +* Add `commands.getDimension()`. +* Add `cc.base64` module. +* Update Cobalt to 0.9.9, bringing in several Lua 5.5 changes: + * Floats are now printed with enough digits to round trip correctly. + * Add `table.create`. + * `utf8.offset` now returns the final position of the codepoint. Several bug fixes: -* Fix crash in recipe serialisers. -* Fix breaking progress overlay not matching turtle's rotation. -* Fix several recipes not displaying correctly with JEI. +* Fix handling of integer indexes in `LuaTable`. +* Correct `min` and `sec` defaults in `os.time`. (sircfenner) +* Make HTTP IP filtering stricter. Type "help changelog" to see the full version history. diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/base64.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/base64.lua new file mode 100644 index 000000000..b11b99f50 --- /dev/null +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/cc/base64.lua @@ -0,0 +1,182 @@ +-- SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers +-- +-- SPDX-License-Identifier: MPL-2.0 + +--[[- The [`cc.base64`] module provides functions for converting binary data to +and from [Base64](https://en.wikipedia.org/wiki/Base64). + +@usage Encode and decode a string from Base64. + + local base64 = require "cc.base64" + print(base64.encode("Hello, world")) + print(base64.decode("SGVsbG8sIHdvcmxk")) + +@since 1.119.0 +]] + +local expect = require "cc.expect".expect + +local rshift, byte, char, sub = bit32.rshift, string.byte, string.char, string.sub + +local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +--[[- +Encode a binary string to Base64. + +@tparam string str The binary data to encode. +@tparam[opt="+/"] string alt_chars A string of length 2, used to encode the 62nd +and 63rd bit. +@treturn string The Base64 encoded data. + +@usage Convert a string to Base64 + + local base64 = require "cc.base64" + print(base64.encode("Hello, world!")) + +@usage Convert a string to [base64url]. This is an alternative form of Base64, +where the string is encoded with `"-_"` instead of `"+/"`. This allows the string +to be more easily used in URLs, though the padding `=` will still need escaping +with [`textutils.urlEncode`]. + + local base64 = require "cc.base64" + print(base64.encode("Test: \255\230", "-_")) + +[base64url]: https://datatracker.ietf.org/doc/html/rfc4648#section-5 "Base 64 Encoding with URL and Filename Safe Alphabet" +]] +local function encode(str, alt_chars) + expect(1, str, "string") + expect(2, alt_chars, "string", "nil") + + if alt_chars and #alt_chars ~= 2 then + error("alt_chars must be exactly two characters", 2) + end + + --[[ + The below code is optimised to run against Cobalt, so the code is not + entirely idiomatic. + - It's quicker to build use a table lookup and do `lookup[x]` than call + `sub(alphabet, x, x)`. + - As we don't have bit operations, it's quicker to do `x % y`, rather than + `band(x, y - 1)` + - Naive concatenation is quicker than appending to a table. + ]] + + local alphabet = alphabet .. (alt_chars or "+/") + local lookup = {} + for i = 1, #alphabet do lookup[i] = sub(alphabet, i, i) end + + local len = #str + local remainder = len % 3 + local out = "" + for i = 1, len - remainder, 3 do + local c1, c2, c3 = byte(str, i, i + 2) + out = out .. + lookup[rshift(c1, 2) + 1] .. + lookup[c1 % 4 * 16 + rshift(c2, 4) + 1] .. + lookup[c2 % 16 * 4 + rshift(c3, 6) + 1] .. + lookup[c3 % 64 + 1] + end + + if remainder == 2 then + local c1, c2 = byte(str, len - 1, len) + out = out .. + lookup[rshift(c1, 2) + 1] .. + lookup[c1 % 4 * 16 + rshift(c2, 4) + 1] .. + lookup[c2 % 16 * 4 + 1] .. + "=" + elseif remainder == 1 then + local c1 = byte(str, len) + out = out .. lookup[rshift(c1, 2) + 1] .. lookup[c1 % 4 * 16 + 1] .. "==" + end + + return out +end + +--[[- +Decode a Base64-encoded string back to its original data. + +This function requires the data to be valid Base64 with the trailing padding +bytes. + +@tparam string str The Base64-encoded data to decode. +@tparam[opt="+/"] string alt_chars A string of length 2, used to encode the 62nd +and 63rd bit. +@treturn[1] string The decoded data. +@treturn[2] nil If the data is not valid Base64, or is missing the trailing padding. +@treturn[2] string The reason the data failed to decode. + +@usage Decode a string from Base64 + + local base64 = require "cc.base64" + print(base64.decode("SGVsbG8sIHdvcmxk")) + +@usage Decode [base64url]-encoded data. + + local base64 = require "cc.base64" + print(base64.decode("VGVzdDog_-Y=", "-_")) + +[base64url]: https://datatracker.ietf.org/doc/html/rfc4648#section-5 "Base 64 Encoding with URL and Filename Safe Alphabet" +]] +local function decode(str, alt_chars) + expect(1, str, "string") + expect(2, alt_chars, "string", "nil") + + if alt_chars and #alt_chars ~= 2 then + error("alt_chars must be exactly two characters", 2) + end + + if not alt_chars then alt_chars = "+/" end + + local len = #str + + if (len % 4) ~= 0 or not str:find("^[%w%" .. alt_chars:sub(1, 1) .. "%" .. alt_chars:sub(2, 2) .. "]*=?=?$") then + return nil, "input is not valid base64" + end + + local alphabet = alphabet .. alt_chars + local lookup = {} + for i = 1, #alphabet do lookup[byte(alphabet, i)] = i - 1 end + + local padding + if sub(str, -2) == "==" then + padding = 2 + elseif sub(str, -1) == "=" then + padding = 1 + else + padding = 0 + end + + local out = "" + for i = 1, padding == 0 and len or len - 4, 4 do + local e1, e2, e3, e4 = byte(str, i, i + 3) + e1 = lookup[e1] + e2 = lookup[e2] + e3 = lookup[e3] + e4 = lookup[e4] + out = out .. char( + e1 * 4 + rshift(e2, 4), + e2 % 16 * 16 + rshift(e3, 2), + e3 % 4 * 64 + e4 + ) + end + + if padding == 2 then + local e1, e2 = byte(str, len - 3, len - 2) + e1 = lookup[e1] + e2 = lookup[e2] + out = out .. char(e1 * 4 + rshift(e2, 4)) + elseif padding == 1 then + local e1, e2, e3 = byte(str, len - 3, len - 1) + e1 = lookup[e1] + e2 = lookup[e2] + e3 = lookup[e3] + out = out .. char( + e1 * 4 + rshift(e2, 4), + e2 % 16 * 16 + rshift(e3, 2) + ) + end + + return out +end + +return { encode = encode, decode = decode } diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/shell.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/shell.lua index 2e7e19654..0d3e36edb 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/programs/shell.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/programs/shell.lua @@ -350,16 +350,6 @@ function shell.resolve(path) end end -local function pathWithExtension(_sPath, _sExt) - local nLen = #sPath - local sEndChar = string.sub(_sPath, nLen, nLen) - -- Remove any trailing slashes so we can add an extension to the path safely - if sEndChar == "/" or sEndChar == "\\" then - _sPath = string.sub(_sPath, 1, nLen - 1) - end - return _sPath .. "." .. _sExt -end - --- Resolve a program, using the [program path][`path`] and list of [aliases][`aliases`]. -- -- @tparam string command The name of the program @@ -384,7 +374,7 @@ function shell.resolveProgram(command) if fs.exists(sPath) and not fs.isDir(sPath) then return sPath else - local sPathLua = pathWithExtension(sPath, "lua") + local sPathLua = sPath .. ".lua" if fs.exists(sPathLua) and not fs.isDir(sPathLua) then return sPathLua end @@ -398,7 +388,7 @@ function shell.resolveProgram(command) if fs.exists(sPath) and not fs.isDir(sPath) then return sPath else - local sPathLua = pathWithExtension(sPath, "lua") + local sPathLua = sPath .. ".lua" if fs.exists(sPathLua) and not fs.isDir(sPathLua) then return sPathLua end diff --git a/projects/core/src/test/java/dan200/computercraft/core/apis/http/NetworkUtilsTest.java b/projects/core/src/test/java/dan200/computercraft/core/apis/http/NetworkUtilsTest.java new file mode 100644 index 000000000..68a93fa06 --- /dev/null +++ b/projects/core/src/test/java/dan200/computercraft/core/apis/http/NetworkUtilsTest.java @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.apis.http; + +import dan200.computercraft.test.core.ReplaceUnderscoresDisplayNameGenerator; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@DisplayNameGeneration(ReplaceUnderscoresDisplayNameGenerator.class) +class NetworkUtilsTest { + @Test + public void test_getAddress_with_scoped_address() { + var err = assertThrows(HTTPRequestException.class, () -> NetworkUtils.getAddress("[::1%1]", 80, false)); + assertEquals("Scoped address not permitted", err.getMessage()); + } +} diff --git a/projects/core/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java b/projects/core/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java index ddf009c82..c23d6c60b 100644 --- a/projects/core/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java +++ b/projects/core/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java @@ -36,6 +36,8 @@ public class AddressRuleTest { "224.0.0.1", "ff02::1", // CGNAT "100.64.0.0", "100.127.255.255", + // NAT64 + "64:ff9b::c0a8:0101", // Cloud metadata providers "100.100.100.200", // Alibaba "192.0.0.192", // Oracle diff --git a/projects/core/src/test/java/dan200/computercraft/core/lua/CobaltLuaTableTest.java b/projects/core/src/test/java/dan200/computercraft/core/lua/CobaltLuaTableTest.java new file mode 100644 index 000000000..391d3e9e8 --- /dev/null +++ b/projects/core/src/test/java/dan200/computercraft/core/lua/CobaltLuaTableTest.java @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.lua; + +import org.squiddev.cobalt.*; + +import java.util.Map; + +class CobaltLuaTableTest implements LuaTableContract { + @Override + public TableImpl create(Map map) { + try { + return new TableImpl(VarargArguments.of(Constants.NONE), convertMap(map)); + } catch (LuaError e) { + throw new RuntimeException(e); + } + } + + private static LuaValue convert(Object object) throws LuaError { + var value = TableImpl.convertValue(object); + if (value != null) return value; + + if (object instanceof Map x) return convertMap(x); + if (object instanceof Integer x) return ValueFactory.valueOf(x); + throw new IllegalArgumentException("Unknown value " + object); + } + + private static LuaTable convertMap(Map map) throws LuaError { + var out = new LuaTable(); + for (var entry : map.entrySet()) out.rawset(convert(entry.getKey()), convert(entry.getValue())); + return out; + } +} diff --git a/projects/core/src/test/java/dan200/computercraft/core/lua/LuaTableContract.java b/projects/core/src/test/java/dan200/computercraft/core/lua/LuaTableContract.java new file mode 100644 index 000000000..1423ccf3b --- /dev/null +++ b/projects/core/src/test/java/dan200/computercraft/core/lua/LuaTableContract.java @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.lua; + +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.lua.LuaTable; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Test Interface defining the behaviour of a {@link LuaTable} implementation. + * + * @param The implementation of {@link LuaTable} we are testing. + */ +public interface LuaTableContract> { + T create(Map map); + + default T createList(List list) { + var out = new HashMap<>(); + var i = 0; + for (var elem : list) { + var idx = ++i; + // We normalise our index to be a double, to match the behaviour of CobaltLuaMachine.toValue, which converts + // all numbers to doubles. + if (elem != null) out.put((double) idx, elem); + } + return create(out); + } + + @Test + default void testLength() { + assertEquals(0, createList(List.of()).length()); + assertEquals(1, createList(List.of("a")).length()); + assertEquals(2, createList(List.of("a", "a")).length()); + assertEquals(1, createList(Arrays.asList("a", null, "a")).length()); + } + + @Test + default void testGetIntLikeKey() { + assertEquals("a", createList(List.of("a", "b", "c")).get(1)); + assertEquals("a", createList(List.of("a", "b", "c")).get(1.0)); + // This is a little dubious, but ensures we have consistent behaviour between implementations (doubles are + // the only number) and we don't need to handle double/int normalisation within ObjectLuaTable. + assertNull(createList(List.of("a", "b", "c")).get((Object) 1)); + assertNull(createList(List.of("a", "b", "c")).get(1.0f)); + assertNull(createList(List.of("a", "b", "c")).get((Object) (short) 1)); + } + + @Test + default void testGetInt() throws LuaException { + assertEquals("a", createList(List.of("a")).get(1)); + assertEquals(true, createList(List.of(true)).getBoolean(1)); + assertEquals(12345, createList(List.of(12345.0)).getInt(1)); + assertEquals(12345L, createList(List.of(12345.0)).getLong(1)); + assertEquals("abc", createList(List.of("abc")).getString(1)); + } +} diff --git a/projects/core/src/test/java/dan200/computercraft/core/lua/ObjectLuaTableTest.java b/projects/core/src/test/java/dan200/computercraft/core/lua/ObjectLuaTableTest.java new file mode 100644 index 000000000..66df7407f --- /dev/null +++ b/projects/core/src/test/java/dan200/computercraft/core/lua/ObjectLuaTableTest.java @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers +// +// SPDX-License-Identifier: MPL-2.0 + +package dan200.computercraft.core.lua; + +import dan200.computercraft.api.lua.ObjectLuaTable; + +import java.util.Map; + +class ObjectLuaTableTest implements LuaTableContract { + @Override + public ObjectLuaTable create(Map map) { + return new ObjectLuaTable(map); + } +} diff --git a/projects/core/src/test/resources/test-rom/spec/apis/http_spec.lua b/projects/core/src/test/resources/test-rom/spec/apis/http_spec.lua index 39a1506ac..4ccc99c8b 100644 --- a/projects/core/src/test/resources/test-rom/spec/apis/http_spec.lua +++ b/projects/core/src/test/resources/test-rom/spec/apis/http_spec.lua @@ -22,6 +22,11 @@ describe("The http library", function() -- to ensure the general control flow works. expect({ http.checkURL("http://localhost") }):same({ false, "Domain not permitted" }) expect({ http.checkURL("http://127.0.0.1") }):same({ false, "Domain not permitted" }) + expect({ http.checkURL("http://[::1]") }):same({ false, "Domain not permitted" }) + end) + + expect("rejects scoped addresses", function() + expect({ http.checkURL("http://[::1%1]") }):same({ false, "Scoped address not permitted" }) end) end) diff --git a/projects/core/src/test/resources/test-rom/spec/apis/os_spec.lua b/projects/core/src/test/resources/test-rom/spec/apis/os_spec.lua index 9c5980adf..4ce6138cc 100644 --- a/projects/core/src/test/resources/test-rom/spec/apis/os_spec.lua +++ b/projects/core/src/test/resources/test-rom/spec/apis/os_spec.lua @@ -143,6 +143,24 @@ describe("The os library", function() local t2 = os.time { year = 2000, month = 10, day = 1, hour = 23, min = 10, sec = 19 } expect(t1 - t2):eq(60 * 2 - 2) end) + + it("uses correct date table default for hour", function() + local t1 = os.time { year = 1970, month = 1, day = 1, hour = nil, min = 0, sec = 0 } + local t2 = os.time { year = 1970, month = 1, day = 1, hour = 12, min = 0, sec = 0 } + expect(t1):eq(t2) + end) + + it("uses correct date table default for min", function() + local t1 = os.time { year = 1970, month = 1, day = 1, hour = 0, min = nil, sec = 0 } + local t2 = os.time { year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = 0 } + expect(t1):eq(t2) + end) + + it("uses correct date table default for sec", function() + local t1 = os.time { year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = nil } + local t2 = os.time { year = 1970, month = 1, day = 1, hour = 0, min = 0, sec = 0 } + expect(t1):eq(t2) + end) end) describe("os.day", function() diff --git a/projects/core/src/test/resources/test-rom/spec/modules/cc/base64_spec.lua b/projects/core/src/test/resources/test-rom/spec/modules/cc/base64_spec.lua new file mode 100644 index 000000000..f129813a9 --- /dev/null +++ b/projects/core/src/test/resources/test-rom/spec/modules/cc/base64_spec.lua @@ -0,0 +1,66 @@ +-- SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers +-- +-- SPDX-License-Identifier: MPL-2.0 + +describe("cc.base64", function() + local base64 = require "cc.base64" + + it("random strings roundtrip", function() + for _ = 1, 1000 do + local len = math.random(1000) + local str = "" + for _ = 1, len do str = str .. string.char(math.random(0, 255)) end + + expect(base64.decode(base64.encode(str))):eq(str) + end + end) + + describe("encode", function() + it("validates arguments", function() + expect.error(base64.encode, 2):eq("bad argument #1 (string expected, got number)") + expect.error(base64.encode, "", 2):eq("bad argument #2 (string expected, got number)") + expect.error(base64.encode, "", ""):eq("alt_chars must be exactly two characters") + end) + + it("encodes as expected", function() + expect(base64.encode("")):eq("") + expect(base64.encode("light w")):eq("bGlnaHQgdw==") + expect(base64.encode("light wo")):eq("bGlnaHQgd28=") + expect(base64.encode("light wor")):eq("bGlnaHQgd29y") + expect(base64.encode("Many hands make light work.")):eq("TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu") + end) + + it("encodes using alternative alphabet", function() + expect(base64.encode("Test: \255\230")):eq("VGVzdDog/+Y=") + expect(base64.encode("Test: \255\230", "-_")):eq("VGVzdDog_-Y=") + end) + end) + + describe("decode", function() + it("validates arguments", function() + expect.error(base64.decode, 2):eq("bad argument #1 (string expected, got number)") + expect.error(base64.decode, "", 2):eq("bad argument #2 (string expected, got number)") + expect.error(base64.decode, "", ""):eq("alt_chars must be exactly two characters") + end) + + it("decodes as expected", function() + expect(base64.decode("")):eq("") + expect(base64.decode("bGlnaHQgdw==")):eq("light w") + expect(base64.decode("bGlnaHQgd28=")):eq("light wo") + expect(base64.decode("bGlnaHQgd29y")):eq("light wor") + expect(base64.decode("TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu")):eq("Many hands make light work.") + end) + + it("decodes using alternative alphabet", function() + expect(base64.decode("VGVzdDog/+Y=")):eq("Test: \255\230") + expect(base64.decode("VGVzdDog_-Y=", "-_")):eq("Test: \255\230") + end) + + it("validates the input string", function() + expect { base64.decode("VGVzdDog/+Y") }:same { nil, "input is not valid base64" } + expect { base64.decode("VGVzdDog/+Y==") }:same { nil, "input is not valid base64" } + expect { base64.decode("VGVzdDog/=Y=") }:same { nil, "input is not valid base64" } + expect { base64.decode("VGVzdDog/===") }:same { nil, "input is not valid base64" } + end) + end) +end) diff --git a/projects/core/src/test/resources/test-rom/spec/programs/shell_spec.lua b/projects/core/src/test/resources/test-rom/spec/programs/shell_spec.lua index aa80d4602..7e7a717a1 100644 --- a/projects/core/src/test/resources/test-rom/spec/programs/shell_spec.lua +++ b/projects/core/src/test/resources/test-rom/spec/programs/shell_spec.lua @@ -152,6 +152,18 @@ describe("The shell", function() shell.resolveProgram("ls") expect.error(shell.resolveProgram, nil):eq("bad argument #1 (string expected, got nil)") end) + + it("finds files on the shell path", function() + expect(shell.resolveProgram("edit")):eq("rom/programs/edit.lua") + expect(shell.resolveProgram("edit.lua")):eq("rom/programs/edit.lua") + end) + + it("finds programs even with a trailing slash", function() + -- This feels silly: "edit/" is a directory, not the name of a file. However, + -- fs.combine (and CC's other path normalisation code) strips trailing slashes, + -- so this should still resolve the program. + expect(shell.resolveProgram("/rom/programs/edit/")):eq("rom/programs/edit.lua") + end) end) describe("shell.complete", function() diff --git a/projects/fabric/build.gradle.kts b/projects/fabric/build.gradle.kts index 12381c0ad..df669d42d 100644 --- a/projects/fabric/build.gradle.kts +++ b/projects/fabric/build.gradle.kts @@ -65,8 +65,6 @@ dependencies { exclude("net.fabricmc", "fabric-loader") exclude("net.fabricmc.fabric-api") } - // FIXME: A lie, but Fabric Create uses the wrong mappings - compileOnly(libs.create.forge) { isTransitive = false } clientRuntimeOnly(libs.bundles.externalMods.fabric.runtime) { exclude("net.fabricmc", "fabric-loader") diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java index 2f289d349..8562861e6 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java @@ -17,7 +17,6 @@ import dan200.computercraft.impl.TurtleUpgrades; import dan200.computercraft.shared.command.CommandComputerCraft; import dan200.computercraft.shared.config.ConfigSpec; import dan200.computercraft.shared.details.FluidDetails; -import dan200.computercraft.shared.integration.CreateIntegration; import dan200.computercraft.shared.network.NetworkMessages; import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods; import dan200.computercraft.shared.peripheral.modem.wired.CableBlock; @@ -141,8 +140,6 @@ public class ComputerCraft { ComputerCraftAPI.registerGenericSource(InventoryMethods::new); Peripherals.addGenericLookup(InventoryMethods::extractContainer); - - if (FabricLoader.getInstance().isModLoaded(CreateIntegration.ID)) CreateIntegration.setup(); } private static void registerPayloadType(PayloadTypeRegistry registry, CustomPacketPayload.TypeAndCodec type) { diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java b/projects/fabric/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java deleted file mode 100644 index a606f9376..000000000 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers -// -// SPDX-License-Identifier: MPL-2.0 - -package dan200.computercraft.shared.integration; - -import com.simibubi.create.api.contraption.BlockMovementChecks; -import com.simibubi.create.api.contraption.BlockMovementChecks.CheckResult; -import dan200.computercraft.shared.peripheral.modem.wired.CableBlock; -import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock; - -/** - * Integration with Create. - */ -public final class CreateIntegration { - public static final String ID = "create"; - - private CreateIntegration() { - } - - public static void setup() { - // Allow modems to be treated as "attached" to their adjacent block. - BlockMovementChecks.registerAttachedCheck((state, world, pos, direction) -> { - var block = state.getBlock(); - if (block instanceof WirelessModemBlock) { - return CheckResult.of(state.getValue(WirelessModemBlock.FACING) == direction); - } else if (block instanceof CableBlock) { - return CheckResult.of(state.getValue(CableBlock.MODEM).getFacing() == direction); - } else { - return CheckResult.PASS; - } - }); - } -} diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index 23187ce13..d85ff6c1c 100644 --- a/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/fabric/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -64,7 +64,6 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; -import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; import java.nio.file.Path; @@ -220,9 +219,10 @@ public class PlatformHelperImpl implements PlatformHelper { } @Override - public boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos) { - return UseEntityCallback.EVENT.invoker().interact(player, entity.level(), InteractionHand.MAIN_HAND, entity, new EntityHitResult(entity, hitPos)).consumesAction() - || player.interactOn(entity, InteractionHand.MAIN_HAND, hitPos).consumesAction(); + public boolean interactWithEntity(ServerPlayer player, EntityHitResult hit) { + var entity = hit.getEntity(); + return UseEntityCallback.EVENT.invoker().interact(player, entity.level(), InteractionHand.MAIN_HAND, entity, hit).consumesAction() + || player.interactOn(entity, InteractionHand.MAIN_HAND, hit.getLocation().subtract(entity.position())).consumesAction(); } @Override diff --git a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java index 40d4a9836..a9165e6f8 100644 --- a/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java +++ b/projects/forge/src/main/java/dan200/computercraft/shared/platform/PlatformHelperImpl.java @@ -52,7 +52,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.EntityHitResult; import net.neoforged.fml.ModList; import net.neoforged.neoforge.capabilities.BlockCapability; import net.neoforged.neoforge.capabilities.BlockCapabilityCache; @@ -216,7 +216,9 @@ public class PlatformHelperImpl implements PlatformHelper { } @Override - public boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos) { + public boolean interactWithEntity(ServerPlayer player, EntityHitResult hit) { + var entity = hit.getEntity(); + var hitPos = hit.getLocation().subtract(entity.position()); var interactAt = CommonHooks.onInteractEntityAt(player, entity, hitPos, InteractionHand.MAIN_HAND); if (interactAt == null) interactAt = player.interactOn(entity, InteractionHand.MAIN_HAND, hitPos); return interactAt.consumesAction(); diff --git a/projects/lints/src/main/kotlin/cc/tweaked/linter/SideChecker.kt b/projects/lints/src/main/kotlin/cc/tweaked/linter/SideChecker.kt index 958051ef3..0f045954d 100644 --- a/projects/lints/src/main/kotlin/cc/tweaked/linter/SideChecker.kt +++ b/projects/lints/src/main/kotlin/cc/tweaked/linter/SideChecker.kt @@ -75,6 +75,7 @@ internal class SideProvider { private fun getSideImpl(sym: Symbol): Optional = when (sym.getKind()) { ElementKind.MODULE -> Optional.empty() + ElementKind.PACKAGE -> { val pkg = sym.toString() when {