1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-11-17 15:24:52 +00:00

Merge branch 'mc-1.16.x' into mc-1.18.x

This commit is contained in:
Jonathan Coates 2022-10-01 12:36:09 +01:00
commit 08895cdecc
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
32 changed files with 342 additions and 150 deletions

View File

@ -1,8 +1,5 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: ComputerCraft Discord
url: https://discord.computercraft.cc
about: Get help on the ComputerCraft Discord.
- name: GitHub Discussions - name: GitHub Discussions
url: https://github.com/cc-tweaked/CC-Tweaked/discussions url: https://github.com/cc-tweaked/CC-Tweaked/discussions
about: Or ask questions on GitHub Discussions. about: Ask questions on GitHub Discussions.

View File

@ -15,7 +15,7 @@ jobs:
with: with:
java-version: 8 java-version: 8
- name: Cache gradle dependencies - name: Cache Gradle dependencies
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/.gradle/caches path: ~/.gradle/caches
@ -32,7 +32,7 @@ jobs:
run: | run: |
./gradlew assemble || ./gradlew assemble ./gradlew assemble || ./gradlew assemble
./gradlew downloadAssets || ./gradlew downloadAssets ./gradlew downloadAssets || ./gradlew downloadAssets
xvfb-run ./gradlew build ./gradlew build
- name: Upload Jar - name: Upload Jar
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
@ -40,31 +40,12 @@ jobs:
name: CC-Tweaked name: CC-Tweaked
path: build/libs path: build/libs
- name: Upload Screnshots - name: Upload coverage
uses: actions/upload-artifact@v2
with:
name: Screenshots
path: test-files/client/screenshots
if-no-files-found: ignore
retention-days: 5
if: failure()
- name: Upload Coverage
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v2
- name: Parse test reports - name: Parse test reports
run: ./tools/parse-reports.py run: ./tools/parse-reports.py
if: ${{ failure() }} if: ${{ failure() }}
- name: Cache pre-commit
uses: actions/cache@v2
with:
path: ~/.cache/pre-commit
key: ${{ runner.os }}-pre-commit-${{ hashFiles('config/pre-commit/config.yml') }}
restore-keys: |
${{ runner.os }}-pre-commit-
- name: Run linters - name: Run linters
run: | uses: pre-commit/action@v3.0.0
pip install pre-commit
pre-commit run --config config/pre-commit/config.yml --show-diff-on-failure --all --color=always

View File

@ -26,12 +26,6 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Setup illuaminate
run: |
test -d bin || mkdir bin
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
chmod +x bin/illuaminate
- name: Setup node - name: Setup node
run: npm ci run: npm ci

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
/classes /classes
/logs /logs
/build /build
/buildSrc/build
/out /out
/doc/out/ /doc/out/
/node_modules /node_modules

View File

@ -17,6 +17,6 @@ vscode:
tasks: tasks:
- name: Setup pre-commit hool - name: Setup pre-commit hool
init: pre-commit install --config config/pre-commit/config.yml --allow-missing-config init: pre-commit install --allow-missing-config
- name: Install npm packages - name: Install npm packages
init: npm ci init: npm ci

View File

@ -41,8 +41,8 @@ repos:
- id: illuaminate - id: illuaminate
name: Check Lua code name: Check Lua code
files: ".*\\.(lua|java|md)" files: ".*\\.(lua|java|md)"
language: script language: system
entry: config/pre-commit/illuaminate-lint.sh entry: ./gradlew lintLua -i
pass_filenames: false pass_filenames: false
require_serial: true require_serial: true

View File

@ -39,40 +39,30 @@ are run whenever you submit a PR, it's often useful to run this before committin
- **[Checkstyle]:** Checks Java code to ensure it is consistently formatted. This can be run with `./gradlew build` or - **[Checkstyle]:** Checks Java code to ensure it is consistently formatted. This can be run with `./gradlew build` or
`./gradle check`. `./gradle check`.
- **[illuaminate]:** Checks Lua code for semantic and styleistic issues. See [the usage section][illuaminate-usage] for - **[illuaminate]:** Checks Lua code for semantic and styleistic issues. This can be run with `./gradlew lintLua`.
how to download and run it. You may need to generate the Java documentation stubs (see "Documentation" below) for all
lints to pass.
### Documentation ### Documentation
When writing documentation for [CC: Tweaked's documentation website][docs], it may be useful to build the documentation When writing documentation for [CC: Tweaked's documentation website][docs], it may be useful to build the documentation
and preview it yourself before submitting a PR. and preview it yourself before submitting a PR.
Building all documentation is, sadly, a multi-stage process (though this is largely hidden by Gradle). First we need to Our documentation generation pipeline is rather complex, and involves invoking several external tools. Most of this
convert Java doc-comments into Lua ones, we also generate some Javascript to embed. All of this is then finally fed into complexity is hidden by Gradle, but you will need to perform some initial setup:
illuaminate, which spits out our HTML.
#### Setting up the tooling - Install [Node/npm][node].
For various reasons, getting the environment set up to build documentation can be pretty complex. I'd quite like to - Run `npm ci` to install our Node dependencies.
automate this via Docker and/or nix in the future, but this needs to be done manually for now.
This tooling is only needed if you need to build the whole website. If you just want to generate the Lua stubs, you can You can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code, writing the resulting
skp this section. HTML into `./build/docs/site`.
- Install Node/npm and install our Node packages with `npm ci`.
- Install [illuaminate][illuaminate-usage] as described above.
#### Building documentation
Gradle should be your entrypoint to building most documentation. There's two tasks which are of interest:
- `./gradlew luaJavadoc` - Generate documentation stubs for Java methods.
- `./gradlew docWebsite` - Generate the whole website (including Javascript pages). The resulting HTML is stored at
`./build/docs/site/`.
#### Writing documentation #### Writing documentation
illuaminate's documentation system is not currently documented (somewhat ironic), but is _largely_ the same as illuaminate's documentation system is not currently documented (somewhat ironic), but is _largely_ the same as
[ldoc][ldoc]. Documentation comments are written in Markdown, [ldoc][ldoc]. Documentation comments are written in Markdown,
Our markdown engine does _not_ support GitHub flavoured markdown, and so does not support all the features one might Our markdown engine does _not_ support GitHub flavoured markdown, and so does not support all the features one might
expect (such as tables). It is very much recommended that you build and preview the docs locally first. expect. It is recommended that you build and preview the docs locally first.
When iterating on documentation, you can get Gradle to rebuild the website every time a file changes by running
`./gradlew docWebsite -t`. This will take a couple of seconds to run, but definitely beats running it manually!
### Testing ### Testing
Thankfully running tests is much simpler than running the documentation generator! `./gradlew check` will run the Thankfully running tests is much simpler than running the documentation generator! `./gradlew check` will run the
@ -90,11 +80,10 @@ Before we get into writing tests, it's worth mentioning the various test suites
These tests are run by the '"Core" Java' test suite, and so are also run with `./gradlew test`. These tests are run by the '"Core" Java' test suite, and so are also run with `./gradlew test`.
- In-game (`./src/testMod/java/dan200/computercraft/ingame/`): These tests are run on an actual Minecraft server and client, - In-game (`./src/testMod/java/dan200/computercraft/ingame/`): These tests are run on an actual Minecraft server, using
using [the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals. the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals.
These are run by `./gradlew testClient` and `./gradlew testServer`. You may want to run the client under `xvfb-run` These tests are run with `./gradlew testServer`.
or similar when running in a headless environment.
## CraftOS tests ## CraftOS tests
CraftOS's tests are written using a test system called "mcfly", heavily inspired by [busted] (and thus RSpec). Groups of CraftOS's tests are written using a test system called "mcfly", heavily inspired by [busted] (and thus RSpec). Groups of
@ -107,9 +96,9 @@ asserts that your variable `foo` is equal to the expected value `"bar"`.
[community]: README.md#Community "Get in touch with the community." [community]: README.md#Community "Get in touch with the community."
[checkstyle]: https://checkstyle.org/ [checkstyle]: https://checkstyle.org/
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub" [illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
[illuaminate-usage]: https://github.com/SquidDev/illuaminate/blob/master/README.md#usage "Installing Illuaminate"
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance" [weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
[docs]: https://tweaked.cc/ "CC: Tweaked documentation" [docs]: https://tweaked.cc/ "CC: Tweaked documentation"
[ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator." [ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator."
[mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg [mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg
[busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing." [busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing."
[node]: https://nodejs.org/en/ "Node.js"

View File

@ -13,9 +13,8 @@ developing the mod, [check out the instructions here](CONTRIBUTING.md#developing
## Community ## Community
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
ComputerCraft we have a [forum](https://forums.computercraft.cc/) and [Discord guild](https://discord.computercraft.cc)! ComputerCraft, do check out our [forum] and [GitHub discussions page][GitHub discussions]! There's also a fairly
There's also a fairly populated, albeit quiet [IRC channel](http://webchat.esper.net/?channels=computercraft), if that's populated, albeit quiet [IRC channel][irc], if that's more your cup of tea.
more your cup of tea.
We also host fairly comprehensive documentation at [tweaked.cc](https://tweaked.cc/ "The CC: Tweaked website"). We also host fairly comprehensive documentation at [tweaked.cc](https://tweaked.cc/ "The CC: Tweaked website").
@ -52,3 +51,6 @@ the generated documentation [can be browsed online](https://tweaked.cc/javadoc/)
[modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth" [modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge." [forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge" [ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge"
[forum]: https://forums.computercraft.cc/
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"

View File

@ -11,9 +11,12 @@ plugins {
id "org.spongepowered.mixin" version "0.7.+" id "org.spongepowered.mixin" version "0.7.+"
id "org.parchmentmc.librarian.forgegradle" version "1.+" id "org.parchmentmc.librarian.forgegradle" version "1.+"
id "com.github.johnrengelman.shadow" version "7.1.2" id "com.github.johnrengelman.shadow" version "7.1.2"
id "cc-tweaked.illuaminate"
} }
import org.apache.tools.ant.taskdefs.condition.Os import org.apache.tools.ant.taskdefs.condition.Os
import cc.tweaked.gradle.IlluaminateExec
import cc.tweaked.gradle.IlluaminateExecToDir
version = mod_version version = mod_version
@ -169,6 +172,10 @@ dependencies {
cctJavadoc 'cc.tweaked:cct-javadoc:1.4.7' cctJavadoc 'cc.tweaked:cct-javadoc:1.4.7'
} }
illuaminate {
version.set("0.1.0-3-g0f40379")
}
// Compile tasks // Compile tasks
javadoc { javadoc {
@ -314,32 +321,36 @@ def rollup = tasks.register("rollup", Exec.class) {
commandLine mkCommand('"node_modules/.bin/rollup" --config rollup.config.js') commandLine mkCommand('"node_modules/.bin/rollup" --config rollup.config.js')
} }
def illuaminateDocs = tasks.register("illuaminateDocs", Exec.class) { def illuaminateDocs = tasks.register("illuaminateDocs", IlluaminateExecToDir.class) {
group = "documentation" group = "documentation"
description = "Generates docs using Illuaminate" description = "Generates docs using Illuaminate"
dependsOn(rollup, luaJavadoc) dependsOn(rollup)
inputs.files(fileTree("doc")).withPropertyName("docs") // Config files
inputs.files(fileTree("src/main/resources/data/computercraft/lua/rom")).withPropertyName("lua rom")
inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp") inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
inputs.dir("$buildDir/docs/luaJavadoc") // Sources
inputs.files(fileTree("doc")).withPropertyName("docs")
inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
// Additional assets
inputs.file("$buildDir/rollup/index.js").withPropertyName("scripts") inputs.file("$buildDir/rollup/index.js").withPropertyName("scripts")
inputs.file("src/web/styles.css").withPropertyName("styles") inputs.file("src/web/styles.css").withPropertyName("styles")
outputs.dir("$buildDir/docs/lua")
commandLine mkCommand('"bin/illuaminate" doc-gen') // Output directory. Also defined in illuaminate.sexp and transform.tsx
output.set(new File(buildDir, "docs/lua"))
args = ["doc-gen"]
} }
def jsxDocs = tasks.register("jsxDocs", Exec) { def jsxDocs = tasks.register("jsxDocs", Exec) {
group = "documentation" group = "documentation"
description = "Post-processes documentation to statically render some dynamic content." description = "Post-processes documentation to statically render some dynamic content."
dependsOn(illuaminateDocs)
inputs.files(fileTree("src/web")).withPropertyName("sources") inputs.files(fileTree("src/web")).withPropertyName("sources")
inputs.file("src/generated/export/index.json").withPropertyName("export") inputs.file("src/generated/export/index.json").withPropertyName("export")
inputs.file("package-lock.json").withPropertyName("package-lock.json") inputs.file("package-lock.json").withPropertyName("package-lock.json")
inputs.file("tsconfig.json").withPropertyName("Typescript config") inputs.file("tsconfig.json").withPropertyName("Typescript config")
inputs.files(fileTree("$buildDir/docs/lua")) inputs.files(illuaminateDocs)
outputs.dir("$buildDir/docs/site") outputs.dir("$buildDir/docs/site")
commandLine mkCommand('"node_modules/.bin/ts-node" -T --esm src/web/transform.tsx') commandLine mkCommand('"node_modules/.bin/ts-node" -T --esm src/web/transform.tsx')
@ -403,6 +414,23 @@ license {
check.dependsOn("licenseCheck") check.dependsOn("licenseCheck")
def lintLua = tasks.register("lintLua", IlluaminateExec.class) {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"
// Config files
inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
// Sources
inputs.files(fileTree("doc")).withPropertyName("docs")
inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
args = ["lint"]
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}
def testServerClassDumpDir = new File(buildDir, "jacocoClassDump/runTestServer") def testServerClassDumpDir = new File(buildDir, "jacocoClassDump/runTestServer")
def testServer = tasks.register("testServer", JavaExec.class) { def testServer = tasks.register("testServer", JavaExec.class) {

18
buildSrc/build.gradle.kts Normal file
View File

@ -0,0 +1,18 @@
plugins {
`java-gradle-plugin`
`kotlin-dsl`
}
repositories {
mavenCentral()
gradlePluginPortal()
}
gradlePlugin {
plugins {
register("cc-tweaked.illuaminate") {
id = "cc-tweaked.illuaminate"
implementationClass = "cc.tweaked.gradle.IlluaminatePlugin"
}
}
}

View File

@ -0,0 +1,11 @@
package cc.tweaked.gradle
import org.gradle.api.provider.Property
import org.gradle.api.tasks.AbstractExecTask
import org.gradle.api.tasks.OutputDirectory
import java.io.File
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
@get:OutputDirectory
abstract val output: Property<File>
}

View File

@ -0,0 +1,121 @@
package cc.tweaked.gradle
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.AbstractExecTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import java.io.File
abstract class IlluaminateExtension {
/** The version of illuaminate to use. */
abstract val version: Property<String>
/** The path to illuaminate. If not given, illuaminate will be downloaded automatically. */
abstract val file: Property<File>
}
class IlluaminatePlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create("illuaminate", IlluaminateExtension::class.java)
extension.file.convention(setupDependency(project, extension.version))
project.tasks.register(SetupIlluaminate.NAME, SetupIlluaminate::class.java) {
file.set(extension.file.map { it.absolutePath })
}
}
/** Set up a repository for illuaminate and download our binary from it. */
private fun setupDependency(project: Project, version: Provider<String>): Provider<File> {
project.repositories.ivy {
name = "Illuaminate"
setUrl("https://squiddev.cc/illuaminate/bin/")
patternLayout {
artifact("[revision]/[artifact]-[ext]")
}
metadataSources {
artifact()
}
content {
includeModule("cc.squiddev", "illuaminate")
}
}
return version.map {
val dep = illuaminateArtifact(project, it)
val configuration = project.configurations.detachedConfiguration(dep)
configuration.isTransitive = false
configuration.resolve().single()
}
}
/** Define a dependency for illuaminate from a version number and the current operating system. */
private fun illuaminateArtifact(project: Project, version: String): Dependency {
val osName = System.getProperty("os.name").toLowerCase()
val (os, suffix) = when {
osName.contains("windows") -> Pair("windows", ".exe")
osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "")
osName.contains("linux") -> Pair("linux", "")
else -> error("Unsupported OS $osName for illuaminate")
}
val osArch = System.getProperty("os.arch").toLowerCase()
val arch = when {
osArch == "arm" || osArch.startsWith("aarch") -> error("Unsupported architecture '$osArch' for illuaminate")
osArch.contains("64") -> "x86_64"
else -> error("Unsupported architecture $osArch for illuaminate")
}
return project.dependencies.create(
mapOf(
"group" to "cc.squiddev",
"name" to "illuaminate",
"version" to version,
"ext" to "$os-$arch$suffix",
),
)
}
}
private val Task.illuaminatePath: String? // "?" needed to avoid overload ambiguity in setExecutable below.
get() = project.extensions.getByType(IlluaminateExtension::class.java).file.get().absolutePath
/** Prepares illuaminate for being run. This simply requests the dependency and then marks it as executable. */
abstract class SetupIlluaminate : DefaultTask() {
@get:Input
abstract val file: Property<String>
@TaskAction
fun setExecutable() {
val file = File(this.file.get())
if (file.canExecute()) {
didWork = false
return
}
file.setExecutable(true)
}
companion object {
const val NAME: String = "setupIlluaminate"
}
}
abstract class IlluaminateExec : AbstractExecTask<IlluaminateExec>(IlluaminateExec::class.java) {
init {
dependsOn(SetupIlluaminate.NAME)
executable = illuaminatePath
}
}
abstract class IlluaminateExecToDir : ExecToDir() {
init {
dependsOn(SetupIlluaminate.NAME)
executable = illuaminatePath
}
}

View File

@ -1,16 +0,0 @@
#!/usr/bin/env sh
set -e
test -d bin || mkdir bin
test -f bin/illuaminate || curl -s -obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
chmod +x bin/illuaminate
if [ -n ${GITHUB_ACTIONS+x} ]; then
# Register a problem matcher (see https://github.com/actions/toolkit/blob/master/docs/problem-matchers.md)
# for illuaminate.
echo "::add-matcher::.github/matchers/illuaminate.json"
trap 'echo "::remove-matcher owner=illuaminate::"' EXIT
fi
./gradlew luaJavadoc
bin/illuaminate lint

View File

@ -185,7 +185,7 @@ end
:::note Confused? :::note Confused?
Don't worry if you don't understand this example. It's quite advanced, and does use some ideas that this guide doesn't Don't worry if you don't understand this example. It's quite advanced, and does use some ideas that this guide doesn't
cover. That said, don't be afraid to ask on [Discord] or [IRC] either! cover. That said, don't be afraid to ask on [GitHub Discussions] or [IRC] either!
::: :::
It's worth noting that the examples of audio processing we've mentioned here are about manipulating the _amplitude_ of It's worth noting that the examples of audio processing we've mentioned here are about manipulating the _amplitude_ of
@ -200,6 +200,5 @@ This is, I'm afraid, left as an exercise to the reader.
[PCM]: https://en.wikipedia.org/wiki/Pulse-code_modulation "Pulse-code Modulation - Wikipedia" [PCM]: https://en.wikipedia.org/wiki/Pulse-code_modulation "Pulse-code Modulation - Wikipedia"
[Ring Buffer]: https://en.wikipedia.org/wiki/Circular_buffer "Circular buffer - Wikipedia" [Ring Buffer]: https://en.wikipedia.org/wiki/Circular_buffer "Circular buffer - Wikipedia"
[Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia" [Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia"
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[Discord]: https://discord.computercraft.cc "The Minecraft Computer Mods Discord" [IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
[IRC]: http://webchat.esper.net/?channels=computercraft "IRC webchat on EsperNet"

View File

@ -37,8 +37,7 @@ little daunting getting started. Thankfully, there's several fantastic tutorials
Once you're a little more familiar with the mod, the sidebar and links below provide more detailed documentation on the Once you're a little more familiar with the mod, the sidebar and links below provide more detailed documentation on the
various APIs and peripherals provided by the mod. various APIs and peripherals provided by the mod.
If you get stuck, do pop in to the [Minecraft Computer Mod Discord guild][discord] or ComputerCraft's If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC].
[IRC channel][irc].
## Get Involved ## Get Involved
CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug]. CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug].
@ -51,5 +50,5 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge." [forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
[ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge" [ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge"
[lua]: https://www.lua.org/ "Lua's main website" [lua]: https://www.lua.org/ "Lua's main website"
[discord]: https://discord.computercraft.cc "The Minecraft Computer Mods Discord" [GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
[irc]: http://webchat.esper.net/?channels=computercraft "IRC webchat on EsperNet" [IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"

View File

@ -14,7 +14,7 @@ thread, not the whole program.
:::tip :::tip
Because sleep internally uses timers, it is a function that yields. This means Because sleep internally uses timers, it is a function that yields. This means
that you can use it to prevent "Too long without yielding" errors, however, as that you can use it to prevent "Too long without yielding" errors. However, as
the minimum sleep time is 0.05 seconds, it will slow your program down. the minimum sleep time is 0.05 seconds, it will slow your program down.
::: :::

View File

@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx3G
kotlin.stdlib.default.dependency=false kotlin.stdlib.default.dependency=false
# Mod properties # Mod properties
mod_version=1.100.9 mod_version=1.100.10
# Minecraft properties (update mods.toml when changing) # Minecraft properties (update mods.toml when changing)
mc_version=1.18.2 mc_version=1.18.2

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -24,7 +24,7 @@ final class LuaDateTime
{ {
} }
static void format( DateTimeFormatterBuilder formatter, String format, ZoneOffset offset ) throws LuaException static void format( DateTimeFormatterBuilder formatter, String format ) throws LuaException
{ {
for( int i = 0; i < format.length(); ) for( int i = 0; i < format.length(); )
{ {
@ -61,7 +61,7 @@ final class LuaDateTime
formatter.appendText( ChronoField.MONTH_OF_YEAR, TextStyle.FULL ); formatter.appendText( ChronoField.MONTH_OF_YEAR, TextStyle.FULL );
break; break;
case 'c': case 'c':
format( formatter, "%a %b %e %H:%M:%S %Y", offset ); format( formatter, "%a %b %e %H:%M:%S %Y" );
break; break;
case 'C': case 'C':
formatter.appendValueReduced( CENTURY, 2, 2, 0 ); formatter.appendValueReduced( CENTURY, 2, 2, 0 );
@ -71,13 +71,13 @@ final class LuaDateTime
break; break;
case 'D': case 'D':
case 'x': case 'x':
format( formatter, "%m/%d/%y", offset ); format( formatter, "%m/%d/%y" );
break; break;
case 'e': case 'e':
formatter.padNext( 2 ).appendValue( ChronoField.DAY_OF_MONTH ); formatter.padNext( 2 ).appendValue( ChronoField.DAY_OF_MONTH );
break; break;
case 'F': case 'F':
format( formatter, "%Y-%m-%d", offset ); format( formatter, "%Y-%m-%d" );
break; break;
case 'g': case 'g':
formatter.appendValueReduced( IsoFields.WEEK_BASED_YEAR, 2, 2, 0 ); formatter.appendValueReduced( IsoFields.WEEK_BASED_YEAR, 2, 2, 0 );
@ -107,10 +107,10 @@ final class LuaDateTime
formatter.appendText( ChronoField.AMPM_OF_DAY ); formatter.appendText( ChronoField.AMPM_OF_DAY );
break; break;
case 'r': case 'r':
format( formatter, "%I:%M:%S %p", offset ); format( formatter, "%I:%M:%S %p" );
break; break;
case 'R': case 'R':
format( formatter, "%H:%M", offset ); format( formatter, "%H:%M" );
break; break;
case 'S': case 'S':
formatter.appendValue( ChronoField.SECOND_OF_MINUTE, 2 ); formatter.appendValue( ChronoField.SECOND_OF_MINUTE, 2 );
@ -120,7 +120,7 @@ final class LuaDateTime
break; break;
case 'T': case 'T':
case 'X': case 'X':
format( formatter, "%H:%M:%S", offset ); format( formatter, "%H:%M:%S" );
break; break;
case 'u': case 'u':
formatter.appendValue( ChronoField.DAY_OF_WEEK ); formatter.appendValue( ChronoField.DAY_OF_WEEK );
@ -212,15 +212,13 @@ final class LuaDateTime
throw new LuaException( "field \"" + field + "\" missing in date table" ); throw new LuaException( "field \"" + field + "\" missing in date table" );
} }
private static final TemporalField CENTURY = map( ChronoField.YEAR, ValueRange.of( 0, 6 ), x -> (x / 100) % 100 ); private static final TemporalField CENTURY = map( ChronoField.YEAR, ValueRange.of( 0, 99 ), x -> (x / 100) % 100 );
private static final TemporalField ZERO_WEEK = map( WeekFields.SUNDAY_START.dayOfWeek(), ValueRange.of( 0, 6 ), x -> x - 1 ); private static final TemporalField ZERO_WEEK = map( WeekFields.SUNDAY_START.dayOfWeek(), ValueRange.of( 0, 6 ), x -> x - 1 );
private static TemporalField map( TemporalField field, ValueRange range, LongUnaryOperator convert ) private static TemporalField map( TemporalField field, ValueRange range, LongUnaryOperator convert )
{ {
return new TemporalField() return new TemporalField()
{ {
private final ValueRange range = ValueRange.of( 0, 99 );
@Override @Override
public TemporalUnit getBaseUnit() public TemporalUnit getBaseUnit()
{ {

View File

@ -493,7 +493,7 @@ public class OSAPI implements ILuaAPI
if( format.equals( "*t" ) ) return LuaDateTime.toTable( date, offset, instant ); if( format.equals( "*t" ) ) return LuaDateTime.toTable( date, offset, instant );
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder(); DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
LuaDateTime.format( formatter, format, offset ); LuaDateTime.format( formatter, format );
return formatter.toFormatter( Locale.ROOT ).format( date ); return formatter.toFormatter( Locale.ROOT ).format( date );
} }

View File

@ -323,8 +323,9 @@ public class TurtleBrain implements ITurtleAccess
try try
{ {
// Create a new turtle // We use Block.UPDATE_CLIENTS here to ensure that neighbour updates caused in Block.updateNeighbourShapes
if( world.setBlock( pos, newState, 0 ) ) // are sent to the client. We want to avoid doing a full block update until the turtle state is copied over.
if( world.setBlock( pos, newState, 2 ) )
{ {
Block block = world.getBlockState( pos ).getBlock(); Block block = world.getBlockState( pos ).getBlock();
if( block == oldBlock.getBlock() ) if( block == oldBlock.getBlock() )

View File

@ -259,7 +259,7 @@ input should the whole output not fit on the display.
local rows = {} local rows = {}
for i = 1, 30 do rows[i] = {("Row #%d"):format(i), math.random(1, 400)} end for i = 1, 30 do rows[i] = {("Row #%d"):format(i), math.random(1, 400)} end
textutils.tabulate(colors.orange, {"Column", "Value"}, colors.lightBlue, table.unpack(rows)) textutils.pagedTabulate(colors.orange, {"Column", "Value"}, colors.lightBlue, table.unpack(rows))
]] ]]
function pagedTabulate(...) function pagedTabulate(...)
return tabulateCommon(true, ...) return tabulateCommon(true, ...)
@ -749,9 +749,9 @@ suitable for pretty printing.
@usage Demonstrates some of the other options @usage Demonstrates some of the other options
local tbl = { 1, 2, 3 } local tbl = { 1, 2, 3 }
print(textutils.serialize({ tbl, tbl }, { allow_repetitions = true })) print(textutils.serialise({ tbl, tbl }, { allow_repetitions = true }))
print(textutils.serialize(tbl, { compact = true })) print(textutils.serialise(tbl, { compact = true }))
]] ]]
function serialize(t, opts) function serialize(t, opts)
local tTracking = {} local tTracking = {}
@ -770,7 +770,7 @@ serialise = serialize -- GB version
--- Converts a serialised string back into a reassembled Lua object. --- Converts a serialised string back into a reassembled Lua object.
-- --
-- This is mainly used together with @{textutils.serialize}. -- This is mainly used together with @{textutils.serialise}.
-- --
-- @tparam string s The serialised string to deserialise. -- @tparam string s The serialised string to deserialise.
-- @return[1] The deserialised object -- @return[1] The deserialised object
@ -807,10 +807,10 @@ unserialise = unserialize -- GB version
-- @throws If the object contains a value which cannot be -- @throws If the object contains a value which cannot be
-- serialised. This includes functions and tables which appear multiple -- serialised. This includes functions and tables which appear multiple
-- times. -- times.
-- @usage textutils.serializeJSON({ values = { 1, "2", true } }) -- @usage textutils.serialiseJSON({ values = { 1, "2", true } })
-- @since 1.7 -- @since 1.7
-- @see textutils.json_null Use to serialize a JSON `null` value. -- @see textutils.json_null Use to serialise a JSON `null` value.
-- @see textutils.empty_json_array Use to serialize a JSON empty array. -- @see textutils.empty_json_array Use to serialise a JSON empty array.
function serializeJSON(t, bNBTStyle) function serializeJSON(t, bNBTStyle)
expect(1, t, "table", "string", "number", "boolean") expect(1, t, "table", "string", "number", "boolean")
expect(2, bNBTStyle, "boolean", "nil") expect(2, bNBTStyle, "boolean", "nil")

View File

@ -1,3 +1,14 @@
# New features in CC: Tweaked 1.100.10
* Mention WAV support in speaker help (MCJack123).
* Add http programs to the path, even when http is not enabled.
Several bug fixes:
* Fix example in textutils.pagedTabulate docs (IvoLeal72).
* Fix help program treating the terminal one line longer than it was.
* Send block updates to client when turtle moves (roland-a).
* Resolve several monitor issues when running Occulus shaders.
# New features in CC: Tweaked 1.100.9 # New features in CC: Tweaked 1.100.9
* Add documentation for setting up GPS (Lupus590). * Add documentation for setting up GPS (Lupus590).

View File

@ -1,5 +1,9 @@
The speaker program plays audio files using speakers attached to this computer. The speaker program plays audio files using speakers attached to this computer.
It supports audio files in a limited number of formats:
* DFPWM: You can convert music to DFPWM with external tools like https://music.madefor.cc.
* WAV: WAV files must be 8-bit PCM or DFPWM, with exactly one channel and a sample rate of 48kHz.
## Examples: ## Examples:
- `speaker play example.dfpwm left` plays the "example.dfpwm" audio file using the speaker on the left of the computer. * `speaker play example.dfpwm left` plays the "example.dfpwm" audio file using the speaker on the left of the computer.
- `speaker stop` stops any currently playing audio. * `speaker stop` stops any currently playing audio.

View File

@ -1,15 +1,12 @@
New features in CC: Tweaked 1.100.9 New features in CC: Tweaked 1.100.10
* Add documentation for setting up GPS (Lupus590). * Mention WAV support in speaker help (MCJack123).
* Add WAV support to the `speaker` program (MCJack123). * Add http programs to the path, even when http is not enabled.
* Expose item groups in `getItemDetail` (itisluiz).
* Other fixes to documentation (Erb3, JohnnyIrvin).
* Add Norwegian translation (Erb3).
Several bug fixes: Several bug fixes:
* Fix z-fighting on bold printout borders (toad-dev). * Fix example in textutils.pagedTabulate docs (IvoLeal72).
* Fix `term.blit` failing on certain strings. * Fix help program treating the terminal one line longer than it was.
* Fix `getItemLimit()` using the wrong slot (heap-underflow). * Send block updates to client when turtle moves (roland-a).
* Increase size of monitor depth blocker. * Resolve several monitor issues when running Occulus shaders.
Type "help changelog" to see the full version history. Type "help changelog" to see the full version history.

View File

@ -146,14 +146,17 @@ end
local contents = file:read("*a") local contents = file:read("*a")
file:close() file:close()
-- Trim trailing newlines from the file to avoid displaying a blank line.
if contents:sub(-1) == "\n" then contents:sub(1, -2) end
local word_wrap = sFile:sub(-3) == ".md" and word_wrap_markdown or word_wrap_basic local word_wrap = sFile:sub(-3) == ".md" and word_wrap_markdown or word_wrap_basic
local width, height = term.getSize() local width, height = term.getSize()
local content_height = height - 1 -- Height of the content box.
local lines, fg, bg, sections = word_wrap(contents, width) local lines, fg, bg, sections = word_wrap(contents, width)
local print_height = #lines local print_height = #lines
-- If we fit within the screen, just display without pagination. -- If we fit within the screen, just display without pagination.
if print_height <= height then if print_height <= content_height then
local _, y = term.getCursorPos() local _, y = term.getCursorPos()
for i = 1, print_height do for i = 1, print_height do
if y + i - 1 > height then if y + i - 1 > height then
@ -201,7 +204,7 @@ end
local function draw() local function draw()
for y = 1, height - 1 do for y = 1, content_height do
term.setCursorPos(1, y) term.setCursorPos(1, y)
if y + offset > print_height then if y + offset > print_height then
-- Should only happen if we resize the terminal to a larger one -- Should only happen if we resize the terminal to a larger one
@ -228,14 +231,14 @@ while true do
if param == keys.up and offset > 0 then if param == keys.up and offset > 0 then
offset = offset - 1 offset = offset - 1
draw() draw()
elseif param == keys.down and offset < print_height - height then elseif param == keys.down and offset < print_height - content_height then
offset = offset + 1 offset = offset + 1
draw() draw()
elseif param == keys.pageUp and offset > 0 then elseif param == keys.pageUp and offset > 0 then
offset = math.max(offset - height + 2, 0) offset = math.max(offset - content_height + 1, 0)
draw() draw()
elseif param == keys.pageDown and offset < print_height - height then elseif param == keys.pageDown and offset < print_height - content_height then
offset = math.min(offset + height - 2, print_height - height) offset = math.min(offset + content_height - 1, print_height - content_height)
draw() draw()
elseif param == keys.home then elseif param == keys.home then
offset = 0 offset = 0
@ -247,7 +250,7 @@ while true do
offset = sections[current_section + 1].offset offset = sections[current_section + 1].offset
draw() draw()
elseif param == keys["end"] then elseif param == keys["end"] then
offset = print_height - height offset = print_height - content_height
draw() draw()
elseif param == keys.q then elseif param == keys.q then
sleep(0) -- Super janky, but consumes stray "char" events. sleep(0) -- Super janky, but consumes stray "char" events.
@ -257,7 +260,7 @@ while true do
if param < 0 and offset > 0 then if param < 0 and offset > 0 then
offset = offset - 1 offset = offset - 1
draw() draw()
elseif param > 0 and offset < print_height - height then elseif param > 0 and offset <= print_height - content_height then
offset = offset + 1 offset = offset + 1
draw() draw()
end end
@ -270,7 +273,8 @@ while true do
end end
width, height = new_width, new_height width, height = new_width, new_height
offset = math.max(math.min(offset, print_height - height), 0) content_height = height - 1
offset = math.max(math.min(offset, print_height - content_height), 0)
draw() draw()
draw_menu() draw_menu()
elseif event == "terminate" then elseif event == "terminate" then

View File

@ -13,8 +13,8 @@ if #tArgs < 2 then
end end
if not http then if not http then
printError("Pastebin requires the http API") printError("Pastebin requires the http API, but it is not enabled")
printError("Set http.enabled to true in CC: Tweaked's config") printError("Set http.enabled to true in CC: Tweaked's server config")
return return
end end

View File

@ -21,8 +21,8 @@ end
local url = table.remove(tArgs, 1) local url = table.remove(tArgs, 1)
if not http then if not http then
printError("wget requires the http API") printError("wget requires the http API, but it is not enabled")
printError("Set http.enabled to true in CC: Tweaked's config") printError("Set http.enabled to true in CC: Tweaked's server config")
return return
end end

View File

@ -1,7 +1,7 @@
local completion = require "cc.shell.completion" local completion = require "cc.shell.completion"
-- Setup paths -- Setup paths
local sPath = ".:/rom/programs" local sPath = ".:/rom/programs:/rom/programs/http"
if term.isColor() then if term.isColor() then
sPath = sPath .. ":/rom/programs/advanced" sPath = sPath .. ":/rom/programs/advanced"
end end
@ -19,9 +19,6 @@ end
if commands then if commands then
sPath = sPath .. ":/rom/programs/command" sPath = sPath .. ":/rom/programs/command"
end end
if http then
sPath = sPath .. ":/rom/programs/http"
end
shell.setPath(sPath) shell.setPath(sPath)
help.setPath("/rom/help") help.setPath("/rom/help")

View File

@ -1,8 +1,46 @@
local capture = require "test_helpers".capture_program local capture = require "test_helpers".capture_program
local with_window_lines = require "test_helpers".with_window_lines
describe("The help program", function() describe("The help program", function()
local function stub_help(content)
local name = "/help_file.txt"
io.open(name, "wb"):write(content):close()
stub(help, "lookup", function() return name end)
end
local function capture_help(width, height, content)
stub_help(content)
local co = coroutine.create(shell.run)
local window = with_window_lines(width, height, function()
local ok, err = coroutine.resume(co, "help topic")
if not ok then error(err, 0) end
end)
return coroutine.status(co) == "dead", window
end
it("errors when there is no such help file", function() it("errors when there is no such help file", function()
expect(capture(stub, "help nothing")) expect(capture(stub, "help nothing"))
:matches { ok = true, error = "No help available\n", output = "" } :matches { ok = true, error = "No help available\n", output = "" }
end) end)
it("prints a short file directly", function()
local dead, output = capture_help(10, 3, "a short\nfile")
expect(dead):eq(true)
expect(output):same {
"a short ",
"file ",
" ",
}
end)
it("launches the viewer for a longer file", function()
local dead, output = capture_help(10, 3, "a longer\nfile\nwith content")
expect(dead):eq(false)
expect(output):same {
"a longer ",
"file ",
"Help: topi",
}
end)
end) end)

View File

@ -56,7 +56,22 @@ local function with_window(width, height, fn)
return redirect return redirect
end end
--- Run a function redirecting to a new window with the given dimensions,
-- returning the content of the window.
--
-- @tparam number width The window's width
-- @tparam number height The window's height
-- @tparam function() fn The action to run
-- @treturn {string...} The content of the window.
local function with_window_lines(width, height, fn)
local window = with_window(width, height, fn)
local out = {}
for i = 1, height do out[i] = window.getLine(i) end
return out
end
return { return {
capture_program = capture_program, capture_program = capture_program,
with_window = with_window, with_window = with_window,
with_window_lines = with_window_lines,
} }

View File

@ -1,3 +1,6 @@
:root {
--nav-width: 250px;
}
/* Some misc styles */ /* Some misc styles */
.big-image { .big-image {