1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-15 14:07:38 +00:00

Compare commits

..

11 Commits

Author SHA1 Message Date
Jonathan Coates
1f3b781a1d Merge branch 'mc-1.16.x' into mc-1.18.x 2023-02-14 10:10:50 +00:00
Jonathan Coates
d7305fb975 Bump CC:T to 1.101.2
Yes, it was the shiny parse errors, and not the steady accumulation of
bugs which made me finally do a version bump.
2023-02-14 10:00:24 +00:00
Jonathan Coates
9b3cadf57c Cherry pick several changes back from 1.19.3
The main purpose of this is to backport the improved parse/runtime
errors to older versions. I think they're sufficiently useful that we
should try to make it as widely available as possible.

We've been running them for a week now on SC3 and the released version
and not seen any issues, so I think it's probably stable enough.

This is a pretty lazy commit: I ended up copying the whole ROM over and
then picking up a few other related changes along the way.

 - Trim spaces from file paths (b8fce1eecc)

 - Correctly format 12AM/PM with
   %I (9f48395596)

 - Fix http.request and htpt.websocketAsync not handling a few failure
   edge-cases correctly (3b42f22a4f).

 - Move the internal modules into the main package path, hidden under
   cc.internal (34a31abd9c).

 - Gather code coverage in Java instead of
   Lua (28a55349a9).

 - Make error messages in edit more
   obvious (8cfbfe7ceb).

 - Make mcfly's test methods global. This means we don't need to pass
   stub everywhere (7335a892b5).

 - Improve runtime and parse errors. This comes from numerous commits,
   but chiefly a12b405acf, and
   5502412181.

 - Hide the internal redirect methods in
   multishell (33b6f38339).

Note this does /not/ include the shebang changes (sorry Emma!). I've
tried to avoid adding any user-controllable features, mostly because I
don't know how to handle the versioning otherwise :).
2023-02-14 09:45:03 +00:00
Jonathan Coates
68f6fa9343 Fall back to the default item when rendering non-turtles
Closes #1328. This isn't an actual fix - I have no clue what's going on
there - but it should be less crashy.
2023-02-09 12:45:20 +00:00
Jonathan Coates
58f2c0bd71 Update Cobalt to fix yielding inside __len
Closes #1307
2023-01-26 10:09:09 +00:00
Jonathan Coates
b46ad62424 Send the original rednet message to the current computer
We were incorrectly enquing the modem payload, not the underlying rednet
message.

Closes #1308.
2023-01-21 08:25:13 +00:00
Jonathan Coates
12f2f854a6 Include the licences of our dependencies in the credits
I feel like we should have been doing this from the beginning. Love to
uncompliant for 11 years :/.
2023-01-07 12:04:56 +00:00
Jonathan Coates
4078a2dcba Clamp speaker volume again
Looks like this was removed in b048b6666d.
2023-01-01 13:59:58 +00:00
Jonathan Coates
0ad12eeab6 Fix computer upgrade recipes
Fixes #1272
2022-12-26 09:32:02 +00:00
Jonathan Coates
68a5081740 Bump Cobalt version
See #1248 and #1249
2022-12-21 16:01:54 +00:00
Jonathan Coates
5e701f73d6 Use the correct import path in import.lua
Backported from 1.19.3
2022-12-14 21:52:32 +00:00
3120 changed files with 76264 additions and 81940 deletions

View File

@@ -8,17 +8,6 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
ij_continuation_indent_size = 4
ij_any_do_while_brace_force = if_multiline
ij_any_if_brace_force = if_multiline
ij_any_for_brace_force = if_multiline
ij_any_spaces_within_array_initializer_braces = true
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_method_parameters_wrap = off
ij_kotlin_call_parameters_wrap = off
[*.md]
trim_trailing_whitespace = false

View File

@@ -1,2 +0,0 @@
# Reformat everything
f478c4ffc4fb9fc2200ec9b0bc751d047057ce81

4
.gitattributes vendored
View File

@@ -1,6 +1,6 @@
# Ignore changes in generated files
projects/*/src/generated/** linguist-generated
projects/common/src/testMod/resources/data/cctest/structures/* linguist-generated
src/generated/** linguist-generated
src/testMod/server-files/structures linguist-generated
* text=auto

View File

@@ -33,25 +33,14 @@ jobs:
./gradlew downloadAssets || ./gradlew downloadAssets
./gradlew build
- name: Run client tests
run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
# These are a little flaky on GH actions: its useful to run them, but don't break the build.
continue-on-error: true
- name: Prepare Jars
run: |
# Find the main jar and append the git hash onto it.
mkdir -p jars
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
- name: Upload Jar
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v2
with:
name: CC-Tweaked
path: ./jars
path: build/libs
- name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v2
- name: Parse test reports
run: ./tools/parse-reports.py
@@ -59,40 +48,3 @@ jobs:
- name: Run linters
uses: pre-commit/action@v3.0.0
build-core:
strategy:
fail-fast: false
matrix:
include:
- name: Windows
uses: windows-latest
- name: macOS
uses: macos-latest
name: Test on ${{ matrix.name }}
runs-on: ${{ matrix.uses }}
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
with:
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
- name: Run tests
run: |
./gradlew --configure-on-demand :core:test
- name: Parse test reports
run: python3 ./tools/parse-reports.py
if: ${{ failure() }}

View File

@@ -12,8 +12,8 @@ chmod 600 "$HOME/.ssh/key"
# And upload
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
"$GITHUB_WORKSPACE/projects/web/build/site/" \
"$GITHUB_WORKSPACE/build/docs/site/" \
"$SSH_USER@$SSH_HOST:/$DEST"
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
"$GITHUB_WORKSPACE/projects/common-api/build/docs/javadoc/" \
"$GITHUB_WORKSPACE/build/docs/javadoc/" \
"$SSH_USER@$SSH_HOST:/$DEST/javadoc"

View File

@@ -3,7 +3,7 @@ name: Build documentation
on:
push:
branches:
- mc-1.19.x
- mc-1.16.x
jobs:
make_doc:
@@ -29,7 +29,7 @@ jobs:
run: ./gradlew compileJava --no-daemon || ./gradlew compileJava --no-daemon
- name: Generate documentation
run: ./gradlew docWebsite :common-api:javadoc --no-daemon
run: ./gradlew docWebsite javadoc --no-daemon
- name: Upload documentation
run: .github/workflows/make-doc.sh 2> /dev/null

10
.gitignore vendored
View File

@@ -2,17 +2,15 @@
/classes
/logs
/build
/projects/*/logs
/projects/*/build
/buildSrc/build
/out
/doc/out/
/node_modules
.jqwik-database
/.jqwik-database
# Runtime directories
/run
/projects/*/run
/run-*
*.ipr
*.iws
@@ -25,6 +23,8 @@
/.project
/.settings
/.vscode
bin/
*.launch
/projects/*/src/generated/resources/.cache
/src/generated/resources/.cache
/src/web/mount/*.d.ts

View File

@@ -48,7 +48,9 @@ repos:
exclude: |
(?x)^(
projects/[a-z]+/src/generated|
projects/core/src/test/resources/test-rom/data/json-parsing/|
src/generated|
src/test/resources/test-rom/data/json-parsing/|
src/testMod/server-files/|
config/idea/|
.*\.dfpwm
)

View File

@@ -4,96 +4,96 @@ provides an introduction as to how to get started in helping out.
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
## Table of Contents
- [Reporting issues](#reporting-issues)
- [Translations](#translations)
- [Setting up a development environment](#setting-up-a-development-environment)
- [Developing CC: Tweaked](#developing-cc-tweaked)
- [Writing documentation](#writing-documentation)
## Reporting issues
If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so, do
use the issue templates - they provide a useful hint on what information to provide.
If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so,
do use the issue templates - they provide a useful hint on what information to provide.
## Translations
Translations are managed through [Weblate], an online interface for managing language strings. This is synced
automatically with GitHub, so please don't submit PRs adding/changing translations!
## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
## Developing
In order to develop CC: Tweaked, you'll need to download the source code and then run it. This is a pretty simple
process. When building on Windows, Use `gradlew.bat` instead of `./gradlew`.
- Make sure you've got the following software instealled:
- Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
- [Git](https://git-scm.com/).
- If you want to work on documentation, [NodeJS][node].
- **Clone the repository:** `git clone https://github.com/cc-tweaked/CC-Tweaked.git && cd CC-Tweaked`
- **Setup Forge:** `./gradlew build`
- **Run Minecraft:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
- **Optionally:** For small PRs (especially those only touching Lua code), it may be easier to use GitPod, which
provides a pre-configured environment: [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-2b2b2b?logo=gitpod)](https://gitpod.io/#https://github.com/cc-tweaked/CC-Tweaked/)
- Download CC: Tweaked's source code:
```
git clone https://github.com/cc-tweaked/CC-Tweaked.git
cd CC-Tweaked
```
Do note you will need to download the mod after compiling to test.
- Build CC: Tweaked with `./gradlew build`. This will be very slow the first time it runs, as it needs to download a
lot of dependencies (and decompile Minecraft several times). Subsequent runs should be much faster!
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
These commands may take a few minutes to run the first time, as the environment is set up, but should be much faster
afterwards.
- You're now ready to start developing CC: Tweaked. Running `./gradlew :forge:runClient` or
`./gradle :fabric:runClient` will start Minecraft under Forge and Fabric respectively.
The following sections describe the more niche sections of CC: Tweaked's build system. Some bits of these are
quite-complex, and (dare I say) over-engineered, so you may wish to ignore them. Well tested/documented PRs are always
preferred (and I'd definitely recommend setting up the tooling if you're doing serious development work), but for
small changes it can be a lot.
If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble` and copy the `.jar` from
`projects/forge/build/libs` (for Forge) or `projects/fabric/build/libs` (for Fabric).
### Code linters
CC: Tweaked uses a couple of "linters" on its source code, to enforce a consistent style across the project. While these
are run whenever you submit a PR, it's often useful to run this before committing.
## Developing CC: Tweaked
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
looking to make your changes. As always, if you're not sure [do ask the community][community]!
- **[Checkstyle]:** Checks Java code to ensure it is consistently formatted. This can be run with `./gradlew build` or
`./gradle check`.
- **[illuaminate]:** Checks Lua code for semantic and styleistic issues. This can be run with `./gradlew lintLua`.
### Testing
When making larger changes, it's may be useful to write a test to make sure your code works as expected.
CC: Tweaked has several test suites, each designed to test something different:
- In order to test CraftOS and its builtin APIs, we have a test suite written in Lua located at
`projects/core/src/test/resources/test-rom/`. These don't rely on any Minecraft code, which means they can run on
emulators, acting as a sort of compliance test.
These tests are written using a test system called "mcfly", heavily inspired by [busted]. Groups of tests go inside
`describe` blocks, and a single test goes inside `it`. Assertions are generally written using `expect` (inspired by
Hamcrest and the like). For instance, `expect(foo):eq("bar")` asserts that your variable `foo` is equal to the
expected value `"bar"`.
These tests can be run with `./gradlew :core:test`.
- In-game functionality, such as the behaviour of blocks and items, is tested using [Minecraft's gametest
system][mc-test] (`projects/common/src/testMod`). These tests spin up a server, spawn a structure for each test, and
then run some code on the blocks defined in that structure.
These tests can be run with `./gradlew runGametest` (or `./gradle :forge:runGametest`/`./gradlew :fabric:runGametest`
for a single loader).
For more information, [see the architecture document][architecture].
## Writing documentation
### 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.
You'll first need to [set up a development environment as above](#setting-up-a-development-environment).
Our documentation generation pipeline is rather complex, and involves invoking several external tools. Most of this
complexity is hidden by Gradle, but you will need to perform some initial setup:
Once this is set up, you can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code,
writing the resulting HTML into `./projects/web/build/site`, which can then be opened in a browser. When iterating on
documentation, you can instead run `./gradlew docWebsite -t`, which will rebuild documentation every time you change a
file.
- Install [Node/npm][node].
- Run `npm ci` to install our Node dependencies.
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
markdown features - if you can, do check what the documentation looks like locally!
You can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code, writing the resulting
HTML into `./build/docs/site`.
When writing long-form documentation (such as the guides in [doc/guides](doc/guides)), I find it useful to tell a
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept and then talk
about how you can build on that, until you've covered everything!
#### Writing documentation
illuaminate's documentation system is not currently documented (somewhat ironic), but is _largely_ the same as
[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
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
Thankfully running tests is much simpler than running the documentation generator! `./gradlew check` will run the
entire test suite (and some additional bits of verification).
Before we get into writing tests, it's worth mentioning the various test suites that CC: Tweaked has:
- "Core" Java (`./src/test/java`): These test core bits of the mod which don't require any Minecraft interaction.
This includes the `@LuaFunction` system, file system code, etc...
These tests are run by `./gradlew test`.
- CraftOS (`./src/test/resources/test-rom/`): These tests are written in Lua, and ensure the Lua environment, libraries
and programs work as expected. These are (generally) written to be able to be run on emulators too, to provide some
sort of compliance 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, using
the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals.
These tests are run with `./gradlew runGametest`.
## CraftOS tests
CraftOS's tests are written using a test system called "mcfly", heavily inspired by [busted] (and thus RSpec). Groups of
tests go inside `describe` blocks, and a single test goes inside `it`.
Assertions are generally written using `expect` (inspired by Hamcrest and the like). For instance, `expect(foo):eq("bar")`
asserts that your variable `foo` is equal to the expected value `"bar"`.
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
[community]: README.md#community "Get in touch with the community."
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
[community]: README.md#Community "Get in touch with the community."
[checkstyle]: https://checkstyle.org/
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
@@ -102,4 +102,3 @@ about how you can build on that, until you've covered everything!
[mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg
[busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing."
[node]: https://nodejs.org/en/ "Node.js"
[architecture]: projects/ARCHITECTURE.md

View File

@@ -26,26 +26,16 @@ on is present.
```groovy
repositories {
maven {
url "https://squiddev.cc/maven/"
url 'https://squiddev.cc/maven/'
content {
includeGroup("cc.tweaked")
includeModule("org.squiddev", "Cobalt")
includeModule("fuzs.forgeconfigapiport", "forgeconfigapiport-fabric")
includeGroup 'org.squiddev'
}
}
}
dependencies {
// Vanilla (i.e. for multi-loader systems)
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
// Forge Gradle
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
// Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")
modRuntimeOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric:$cctVersion")
compileOnly fg.deobf("org.squiddev:cc-tweaked-${mc_version}:${cct_version}:api")
runtimeOnly fg.deobf("org.squiddev:cc-tweaked-${mc_version}:${cct_version}")
}
```

View File

@@ -1,18 +1,400 @@
import org.jetbrains.gradle.ext.compiler
import org.jetbrains.gradle.ext.settings
import cc.tweaked.gradle.*
import net.darkhax.curseforgegradle.TaskPublishCurseForge
import net.minecraftforge.gradle.common.util.RunConfig
plugins {
publishing
alias(libs.plugins.taskTree)
// Build
alias(libs.plugins.forgeGradle)
alias(libs.plugins.mixinGradle)
alias(libs.plugins.librarian)
alias(libs.plugins.shadow)
// Publishing
`maven-publish`
alias(libs.plugins.curseForgeGradle)
alias(libs.plugins.githubRelease)
id("org.jetbrains.gradle.plugin.idea-ext")
alias(libs.plugins.minotaur)
// Utility
alias(libs.plugins.taskTree)
id("cc-tweaked.illuaminate")
id("cc-tweaked.node")
id("cc-tweaked.gametest")
id("cc-tweaked")
}
val isUnstable = project.properties["isUnstable"] == "true"
val isStable = true
val modVersion: String by extra
val mcVersion: String by extra
group = "org.squiddev"
version = modVersion
base.archivesName.set("cc-tweaked-$mcVersion")
java.registerFeature("extraMods") { usingSourceSet(sourceSets.main.get()) }
sourceSets {
main {
resources.srcDir("src/generated/resources")
}
}
minecraft {
runs {
// configureEach would be better, but we need to eagerly configure configs or otherwise the run task doesn't
// get set up properly.
all {
lazyToken("minecraft_classpath") {
configurations["shade"].copyRecursive().resolve().joinToString(File.pathSeparator) { it.absolutePath }
}
property("forge.logging.markers", "REGISTRIES")
property("forge.logging.console.level", "debug")
forceExit = false
mods.register("computercraft") { source(sourceSets.main.get()) }
}
val client by registering {
workingDirectory(file("run"))
}
val server by registering {
workingDirectory(file("run/server"))
arg("--nogui")
}
val data by registering {
workingDirectory(file("run"))
args(
"--mod",
"computercraft",
"--all",
"--output",
file("src/generated/resources/"),
"--existing",
file("src/main/resources/"),
)
property("cct.pretty-json", "true")
}
fun RunConfig.configureForGameTest() {
val old = lazyTokens.get("minecraft_classpath")
lazyToken("minecraft_classpath") {
// We do some terrible hacks here to basically find all things not already on the runtime classpath
// and add them. /Except/ for our source sets, as those need to load inside the Minecraft classpath.
val testMod = configurations["testModRuntimeClasspath"].resolve()
val implementation = configurations.runtimeClasspath.get().resolve()
val new = (testMod - implementation)
.asSequence()
.filter { it.isFile && !it.name.endsWith("-test-fixtures.jar") }
.map { it.absolutePath }
.joinToString(File.pathSeparator)
if (old == null) new else old.get() + File.pathSeparator + new
}
property("cctest.sources", file("src/testMod/resources/data/cctest").absolutePath)
arg("--mixin.config=computercraft-gametest.mixins.json")
mods.register("cctest") {
source(sourceSets["testMod"])
source(sourceSets["testFixtures"])
}
}
val testClient by registering {
workingDirectory(file("run/testClient"))
parent(client.get())
configureForGameTest()
}
val gameTestServer by registering {
workingDirectory(file("run/testServer"))
configureForGameTest()
property("forge.logging.console.level", "info")
}
}
mappings("parchment", "${libs.versions.parchmentMc.get()}-${libs.versions.parchment.get()}-$mcVersion")
accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
}
mixin {
add(sourceSets.main.get(), "computercraft.mixins.refmap.json")
config("computercraft.mixins.json")
}
reobf {
register("shadowJar")
}
configurations {
val shade by registering { isTransitive = false }
implementation { extendsFrom(shade.get()) }
register("cctJavadoc")
}
dependencies {
minecraft("net.minecraftforge:forge:$mcVersion-${libs.versions.forge.get()}")
annotationProcessor("org.spongepowered:mixin:0.8.5:processor")
compileOnly(libs.jetbrainsAnnotations)
annotationProcessorEverywhere(libs.autoService)
"extraModsCompileOnly"(fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116:api"))
"extraModsRuntimeOnly"(fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116"))
"extraModsCompileOnly"(fg.deobf("maven.modrinth:oculus:1.18.2-1.2.5"))
"shade"(libs.cobalt)
testFixturesApi(libs.bundles.test)
testFixturesApi(libs.bundles.kotlin)
testImplementation(libs.bundles.test)
testImplementation(libs.bundles.kotlin)
testRuntimeOnly(libs.bundles.testRuntime)
"cctJavadoc"(libs.cctJavadoc)
}
illuaminate {
version.set(libs.versions.illuaminate)
}
// Compile tasks
tasks.javadoc {
include("dan200/computercraft/api/**/*.java")
(options as StandardJavadocDocletOptions).links("https://docs.oracle.com/en/java/javase/17/docs/api/")
}
val apiJar by tasks.registering(Jar::class) {
archiveClassifier.set("api")
from(sourceSets.main.get().output) {
include("dan200/computercraft/api/**/*")
}
}
tasks.assemble { dependsOn(apiJar) }
val luaJavadoc by tasks.registering(Javadoc::class) {
description = "Generates documentation for Java-side Lua functions."
group = JavaBasePlugin.DOCUMENTATION_GROUP
source(sourceSets.main.get().java)
setDestinationDir(buildDir.resolve("docs/luaJavadoc"))
classpath = sourceSets.main.get().compileClasspath
options.docletpath = configurations["cctJavadoc"].files.toList()
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
(options as StandardJavadocDocletOptions).noTimestamp(false)
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(cc.tweaked.gradle.CCTweakedPlugin.JAVA_VERSION)
},
)
}
tasks.processResources {
inputs.property("modVersion", modVersion)
inputs.property("forgeVersion", libs.versions.forge.get())
inputs.property("gitHash", cct.gitHash)
filesMatching("data/computercraft/lua/rom/help/credits.md") {
expand(mapOf("gitContributors" to cct.gitContributors.get().joinToString("\n")))
}
filesMatching("META-INF/mods.toml") {
expand(mapOf("forgeVersion" to libs.versions.forge.get(), "file" to mapOf("jarVersion" to modVersion)))
}
}
tasks.jar {
isReproducibleFileOrder = true
isPreserveFileTimestamps = false
finalizedBy("reobfJar")
archiveClassifier.set("slim")
manifest {
attributes(
"Specification-Title" to "computercraft",
"Specification-Vendor" to "SquidDev",
"Specification-Version" to "1",
"Implementation-Title" to "cctweaked",
"Implementation-Version" to modVersion,
"Implementation-Vendor" to "SquidDev",
)
}
}
tasks.shadowJar {
finalizedBy("reobfShadowJar")
archiveClassifier.set("")
configurations = listOf(project.configurations["shade"])
relocate("org.squiddev.cobalt", "cc.tweaked.internal.cobalt")
minimize()
}
tasks.assemble { dependsOn("shadowJar") }
// Web tasks
val rollup by tasks.registering(NpxExecToDir::class) {
group = LifecycleBasePlugin.BUILD_GROUP
description = "Bundles JS into rollup"
// Sources
inputs.files(fileTree("src/web")).withPropertyName("sources")
// Config files
inputs.file("tsconfig.json").withPropertyName("Typescript config")
inputs.file("rollup.config.js").withPropertyName("Rollup config")
// Output directory. Also defined in illuaminate.sexp and rollup.config.js
output.set(buildDir.resolve("rollup"))
args = listOf("rollup", "--config", "rollup.config.js")
}
val illuaminateDocs by tasks.registering(IlluaminateExecToDir::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description = "Generates docs using 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)
// Additional assets
inputs.files(rollup)
inputs.file("src/web/styles.css").withPropertyName("styles")
// Output directory. Also defined in illuaminate.sexp and transform.tsx
output.set(buildDir.resolve("illuaminate"))
args = listOf("doc-gen")
}
val jsxDocs by tasks.registering(NpxExecToDir::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description = "Post-processes documentation to statically render some dynamic content."
// Config files
inputs.file("tsconfig.json").withPropertyName("Typescript config")
// Sources
inputs.files(fileTree("src/web")).withPropertyName("sources")
inputs.file("src/generated/export/index.json").withPropertyName("export")
inputs.files(illuaminateDocs)
// Output directory. Also defined in src/web/transform.tsx
output.set(buildDir.resolve("jsxDocs"))
args = listOf("ts-node", "-T", "--esm", "src/web/transform.tsx")
}
val docWebsite by tasks.registering(Copy::class) {
group = JavaBasePlugin.DOCUMENTATION_GROUP
description = "Assemble docs and assets together into the documentation website."
from(jsxDocs)
from("doc") {
include("logo.png")
include("images/**")
}
from(rollup) { exclude("index.js") }
from(illuaminateDocs) { exclude("**/*.html") }
from("src/generated/export/items") { into("images/items") }
into(buildDir.resolve("docs/site"))
}
// Check tasks
tasks.test {
systemProperty("cct.test-files", buildDir.resolve("tmp/testFiles").absolutePath)
}
val lintLua by tasks.registering(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 = listOf("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::") }
}
val runGametest by tasks.registering(JavaExec::class) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Runs tests on a temporary Minecraft instance."
dependsOn("cleanRunGametest")
// Copy from runGameTestServer. We do it in this slightly odd way as runGameTestServer
// isn't created until the task is configured (which is no good for us).
val exec = tasks.getByName<JavaExec>("runGameTestServer")
dependsOn(exec.dependsOn)
exec.copyToFull(this)
}
cct.jacoco(runGametest)
tasks.check { dependsOn(runGametest) }
// Upload tasks
val checkChangelog by tasks.registering(CheckChangelog::class) {
version.set(modVersion)
whatsNew.set(file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md"))
changelog.set(file("src/main/resources/data/computercraft/lua/rom/help/changelog.md"))
}
tasks.check { dependsOn(checkChangelog) }
val publishCurseForge by tasks.registering(TaskPublishCurseForge::class) {
group = PublishingPlugin.PUBLISH_TASK_GROUP
description = "Upload artifacts to CurseForge"
apiToken = findProperty("curseForgeApiKey") ?: ""
enabled = apiToken != ""
val mainFile = upload("282001", tasks.shadowJar.get().archiveFile)
dependsOn(tasks.shadowJar) // Ughr.
mainFile.changelog = "Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)."
mainFile.changelogType = "markdown"
mainFile.releaseType = if (isStable) "release" else "alpha"
mainFile.gameVersions.add(mcVersion)
}
tasks.publish { dependsOn(publishCurseForge) }
modrinth {
token.set(findProperty("modrinthApiKey") as String? ?: "")
projectId.set("gu7yAYhd")
versionNumber.set("$mcVersion-$modVersion")
versionName.set(modVersion)
versionType.set(if (isStable) "release" else "alpha")
uploadFile.set(tasks.shadowJar as Any)
gameVersions.add(mcVersion)
changelog.set("Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion).")
syncBodyFrom.set(provider { file("doc/mod-page.md").readText() })
}
tasks.publish { dependsOn(tasks.modrinth) }
githubRelease {
token(findProperty("githubApiKey") as String? ?: "")
owner.set("cc-tweaked")
@@ -23,30 +405,54 @@ githubRelease {
releaseName.set("[$mcVersion] $modVersion")
body.set(
provider {
"## " + project(":core").file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
"## " + file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
.readLines()
.takeWhile { it != "Type \"help changelog\" to see the full version history." }
.joinToString("\n").trim()
},
)
prerelease.set(isUnstable)
prerelease.set(!isStable)
}
tasks.publish { dependsOn(tasks.githubRelease) }
idea.project.settings.compiler.javac {
// We want ErrorProne to be present when compiling via IntelliJ, as it offers some helpful warnings
// and errors. Loop through our source sets and find the appropriate flags.
moduleJavacAdditionalOptions = subprojects
.asSequence()
.map { evaluationDependsOn(it.path) }
.flatMap { project ->
val sourceSets = project.extensions.findByType(SourceSetContainer::class) ?: return@flatMap sequenceOf()
sourceSets.asSequence().map { sourceSet ->
val name = "${idea.project.name}.${project.name}.${sourceSet.name}"
val compile = project.tasks.named(sourceSet.compileJavaTaskName, JavaCompile::class).get()
name to compile.options.allCompilerArgs.joinToString(" ") { if (it.contains(" ")) "\"$it\"" else it }
publishing {
publications {
register<MavenPublication>("maven") {
artifactId = base.archivesName.get()
from(components["java"])
artifact(apiJar)
fg.component(this)
pom {
name.set("CC: Tweaked")
description.set("CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.")
url.set("https://github.com/cc-tweaked/CC-Tweaked")
scm {
url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
}
issueManagement {
system.set("github")
url.set("https://github.com/cc-tweaked/CC-Tweaked/issues")
}
licenses {
license {
name.set("ComputerCraft Public License, Version 1.0")
url.set("https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE")
}
}
}
}
.toMap()
}
repositories {
maven("https://squiddev.cc/maven") {
name = "SquidDev"
credentials(PasswordCredentials::class)
}
}
}

View File

@@ -3,51 +3,14 @@ plugins {
`kotlin-dsl`
}
// Duplicated in settings.gradle.kts
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.minecraftforge.net") {
name = "Forge"
content {
includeGroup("net.minecraftforge")
includeGroup("net.minecraftforge.gradle")
}
}
maven("https://maven.parchmentmc.org") {
name = "Librarian"
content {
includeGroupByRegex("^org\\.parchmentmc.*")
}
}
maven("https://repo.spongepowered.org/repository/maven-public/") {
name = "Sponge"
content {
includeGroup("org.spongepowered")
}
}
maven("https://maven.fabricmc.net/") {
name = "Fabric"
content {
includeGroup("net.fabricmc")
}
}
}
dependencies {
implementation(libs.errorProne.plugin)
implementation(libs.kotlin.plugin)
implementation(libs.spotless)
implementation(libs.fabric.loom)
implementation(libs.forgeGradle)
implementation(libs.librarian)
implementation(libs.quiltflower)
implementation(libs.vanillaGradle)
}
gradlePlugin {

View File

@@ -1,66 +0,0 @@
/** Default configuration for Fabric projects. */
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
`java-library`
id("fabric-loom")
id("io.github.juuxel.loom-quiltflower")
id("cc-tweaked.java-convention")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
repositories {
maven("https://maven.parchmentmc.org/") {
name = "Parchment"
content {
includeGroup("org.parchmentmc.data")
}
}
}
loom {
splitEnvironmentSourceSets()
splitModDependencies.set(true)
}
MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = "fabric")
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
minecraft("com.mojang:minecraft:$mcVersion")
mappings(
loom.layered {
officialMojangMappings()
parchment(
project.dependencies.create(
group = "org.parchmentmc.data",
name = "parchment-${libs.findVersion("parchmentMc").get()}",
version = libs.findVersion("parchment").get().toString(),
ext = "zip",
),
)
},
)
modImplementation(libs.findLibrary("fabric-loader").get())
modImplementation(libs.findLibrary("fabric-api").get())
// Depend on error prone annotations to silence a lot of compile warnings.
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
}
tasks.ideaSyncTask {
doLast { IdeaRunConfigurations(project).patch() }
}

View File

@@ -1,39 +0,0 @@
/** Default configuration for Forge projects. */
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
id("net.minecraftforge.gradle")
id("org.parchmentmc.librarian.forgegradle")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
}
MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = "forge")
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
"minecraft"("net.minecraftforge:forge:$mcVersion-${libs.findVersion("forge").get()}")
}
tasks.configureEach {
// genIntellijRuns isn't registered until much later, so we need this silly hijinks.
if (name == "genIntellijRuns") doLast { IdeaRunConfigurations(project).patch() }
}

View File

@@ -1,5 +1,4 @@
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
/**
* Sets up the configurations for writing game tests.
@@ -12,13 +11,12 @@ plugins {
id("cc-tweaked.java-convention")
}
val main = sourceSets["main"]
val client = sourceSets["client"]
val main = sourceSets.main.get()
// Both testMod and testFixtures inherit from the main and client classpath, just so we have access to Minecraft classes.
// Both testMod and testFixtures inherit from the main classpath, just so we have access to Minecraft classes.
val testMod by sourceSets.creating {
compileClasspath += main.compileClasspath + client.compileClasspath
runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
compileClasspath += main.compileClasspath
runtimeClasspath += main.runtimeClasspath
}
configurations {
@@ -34,13 +32,12 @@ configurations {
// Like the main test configurations, we're safe to depend on source set outputs.
dependencies {
add(testMod.implementationConfigurationName, main.output)
add(testMod.implementationConfigurationName, client.output)
}
// Similar to java-test-fixtures, but tries to avoid putting the obfuscated jar on the classpath.
val testFixtures by sourceSets.creating {
compileClasspath += main.compileClasspath + client.compileClasspath
compileClasspath += main.compileClasspath
}
java.registerFeature("testFixtures") {
@@ -49,12 +46,8 @@ java.registerFeature("testFixtures") {
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
add(testFixtures.apiConfigurationName, libs.findBundle("test").get())
// Consumers of this project already have the common and client classes on the classpath, so it's fine for these
// to be compile-only.
add(testFixtures.compileOnlyApiConfigurationName, commonClasses(project))
add(testFixtures.compileOnlyApiConfigurationName, clientClasses(project))
add(testFixtures.implementationConfigurationName, main.output)
testImplementation(testFixtures(project))
add(testMod.implementationConfigurationName, testFixtures(project))
}

View File

@@ -1,35 +1,23 @@
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.LicenseHeader
import com.diffplug.gradle.spotless.FormatExtension
import com.diffplug.spotless.LineEnding
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
import java.nio.charset.StandardCharsets
plugins {
`java-library`
idea
jacoco
checkstyle
id("com.diffplug.spotless")
id("net.ltgt.errorprone")
}
val modVersion: String by extra
val mcVersion: String by extra
group = "cc.tweaked"
version = modVersion
base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
java {
toolchain {
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
}
withSourcesJar()
withJavadocJar()
}
repositories {
@@ -40,15 +28,10 @@ repositories {
includeGroup("org.squiddev")
includeGroup("cc.tweaked")
// Things we mirror
includeGroup("dev.architectury")
includeGroup("com.blamejared.crafttweaker")
includeGroup("commoble.morered")
includeGroup("maven.modrinth")
includeGroup("me.shedaniel")
includeGroup("me.shedaniel.cloth")
includeGroup("mezz.jei")
includeModule("com.terraformersmc", "modmenu")
includeModule("fuzs.forgeconfigapiport", "forgeconfigapiport-fabric")
// Until https://github.com/SpongePowered/Mixin/pull/593 is merged
includeModule("org.spongepowered", "mixin")
}
}
}
@@ -56,9 +39,6 @@ repositories {
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
checkstyle(libs.findLibrary("checkstyle").get())
errorprone(libs.findLibrary("errorProne-core").get())
errorprone(libs.findLibrary("nullAway").get())
}
// Configure default JavaCompile tasks with our arguments.
@@ -66,68 +46,13 @@ sourceSets.all {
tasks.named(compileJavaTaskName, JavaCompile::class.java) {
// Processing just gives us "No processor claimed any of these annotations", so skip that!
options.compilerArgs.addAll(listOf("-Xlint", "-Xlint:-processing"))
options.errorprone {
check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
check("InvalidParam", CheckSeverity.OFF) // Broken by records.
check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally
// Too many false positives right now. Maybe we need an indirection for it later on.
check("ReferenceEquality", CheckSeverity.OFF)
check("UnusedVariable", CheckSeverity.OFF) // Too many false positives with records.
check("OperatorPrecedence", CheckSeverity.OFF) // For now.
check("AlreadyChecked", CheckSeverity.OFF) // Seems to be broken?
check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
check("NullAway", CheckSeverity.ERROR)
option("NullAway:AnnotatedPackages", listOf("dan200.computercraft", "net.fabricmc.fabric.api").joinToString(","))
option("NullAway:ExcludedFieldAnnotations", listOf("org.spongepowered.asm.mixin.Shadow").joinToString(","))
option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
option("NullAway:CheckOptionalEmptiness")
option("NullAway:AcknowledgeRestrictiveAnnotations")
}
}
}
tasks.compileTestJava {
options.errorprone {
check("NullAway", CheckSeverity.OFF)
}
}
tasks.withType(JavaCompile::class.java).configureEach {
options.encoding = "UTF-8"
}
tasks.withType(AbstractArchiveTask::class.java).configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
dirMode = Integer.valueOf("755", 8)
fileMode = Integer.valueOf("664", 8)
}
tasks.jar {
manifest {
attributes(
"Specification-Title" to "computercraft",
"Specification-Vendor" to "SquidDev",
"Specification-Version" to "1",
"Implementation-Title" to "cctweaked-${project.name}",
"Implementation-Version" to modVersion,
"Implementation-Vendor" to "SquidDev",
)
}
}
tasks.javadoc {
options {
val stdOptions = this as StandardJavadocDocletOptions
stdOptions.addBooleanOption("Xdoclint:all,-missing", true)
stdOptions.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
}
}
tasks.test {
finalizedBy("jacocoTestReport")
@@ -142,14 +67,6 @@ tasks.withType(JacocoReport::class.java).configureEach {
reports.html.required.set(true)
}
project.plugins.withType(CCTweakedPlugin::class.java) {
// Set up jacoco to read from /all/ our source directories.
val cct = project.extensions.getByType<CCTweakedExtension>()
project.tasks.named("jacocoTestReport", JacocoReport::class.java) {
for (ref in cct.sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
}
}
spotless {
encoding = StandardCharsets.UTF_8
lineEndings = LineEnding.UNIX
@@ -161,8 +78,8 @@ spotless {
}
val licenser = LicenseHeader.create(
api = rootProject.file("config/license/api.txt"),
main = rootProject.file("config/license/main.txt"),
api = file("config/license/api.txt"),
main = file("config/license/main.txt"),
)
java {
@@ -187,12 +104,3 @@ spotless {
ktlint().editorConfigOverride(ktlintConfig)
}
}
idea.module {
excludeDirs.addAll(project.files("run", "out", "logs").files)
// Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes.
// This is required for Loom, and we patch Forge's run configurations to work there.
// TODO: Submit a patch to Forge to support ProjectRootManager.
inheritOutputDirs = true
}

View File

@@ -1,45 +0,0 @@
import org.gradle.kotlin.dsl.`maven-publish`
plugins {
`java-library`
`maven-publish`
}
publishing {
publications {
register<MavenPublication>("maven") {
artifactId = base.archivesName.get()
from(components["java"])
pom {
name.set("CC: Tweaked")
description.set("CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.")
url.set("https://github.com/cc-tweaked/CC-Tweaked")
scm {
url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
}
issueManagement {
system.set("github")
url.set("https://github.com/cc-tweaked/CC-Tweaked/issues")
}
licenses {
license {
name.set("ComputerCraft Public License, Version 1.0")
url.set("https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE")
}
}
}
}
}
repositories {
maven("https://squiddev.cc/maven") {
name = "SquidDev"
credentials(PasswordCredentials::class)
}
}
}

View File

@@ -1,31 +0,0 @@
/** Default configuration for non-modloader-specific Minecraft projects. */
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
id("org.spongepowered.gradle.vanilla")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
version(mcVersion)
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
// Depend on error prone annotations to silence a lot of compile warnings.
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
}
MinecraftConfigurations.setup(project)
extensions.configure(CCTweakedExtension::class.java) {
linters(minecraft = true, loader = null)
}

View File

@@ -1,35 +1,23 @@
package cc.tweaked.gradle
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
import org.gradle.api.GradleException
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.attributes.TestSuiteType
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.reporting.ReportingExtension
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.configurationcache.extensions.capitalized
import org.gradle.kotlin.dsl.get
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.language.jvm.tasks.ProcessResources
import org.gradle.process.JavaForkOptions
import org.gradle.testing.jacoco.plugins.JacocoCoverageReport
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoReport
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
import java.io.BufferedWriter
import java.io.IOException
import java.net.URI
import java.net.URL
import java.io.OutputStreamWriter
import java.util.regex.Pattern
abstract class CCTweakedExtension(
@@ -38,137 +26,45 @@ abstract class CCTweakedExtension(
) {
/** Get the hash of the latest git commit. */
val gitHash: Provider<String> = gitProvider(project, "<no git hash>") {
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "HEAD").trim()
ProcessHelpers.captureOut("git", "-C", project.projectDir.absolutePath, "rev-parse", "HEAD").trim()
}
/** Get the current git branch. */
val gitBranch: Provider<String> = gitProvider(project, "<no git branch>") {
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD")
.trim()
ProcessHelpers.captureOut("git", "-C", project.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD").trim()
}
/** Get a list of all contributors to the project. */
val gitContributors: Provider<List<String>> = gitProvider(project, listOf()) {
ProcessHelpers.captureLines(
"git", "-C", project.rootProject.projectDir.absolutePath, "shortlog", "-ns",
"--group=author", "--group=trailer:co-authored-by", "HEAD",
val authors: Set<String> = HashSet(
ProcessHelpers.captureLines(
"git", "-C", project.projectDir.absolutePath, "log",
"--format=tformat:%an <%ae>%n%cn <%ce>%n%(trailers:key=Co-authored-by,valueonly)",
),
)
.asSequence()
.map {
val matcher = COMMIT_COUNTS.matcher(it)
matcher.find()
matcher.group(1)
}
.filter { !IGNORED_USERS.contains(it) }
.toList()
.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
val process = ProcessHelpers.startProcess("git", "check-mailmap", "--stdin")
BufferedWriter(OutputStreamWriter(process.outputStream)).use { writer ->
for (authorName in authors) {
var author = authorName
/**
* References to other sources
*/
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
/** All source sets referenced by this project. */
val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
init {
sourceDirectories.finalizeValueOnRead()
project.afterEvaluate { sourceDirectories.disallowChanges() }
}
/**
* Mark this project as consuming another project. Its [sourceDirectories] are added, allowing easier configuration
* of run configurations and other tasks which consume sources/classes.
*/
fun externalSources(project: Project) {
val otherCct = project.extensions.getByType(CCTweakedExtension::class.java)
for (sourceSet in otherCct.sourceDirectories.get()) {
sourceDirectories.add(SourceSetReference(sourceSet.sourceSet, classes = sourceSet.classes, external = true))
}
}
/**
* Add a dependency on another project such that its sources and compiles are processed with this one.
*
* This is used when importing a common library into a loader-specific one, as we want to compile sources using
* the loader-specific sources.
*/
fun inlineProject(path: String) {
val otherProject = project.evaluationDependsOn(path)
val otherJava = otherProject.extensions.getByType(JavaPluginExtension::class.java)
val main = otherJava.sourceSets.getByName("main")
val client = otherJava.sourceSets.getByName("client")
val testMod = otherJava.sourceSets.findByName("testMod")
val testFixtures = otherJava.sourceSets.findByName("testFixtures")
// Pull in sources from the other project.
extendSourceSet(otherProject, main)
extendSourceSet(otherProject, client)
if (testMod != null) extendSourceSet(otherProject, testMod)
if (testFixtures != null) extendSourceSet(otherProject, testFixtures)
// The extra source-processing tasks should include these files too.
project.tasks.named(main.javadocTaskName, Javadoc::class.java) { source(main.allJava, client.allJava) }
project.tasks.named(main.sourcesJarTaskName, Jar::class.java) { from(main.allSource, client.allSource) }
sourceDirectories.addAll(SourceSetReference.inline(main), SourceSetReference.inline(client))
}
/**
* Extend a source set with files from another project.
*
* This actually extends the original compile tasks, as extending the source sets does not play well with IDEs.
*/
private fun extendSourceSet(otherProject: Project, sourceSet: SourceSet) {
project.tasks.named(sourceSet.compileJavaTaskName, JavaCompile::class.java) {
dependsOn(otherProject.tasks.named(sourceSet.compileJavaTaskName)) // Avoid duplicate compile errors
source(sourceSet.allJava)
}
project.tasks.named(sourceSet.processResourcesTaskName, ProcessResources::class.java) {
from(sourceSet.resources)
}
// Also try to depend on Kotlin if it exists
val kotlin = otherProject.extensions.findByType(KotlinProjectExtension::class.java)
if (kotlin != null) {
val compileKotlin = sourceSet.getCompileTaskName("kotlin")
project.tasks.named(compileKotlin, KotlinCompile::class.java) {
dependsOn(otherProject.tasks.named(compileKotlin))
source(kotlin.sourceSets.getByName(sourceSet.name).kotlin)
if (author.isEmpty()) continue
if (!author.endsWith(">")) author += ">" // Some commits have broken Co-Authored-By lines!
writer.write(author)
writer.newLine()
}
}
// If we're doing an IDE sync, add a fake dependency to ensure it's on the classpath.
if (isIdeSync) project.dependencies.add(sourceSet.apiConfigurationName, sourceSet.output)
}
fun linters(@Suppress("UNUSED_PARAMETER") vararg unused: UseNamedArgs, minecraft: Boolean, loader: String?) {
val java = project.extensions.getByType(JavaPluginExtension::class.java)
val sourceSets = java.sourceSets
project.dependencies.run { add("errorprone", project(mapOf("path" to ":lints"))) }
sourceSets.all {
val name = name
project.tasks.named(compileJavaTaskName, JavaCompile::class.java) {
options.errorprone {
// Only the main source set should run the side checker
check("SideChecker", if (minecraft && name == "main") CheckSeverity.DEFAULT else CheckSeverity.OFF)
// The MissingLoaderOverride check superseeds the MissingOverride one, so disable that.
if (loader != null) {
check("MissingOverride", CheckSeverity.OFF)
option("ModLoader", loader)
} else {
check("LoaderOverride", CheckSeverity.OFF)
check("MissingLoaderOverride", CheckSeverity.OFF)
}
}
}
val contributors: MutableSet<String> = HashSet()
for (authorLine in ProcessHelpers.captureLines(process)) {
val matcher = EMAIL.matcher(authorLine)
matcher.find()
val name = matcher.group(1)
if (!IGNORED_USERS.contains(name)) contributors.add(name)
}
contributors.sortedWith(String.CASE_INSENSITIVE_ORDER)
}
fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
fun jacoco(task: NamedDomainObjectProvider<JavaExec>) {
val classDump = project.buildDir.resolve("jacocoClassDump/${task.name}")
val reportTaskName = "jacoco${task.name.capitalized()}Report"
@@ -197,7 +93,8 @@ abstract class CCTweakedExtension(
classDirectories.from(classDump)
// Don't want to use sourceSets(...) here as we have a custom class directory.
for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
val sourceSets = project.extensions.getByType(SourceSetContainer::class.java)
sourceDirectories.from(sourceSets["main"].allSource.sourceDirectories)
}
project.extensions.configure(ReportingExtension::class.java) {
@@ -207,43 +104,8 @@ abstract class CCTweakedExtension(
}
}
/**
* Download a file by creating a dummy Ivy repository.
*
* This should only be used for one-off downloads. Using a more conventional Ivy or Maven repository is preferred
* where possible.
*/
fun downloadFile(label: String, url: String): File {
val url = URL(url)
val path = File(url.path)
project.repositories.ivy {
name = label
setUrl(URI(url.protocol, url.userInfo, url.host, url.port, path.parent, null, null))
patternLayout {
artifact("[artifact].[ext]")
}
metadataSources {
artifact()
}
content {
includeModule("cc.tweaked.internal", path.nameWithoutExtension)
}
}
return project.configurations.detachedConfiguration(
project.dependencies.create(
mapOf(
"group" to "cc.tweaked.internal",
"name" to path.nameWithoutExtension,
"ext" to path.extension,
),
),
).resolve().single()
}
companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val EMAIL = Pattern.compile("^([^<]+) <.+>$")
private val IGNORED_USERS = setOf(
"GitHub", "Daniel Ratcliffe", "Weblate",
)
@@ -255,14 +117,8 @@ abstract class CCTweakedExtension(
} catch (e: IOException) {
project.logger.error("Cannot read Git repository: ${e.message}")
default
} catch (e: GradleException) {
project.logger.error("Cannot read Git repository: ${e.message}")
default
}
}
}
private val isIdeSync: Boolean
get() = java.lang.Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"))
}
}

View File

@@ -2,8 +2,6 @@ package cc.tweaked.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.jvm.toolchain.JavaLanguageVersion
/**
@@ -11,12 +9,7 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion
*/
class CCTweakedPlugin : Plugin<Project> {
override fun apply(project: Project) {
val cct = project.extensions.create("cct", CCTweakedExtension::class.java)
project.plugins.withType(JavaPlugin::class.java) {
val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main")))
}
project.extensions.create("cct", CCTweakedExtension::class.java)
}
companion object {

View File

@@ -6,6 +6,7 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.language.base.plugins.LifecycleBasePlugin
import java.nio.charset.StandardCharsets
/**
* Checks the `changelog.md` and `whatsnew.md` files are well-formed.
@@ -34,7 +35,7 @@ abstract class CheckChangelog : DefaultTask() {
var ok = true
// Check we're targeting the current version
// Check we're targetting the current version
var whatsNew = whatsNew.get().asFile.readLines()
if (whatsNew[0] != "New features in CC: Tweaked $version") {
ok = false

View File

@@ -5,6 +5,7 @@ import com.diffplug.spotless.FormatterStep
import com.diffplug.spotless.generic.LicenseHeaderStep
import java.io.File
import java.io.Serializable
import java.nio.charset.StandardCharsets
/**
* Similar to [LicenseHeaderStep], but supports multiple licenses.

View File

@@ -2,116 +2,19 @@ package cc.tweaked.gradle
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.tasks.JavaExec
import org.gradle.process.BaseExecSpec
import org.gradle.process.JavaExecSpec
import org.gradle.process.ProcessForkOptions
/**
* Add an annotation processor to all source sets.
*/
fun DependencyHandler.annotationProcessorEverywhere(dep: Any) {
add("compileOnly", dep)
add("annotationProcessor", dep)
add("clientCompileOnly", dep)
add("clientAnnotationProcessor", dep)
add("testCompileOnly", dep)
add("testAnnotationProcessor", dep)
}
/**
* A version of [JavaExecSpec.copyTo] which copies *all* properties.
*/
fun JavaExec.copyToFull(spec: JavaExec) {
copyTo(spec)
// Additional Java options
spec.jvmArgs = jvmArgs // Fabric overrides getJvmArgs so copyTo doesn't do the right thing.
spec.args = args
spec.argumentProviders.addAll(argumentProviders)
spec.mainClass.set(mainClass)
spec.classpath = classpath
spec.mainClass.set(mainClass)
spec.javaLauncher.set(javaLauncher)
if (executable != null) spec.setExecutable(executable!!)
// Additional ExecSpec options
copyToExec(spec)
}
/**
* Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
*/
fun BaseExecSpec.copyToExec(spec: BaseExecSpec) {
spec.isIgnoreExitValue = isIgnoreExitValue
if (standardInput != null) spec.standardInput = standardInput
if (standardOutput != null) spec.standardOutput = standardOutput
if (errorOutput != null) spec.errorOutput = errorOutput
}
/**
* An alternative to [Nothing] with a more descriptive name. Use to enforce calling a function with named arguments:
*
* ```kotlin
* fun f(vararg unused: UseNamedArgs, arg1: Int, arg2: Int) {
* // ...
* }
* ```
*/
class UseNamedArgs private constructor()
/**
* An [AutoCloseable] implementation which can be used to combine other [AutoCloseable] instances.
*
* Values which implement [AutoCloseable] can be dynamically registered with [CloseScope.add]. When the scope is closed,
* each value is closed in the opposite order.
*
* This is largely intended for cases where it's not appropriate to nest [AutoCloseable.use], for instance when nested
* would be too deep.
*/
class CloseScope : AutoCloseable {
private val toClose = ArrayDeque<AutoCloseable>()
/**
* Add a value to be closed when this scope is closed.
*/
public fun add(value: AutoCloseable) {
toClose.addLast(value)
}
override fun close() {
close(null)
}
@PublishedApi
internal fun close(baseException: Throwable?) {
var exception = baseException
while (true) {
var toClose = toClose.removeLastOrNull() ?: break
try {
toClose.close()
} catch (e: Throwable) {
if (exception == null) {
exception = e
} else {
exception.addSuppressed(e)
}
}
}
if (exception != null) throw exception
}
inline fun <R> use(block: (CloseScope) -> R): R {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
close(exception)
}
}
spec.args = args
}

View File

@@ -1,170 +0,0 @@
package cc.tweaked.gradle
import org.gradle.api.Project
import org.gradle.api.logging.Logging
import org.w3c.dom.Attr
import org.w3c.dom.Document
import org.w3c.dom.Node
import org.xml.sax.InputSource
import java.nio.file.Files
import java.nio.file.Path
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory
/**
* Patches up run configurations from ForgeGradle and Loom.
*
* Would be good to PR some (or all) of these changes upstream at some point.
*
* @see net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask
* @see net.minecraftforge.gradle.common.util.runs.IntellijRunGenerator
*/
internal class IdeaRunConfigurations(project: Project) {
private val rootProject = project.rootProject
private val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
private val xpath = XPathFactory.newInstance().newXPath()
private val writer = TransformerFactory.newInstance().newTransformer()
private val ideaDir = rootProject.file(".idea/")
private val buildDir: Lazy<String?> = lazy {
val ideaMisc = ideaDir.resolve("misc.xml")
try {
val doc = Files.newBufferedReader(ideaMisc.toPath()).use {
documentBuilder.parse(InputSource(it))
}
val node =
xpath.evaluate("//component[@name=\"ProjectRootManager\"]/output", doc, XPathConstants.NODE) as Node
val attr = node.attributes.getNamedItem("url") as Attr
attr.value.removePrefix("file://")
} catch (e: Exception) {
LOGGER.error("Failed to find root directory", e)
null
}
}
fun patch() = synchronized(LOCK) {
val runConfigDir = ideaDir.resolve("runConfigurations")
if (!runConfigDir.isDirectory) return
Files.list(runConfigDir.toPath()).use {
for (configuration in it) {
val filename = configuration.fileName.toString();
when {
filename.endsWith("_fabric.xml") -> patchFabric(configuration)
filename.startsWith("forge_") && filename.endsWith(".xml") -> patchForge(configuration)
else -> {}
}
}
}
}
private fun patchFabric(path: Path) = withXml(path) {
setXml("//configuration", "folderName") { "Fabric" }
}
private fun patchForge(path: Path) = withXml(path) {
val configId = path.fileName.toString().removePrefix("forge_").removeSuffix(".xml")
val sourceSet = forgeConfigs[configId]
if (sourceSet == null) {
LOGGER.error("[{}] Cannot map run configuration to a known source set", path)
return@withXml
}
setXml("//configuration", "folderName") { "Forge" }
setXml("//configuration/module", "name") { "${rootProject.name}.forge.$sourceSet" }
if (buildDir.value == null) return@withXml
setXml("//configuration/envs/env[@name=\"MOD_CLASSES\"]", "value") { classpath ->
val classes = classpath!!.split(':')
val newClasses = mutableListOf<String>()
fun appendUnique(x: String) {
if (!newClasses.contains(x)) newClasses.add(x)
}
for (entry in classes) {
if (!entry.contains("/out/")) {
appendUnique(entry)
continue
}
val match = CLASSPATH_ENTRY.matchEntire(entry)
if (match != null) {
val modId = match.groups["modId"]!!.value
val proj = match.groups["proj"]!!.value
var component = match.groups["component"]!!.value
if (component == "production") component = "main"
appendUnique(forgeModEntry(modId, proj, component))
} else {
LOGGER.warn("[{}] Unknown classpath entry {}", path, entry)
appendUnique(entry)
}
}
// Ensure common code is on the classpath
for (proj in listOf("common", "common-api")) {
for (component in listOf("main", "client")) {
appendUnique(forgeModEntry("computercraft", proj, component))
}
}
if (newClasses.any { it.startsWith("cctest%%") }) {
appendUnique(forgeModEntry("cctest", "core", "testFixtures"))
appendUnique(forgeModEntry("cctest", "common", "testFixtures"))
appendUnique(forgeModEntry("cctest", "common", "testMod"))
}
newClasses.joinToString(":")
}
}
private fun forgeModEntry(mod: String, project: String, component: String) =
"$mod%%${buildDir.value}/production/${rootProject.name}.$project.$component"
private fun LocatedDocument.setXml(xpath: String, attribute: String, value: (String?) -> String) {
val node = this@IdeaRunConfigurations.xpath.evaluate(xpath, document, XPathConstants.NODE) as Node?
if (node == null) {
LOGGER.error("[{}] Cannot find {}", path.fileName, xpath)
return
}
val attr = node.attributes.getNamedItem(attribute) as Attr? ?: document.createAttribute(attribute)
val oldValue = attr.value
attr.value = value(attr.value)
node.attributes.setNamedItem(attr)
if (oldValue != attr.value) {
LOGGER.info("[{}] Setting {}@{}:\n Old: {}\n New: {}", path.fileName, xpath, attribute, oldValue, attr.value)
}
}
private fun withXml(path: Path, run: LocatedDocument.() -> Unit) {
val doc = Files.newBufferedReader(path).use { documentBuilder.parse(InputSource(it)) }
run(LocatedDocument(path, doc))
Files.newBufferedWriter(path).use { writer.transform(DOMSource(doc), StreamResult(it)) }
}
private class LocatedDocument(val path: Path, val document: Document)
companion object {
private val LOGGER = Logging.getLogger(IdeaRunConfigurations::class.java)
private val LOCK = Any()
private val CLASSPATH_ENTRY =
Regex("(?<modId>[a-z]+)%%\\\$PROJECT_DIR\\\$/projects/(?<proj>[a-z-]+)/out/(?<component>\\w+)/(?<type>[a-z]+)\$")
private val forgeConfigs = mapOf(
"runClient" to "client",
"runData" to "main",
"runGameTestServer" to "testMod",
"runServer" to "main",
"runTestClient" to "testMod",
)
}
}

View File

@@ -66,11 +66,9 @@ class IlluaminatePlugin : Plugin<Project> {
val osArch = System.getProperty("os.arch").toLowerCase()
val arch = when {
// On macOS the x86_64 binary will work for both ARM and Intel Macs through Rosetta.
os == "macos" -> "x86_64"
osArch == "arm" || osArch.startsWith("aarch") -> error("Unsupported architecture '$osArch' for illuaminate")
osArch.contains("64") -> "x86_64"
else -> error("Unsupported architecture '$osArch' for illuaminate")
else -> error("Unsupported architecture $osArch for illuaminate")
}
return project.dependencies.create(

View File

@@ -1,62 +0,0 @@
package cc.tweaked.gradle
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.specs.Spec
/**
* A dependency in a POM file.
*/
data class MavenDependency(val groupId: String?, val artifactId: String?, val version: String?, val scope: String?)
/**
* A spec specifying which dependencies to include/exclude.
*/
class MavenDependencySpec {
private val excludeSpecs = mutableListOf<Spec<MavenDependency>>()
fun exclude(spec: Spec<MavenDependency>) {
excludeSpecs.add(spec)
}
fun exclude(dep: Dependency) {
exclude {
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
(dep.name.isNullOrEmpty() || dep.name == it.artifactId) &&
(dep.version.isNullOrEmpty() || dep.version == it.version)
}
}
fun exclude(dep: MinimalExternalModuleDependency) {
exclude {
dep.module.group == it.groupId && dep.module.name == it.artifactId
}
}
fun isIncluded(dep: MavenDependency) = !excludeSpecs.any { it.isSatisfiedBy(dep) }
}
/**
* Configure dependencies present in this publication's POM file.
*
* While this approach is very ugly, it's the easiest way to handle it!
*/
fun MavenPublication.mavenDependencies(action: MavenDependencySpec.() -> Unit) {
val spec = MavenDependencySpec()
action(spec)
pom.withXml {
val dependencies = XmlUtil.findChild(asNode(), "dependencies") ?: return@withXml
dependencies.children().map { it as groovy.util.Node }.forEach {
val dep = MavenDependency(
groupId = XmlUtil.findChild(it, "groupId")?.text(),
artifactId = XmlUtil.findChild(it, "artifactId")?.text(),
version = XmlUtil.findChild(it, "version")?.text(),
scope = XmlUtil.findChild(it, "scope")?.text(),
)
if (!spec.isIncluded(dep)) it.parent().remove(it)
}
}
}

View File

@@ -1,189 +0,0 @@
package cc.tweaked.gradle
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.attributes.Bundling
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Usage
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.capabilities.Capability
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.named
/**
* This sets up a separate client-only source set, and extends that and the main/common source set with additional
* metadata, to make it easier to consume jars downstream.
*/
class MinecraftConfigurations private constructor(private val project: Project) {
private val java = project.extensions.getByType(JavaPluginExtension::class.java)
private val sourceSets = java.sourceSets
private val configurations = project.configurations
private val objects = project.objects
private val main = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
private val test = sourceSets[SourceSet.TEST_SOURCE_SET_NAME]
/**
* Performs the initial setup of our configurations.
*/
private fun setup() {
// Define a client source set.
val client = sourceSets.maybeCreate("client")
// Ensure the client classpaths behave the same as the main ones.
configurations.named(client.compileClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.compileClasspathConfigurationName])
}
configurations.named(client.runtimeClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.runtimeClasspathConfigurationName])
}
// Set up an API configuration for clients (to ensure it's consistent with the main source set).
val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply {
isVisible = false
isCanBeConsumed = false
isCanBeResolved = false
}
configurations.named(client.implementationConfigurationName) { extendsFrom(clientApi) }
/*
Now add outgoing variants for the main and common source sets that we can consume downstream. This is possibly
the worst way to do things, but unfortunately the alternatives don't actually work very well:
- Just using source set outputs: This means dependencies don't propagate, which means when :fabric depends
on :fabric-api, we don't inherit the fake :common-api in IDEA.
- Having separate common/main jars: Nice in principle, but unfortunately Forge needs a separate deobf jar
task (as the original jar is obfuscated), and IDEA is not able to map its output back to a source set.
This works for now, but is incredibly brittle. It's part of the reason we can't use testFixtures inside our
MC projects, as that adds a project(self) -> test dependency, which would pull in the jar instead.
Note we register a fake client jar here. It's not actually needed, but is there to make sure IDEA has
a way to tell that client classes are needed at runtime.
I'm so sorry, deeply aware how cursed this is.
*/
setupOutgoing(main, "CommonOnly")
project.tasks.register(client.jarTaskName, Jar::class.java) {
description = "An empty jar standing in for the client classes."
group = BasePlugin.BUILD_GROUP
archiveClassifier.set("client")
}
setupOutgoing(client)
// Reset the client classpath (Loom configures it slightly differently to this) and add a main -> client
// dependency. Here we /can/ use source set outputs as we add transitive deps by patching the classpath. Nasty,
// but avoids accidentally pulling in Forge's obfuscated jar.
client.compileClasspath = client.compileClasspath + main.compileClasspath
client.runtimeClasspath = client.runtimeClasspath + main.runtimeClasspath
project.dependencies.add(client.apiConfigurationName, main.output)
// Also add client classes to the test classpath. We do the same nasty tricks as needed for main -> client.
test.compileClasspath += client.compileClasspath
test.runtimeClasspath += client.runtimeClasspath
project.dependencies.add(test.implementationConfigurationName, client.output)
// Configure some tasks to include our additional files.
project.tasks.named("javadoc", Javadoc::class.java) {
source(client.allJava)
classpath = main.compileClasspath + main.output + client.compileClasspath + client.output
}
// This are already done by Fabric, but we need it for Forge and vanilla. It shouldn't conflict at all.
project.tasks.named("jar", Jar::class.java) { from(client.output) }
project.tasks.named("sourcesJar", Jar::class.java) { from(client.allSource) }
project.extensions.configure(CCTweakedExtension::class.java) {
sourceDirectories.add(SourceSetReference.internal(client))
}
}
private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {
setupOutgoing("${sourceSet.apiElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_API)) {
description = "API elements for ${sourceSet.name}"
extendsFrom(configurations[sourceSet.apiConfigurationName])
}
setupOutgoing("${sourceSet.runtimeElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_RUNTIME)) {
description = "Runtime elements for ${sourceSet.name}"
extendsFrom(configurations[sourceSet.implementationConfigurationName], configurations[sourceSet.runtimeOnlyConfigurationName])
}
}
/**
* Set up an outgoing configuration for a specific source set. We set an additional "main" or "client" capability
* (depending on the source set name) which allows downstream projects to consume them separately (see
* [DependencyHandler.commonClasses] and [DependencyHandler.clientClasses]).
*/
private fun setupOutgoing(name: String, sourceSet: SourceSet, usage: Usage, configure: Configuration.() -> Unit) {
configurations.register(name) {
isVisible = false
isCanBeConsumed = true
isCanBeResolved = false
configure(this)
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
attribute(Usage.USAGE_ATTRIBUTE, usage)
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
attributeProvider(
TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,
java.toolchain.languageVersion.map { it.asInt() },
)
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
}
outgoing {
capability(BasicOutgoingCapability(project, sourceSet.name))
// We have two outgoing variants here: the original jar and the classes.
artifact(project.tasks.named(sourceSet.jarTaskName))
variants.create("classes") {
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.CLASSES))
sourceSet.output.classesDirs.forEach { artifact(it) { builtBy(sourceSet.output) } }
}
}
}
}
companion object {
fun setup(project: Project) {
MinecraftConfigurations(project).setup()
}
}
}
private class BasicIncomingCapability(private val module: ModuleDependency, private val name: String) : Capability {
override fun getGroup(): String = module.group!!
override fun getName(): String = "${module.name}-$name"
override fun getVersion(): String? = null
}
private class BasicOutgoingCapability(private val project: Project, private val name: String) : Capability {
override fun getGroup(): String = project.group.toString()
override fun getName(): String = "${project.name}-$name"
override fun getVersion(): String = project.version.toString()
}
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency {
val dep = create(notation) as ModuleDependency
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "client")) }
return dep
}
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency {
val dep = create(notation) as ModuleDependency
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "main")) }
return dep
}

View File

@@ -1,191 +0,0 @@
package cc.tweaked.gradle
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.getByName
import org.gradle.language.base.plugins.LifecycleBasePlugin
import java.io.File
import java.nio.file.Files
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
import javax.inject.Inject
import kotlin.random.Random
/**
* A [JavaExec] task for client-tests. This sets some common setup, and uses [MinecraftRunnerService] to ensure only one
* test runs at once.
*/
abstract class ClientJavaExec : JavaExec() {
private val clientRunner: Provider<MinecraftRunnerService> = MinecraftRunnerService.get(project.gradle)
init {
group = LifecycleBasePlugin.VERIFICATION_GROUP
usesService(clientRunner)
}
/**
* When [false], tests will not be run automatically, allowing the user to debug rendering.
*/
@get:Input
val clientDebug get() = project.hasProperty("clientDebug")
/**
* When [false], tests will not run under a framebuffer.
*/
@get:Input
val useFramebuffer get() = !clientDebug && !project.hasProperty("clientNoFramebuffer")
/**
* The path test results are written to.
*/
@get:OutputFile
val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
/**
* Copy configuration from a task with the given name.
*/
fun copyFrom(path: String) = copyFrom(project.tasks.getByName(path, JavaExec::class))
/**
* Copy configuration from an existing [JavaExec] task.
*/
fun copyFrom(task: JavaExec) {
for (dep in task.dependsOn) dependsOn(dep)
task.copyToFull(this)
if (!clientDebug) systemProperty("cctest.client", "")
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
workingDir(project.buildDir.resolve("gametest").resolve(name))
}
/**
* Only run tests with the given tags.
*/
fun tags(vararg tags: String) {
systemProperty("cctest.tags", tags.joinToString(","))
}
/**
* Write a file with the given contents before starting Minecraft. This may be useful for writing config files.
*/
fun withFileContents(path: Any, contents: Supplier<String>) {
val file = project.file(path).toPath()
doFirst {
Files.createDirectories(file.parent)
Files.writeString(file, contents.get())
}
}
/**
* Copy a file to the provided path before starting Minecraft. This copy only occurs if the file does not already
* exist.
*/
fun withFileFrom(path: Any, source: Supplier<File>) {
val file = project.file(path).toPath()
doFirst {
Files.createDirectories(file.parent)
if (!Files.exists(file)) Files.copy(source.get().toPath(), file)
}
}
@TaskAction
override fun exec() {
Files.createDirectories(workingDir.toPath())
fsOperations.delete { delete(workingDir.resolve("screenshots")) }
if (useFramebuffer) {
clientRunner.get().wrapClient(this) { super.exec() }
} else {
super.exec()
}
}
@get:Inject
protected abstract val fsOperations: FileSystemOperations
}
/**
* A service for [JavaExec] tasks which start Minecraft.
*
* Tasks may run `usesService(MinecraftRunnerService.get(gradle))` to ensure that only one Minecraft-related task runs
* at once.
*/
abstract class MinecraftRunnerService : BuildService<BuildServiceParameters.None> {
private val hasXvfb = lazy {
System.getProperty("os.name", "").equals("linux", ignoreCase = true) && ProcessHelpers.onPath("xvfb-run")
}
internal fun wrapClient(exec: JavaExec, run: () -> Unit) = when {
hasXvfb.value -> runXvfb(exec, run)
else -> run()
}
/**
* Run a program under Xvfb, preventing it spawning a window.
*/
private fun runXvfb(exec: JavaExec, run: () -> Unit) {
fun ProcessBuilder.startVerbose(): Process {
exec.logger.info("Running ${this.command()}")
return start()
}
CloseScope().use { scope ->
val dir = Files.createTempDirectory("cctweaked").toAbsolutePath()
scope.add { fsOperations.delete { delete(dir) } }
val authFile = Files.createTempFile(dir, "Xauthority", "").toAbsolutePath()
val cookie = StringBuilder().also {
for (i in 0..31) it.append("0123456789abcdef"[Random.nextInt(16)])
}.toString()
val xvfb =
ProcessBuilder("Xvfb", "-displayfd", "1", "-screen", "0", "640x480x24", "-nolisten", "tcp").also {
it.inheritIO()
it.environment()["XAUTHORITY"] = authFile.toString()
it.redirectOutput(ProcessBuilder.Redirect.PIPE)
}.startVerbose()
scope.add { xvfb.destroyForcibly().waitFor() }
val server = xvfb.inputReader().use { it.readLine().trim() }
exec.logger.info("Running at :$server (XAUTHORITY=$authFile.toA")
ProcessBuilder("xauth", "add", ":$server", ".", cookie).also {
it.inheritIO()
it.environment()["XAUTHORITY"] = authFile.toString()
}.startVerbose().waitForOrThrow("Failed to setup XAuthority file")
scope.add {
ProcessBuilder("xauth", "remove", ":$server").also {
it.inheritIO()
it.environment()["XAUTHORITY"] = authFile.toString()
}.startVerbose().waitFor()
}
// Wait a few seconds for Xvfb to start. Ugly, but identical to xvfb-run.
if (xvfb.waitFor(3, TimeUnit.SECONDS)) {
throw GradleException("Xvfb unexpectedly exited (with status code ${xvfb.exitValue()})")
}
exec.environment("XAUTHORITY", authFile.toString())
exec.environment("DISPLAY", ":$server")
run()
}
}
@get:Inject
protected abstract val fsOperations: FileSystemOperations
companion object {
fun get(gradle: Gradle): Provider<MinecraftRunnerService> =
gradle.sharedServices.registerIfAbsent("cc.tweaked.gradle.ClientJavaExec", MinecraftRunnerService::class.java) {
maxParallelUsages.set(1)
}
}
}

View File

@@ -1,50 +1,35 @@
package cc.tweaked.gradle
import org.codehaus.groovy.runtime.ProcessGroovyMethods
import org.gradle.api.GradleException
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStreamReader
import java.util.stream.Collectors
internal object ProcessHelpers {
fun startProcess(vararg command: String): Process {
// Something randomly passes in "GIT_DIR=" as an environment variable which clobbers everything else. Don't
// inherit the environment array!
return ProcessBuilder()
.command(*command)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.also { it.environment().clear() }
.start()
return Runtime.getRuntime().exec(command, arrayOfNulls(0))
}
fun captureOut(vararg command: String): String {
val process = startProcess(*command)
process.outputStream.close()
val result = ProcessGroovyMethods.getText(process)
process.waitForOrThrow("Failed to run command")
if (process.waitFor() != 0) throw IOException("Command exited with a non-0 status")
return result
}
fun captureLines(vararg command: String): List<String> {
val process = startProcess(*command)
process.outputStream.close()
return captureLines(startProcess(*command))
}
fun captureLines(process: Process): List<String> {
val out = BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
reader.lines().filter { it.isNotEmpty() }.toList()
reader.lines().filter { it.isNotEmpty() }.collect(Collectors.toList())
}
ProcessGroovyMethods.closeStreams(process)
process.waitForOrThrow("Failed to run command")
if (process.waitFor() != 0) throw IOException("Command exited with a non-0 status")
return out
}
fun onPath(name: String): Boolean {
val path = System.getenv("PATH") ?: return false
return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() }
}
}
internal fun Process.waitForOrThrow(message: String) {
val ret = waitFor()
if (ret != 0) throw GradleException("$message (exited with $ret)")
}

View File

@@ -1,20 +0,0 @@
package cc.tweaked.gradle
import org.gradle.api.tasks.SourceSet
data class SourceSetReference(
val sourceSet: SourceSet,
val classes: Boolean,
val external: Boolean,
) {
companion object {
/** A source set in the current project. */
fun internal(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = false)
/** A source set from another project. */
fun external(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = true)
/** A source set which is inlined into the current project. */
fun inline(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = false, external = false)
}
}

View File

@@ -1,12 +0,0 @@
package cc.tweaked.gradle
import groovy.util.Node
import groovy.util.NodeList
object XmlUtil {
fun findChild(node: Node, name: String): Node? = when (val child = node.get(name)) {
is Node -> child
is NodeList -> child.singleOrNull() as Node?
else -> null
}
}

View File

@@ -17,10 +17,7 @@
<module name="TreeWalker">
<!-- Annotations -->
<module name="AnnotationLocation" />
<module name="AnnotationUseStyle">
<!-- We want trailing commas on multiline arrays. -->
<property name="trailingArrayComma" value="ignore" />
</module>
<module name="AnnotationUseStyle" />
<module name="MissingDeprecated" />
<module name="MissingOverride" />
@@ -29,11 +26,17 @@
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="ignored" />
</module>
<module name="LeftCurly" />
<module name="LeftCurly">
<property name="option" value="nl" />
<!-- The defaults, minus lambdas. -->
<property name="tokens" value="ANNOTATION_DEF,CLASS_DEF,CTOR_DEF,ENUM_CONSTANT_DEF,ENUM_DEF,INTERFACE_DEF,LITERAL_CASE,LITERAL_CATCH,LITERAL_DEFAULT,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,METHOD_DEF,OBJBLOCK,STATIC_INIT" />
</module>
<module name="NeedBraces">
<property name="allowSingleLineStatement" value="true"/>
</module>
<module name="RightCurly" />
<module name="RightCurly">
<property name="option" value="alone" />
</module>
<!-- Class design. As if we've ever followed good practice here. -->
<module name="FinalClass" />
@@ -111,7 +114,7 @@
</module>
<module name="MethodTypeParameterName" />
<module name="PackageName">
<property name="format" value="^(dan200\.computercraft|cc\.tweaked)(\.[a-z][a-z0-9]*)*" />
<property name="format" value="^dan200\.computercraft(\.[a-z][a-z0-9]*)*" />
</module>
<module name="ParameterName" />
<module name="StaticVariableName">
@@ -128,11 +131,18 @@
<module name="MethodParamPad" />
<module name="NoLineWrap" />
<module name="NoWhitespaceAfter">
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP,METHOD_REF" />
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP" />
</module>
<module name="NoWhitespaceBefore" />
<!-- TODO: Decide on an OperatorWrap style. -->
<module name="ParenPad" />
<module name="ParenPad">
<property name="option" value="space" />
<property name="tokens" value="ANNOTATION,ANNOTATION_FIELD_DEF,CTOR_CALL,CTOR_DEF,ENUM_CONSTANT_DEF,LITERAL_CATCH,LITERAL_DO,LITERAL_FOR,LITERAL_IF,LITERAL_NEW,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_WHILE,METHOD_CALL,METHOD_DEF,RESOURCE_SPECIFICATION,SUPER_CTOR_CALL,LAMBDA" />
</module>
<module name="ParenPad">
<property name="option" value="nospace" />
<property name="tokens" value="DOT,EXPR,QUESTION" />
</module>
<module name="SeparatorWrap">
<property name="option" value="eol" />
<property name="tokens" value="COMMA,SEMI,ELLIPSIS,ARRAY_DECLARATOR,RBRACK,METHOD_REF" />

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
<code_scheme name="Project" version="173">
<JSON>
<option name="OBJECT_WRAPPING" value="1" />
<option name="ARRAY_WRAPPING" value="1" />
</JSON>
<JavaCodeStyleSettings>
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="JD_P_AT_EMPTY_LINES" value="false" />
<option name="JD_PRESERVE_LINE_FEEDS" value="true" />
</JavaCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="BRACE_STYLE" value="2" />
<option name="CLASS_BRACE_STYLE" value="2" />
<option name="METHOD_BRACE_STYLE" value="2" />
<option name="LAMBDA_BRACE_STYLE" value="5" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="SPACE_WITHIN_METHOD_CALL_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_METHOD_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_IF_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_WHILE_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_FOR_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_TRY_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_CATCH_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_SWITCH_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_SYNCHRONIZED_PARENTHESES" value="true" />
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
<option name="SPACE_BEFORE_IF_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_TRY_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="1" />
<option name="DOWHILE_BRACE_FORCE" value="1" />
<option name="WHILE_BRACE_FORCE" value="1" />
<option name="FOR_BRACE_FORCE" value="1" />
<option name="SPACE_WITHIN_ANNOTATION_PARENTHESES" value="true" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JSON">
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="SPACE_WITHIN_BRACKETS" value="true" />
<option name="SPACE_WITHIN_BRACES" value="true" />
<indentOptions>
<option name="INDENT_SIZE" value="4" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
</codeStyleSettings>
</code_scheme>

View File

@@ -3,20 +3,19 @@ module: [kind=event] alarm
see: os.setAlarm To start an alarm.
---
The @{alarm} event is fired when an alarm started with @{os.setAlarm} completes.
The @{timer} event is fired when an alarm started with @{os.setAlarm} completes.
## Return Values
1. @{string}: The event name.
2. @{number}: The ID of the alarm that finished.
## Example
Starts a timer and then waits for it to complete.
Starts a timer and then prints its ID:
```lua
local alarm_id = os.setAlarm(os.time() + 0.05)
local alarmID = os.setAlarm(os.time() + 0.05)
local event, id
repeat
event, id = os.pullEvent("alarm")
until id == alarm_id
until id == alarmID
print("Alarm with ID " .. id .. " was fired")
```

View File

@@ -3,7 +3,7 @@ module: [kind=event] char
see: key To listen to any key press.
---
The @{char} event is fired when a character is typed on the keyboard.
The @{char} event is fired when a character is _typed_ on the keyboard.
The @{char} event is different to a key press. Sometimes multiple key presses may result in one character being
typed (for instance, on some European keyboards). Similarly, some keys (e.g. <kbd>Ctrl</kbd>) do not have any
@@ -16,10 +16,9 @@ corresponding character. The @{key} should be used if you want to listen to key
## Example
Prints each character the user presses:
```lua
while true do
local event, character = os.pullEvent("char")
print(character .. " was pressed.")
local event, character = os.pullEvent("char")
print(character .. " was pressed.")
end
```

View File

@@ -6,7 +6,7 @@ The @{computer_command} event is fired when the `/computercraft queue` command i
## Return Values
1. @{string}: The event name.
2. @{string}<abbr title="Variable number of arguments">&hellip;</abbr>: The arguments passed to the command.
... @{string}: The arguments passed to the command.
## Example
Prints the contents of messages sent:

View File

@@ -5,9 +5,9 @@ since: 1.101.0
The @{file_transfer} event is queued when a user drags-and-drops a file on an open computer.
This event contains a single argument of type @{TransferredFiles}, which can be used to @{TransferredFiles.getFiles|get
the files to be transferred}. Each file returned is a @{fs.BinaryReadHandle|binary file handle} with an additional
@{TransferredFile.getName|getName} method.
This event contains a single argument, that in turn has a single method @{TransferredFiles.getFiles|getFiles}. This
returns the list of files that are being transferred. Each file is a @{fs.BinaryReadHandle|binary file handle} with an
additional @{TransferredFile.getName|getName} method.
## Return values
1. @{string}: The event name

View File

@@ -11,4 +11,4 @@ This event is normally handled inside @{http.checkURL}, but it can still be seen
1. @{string}: The event name.
2. @{string}: The URL requested to be checked.
3. @{boolean}: Whether the check succeeded.
4. <span class="type">@{string}|@{nil}</span>: If the check failed, a reason explaining why the check failed.
4. @{string|nil}: If the check failed, a reason explaining why the check failed.

View File

@@ -11,8 +11,7 @@ This event is normally handled inside @{http.get} and @{http.post}, but it can s
1. @{string}: The event name.
2. @{string}: The URL of the site requested.
3. @{string}: An error describing the failure.
4. <span class="type">@{http.Response}|@{nil}</span>: A response handle if the connection succeeded, but the server's
response indicated failure.
4. @{http.Response|nil}: A response handle if the connection succeeded, but the server's response indicated failure.
## Example
Prints an error why the website cannot be contacted:

View File

@@ -10,7 +10,7 @@ This event is normally handled inside @{http.get} and @{http.post}, but it can s
## Return Values
1. @{string}: The event name.
2. @{string}: The URL of the site requested.
3. @{http.Response}: The successful HTTP response.
3. @{http.Response}: The handle for the response text.
## Example
Prints the content of a website (this may fail if the request fails):

View File

@@ -10,7 +10,7 @@ The @{modem_message} event is fired when a message is received on an open channe
3. @{number}: The channel that the message was sent on.
4. @{number}: The reply channel set by the sender.
5. @{any}: The message as sent by the sender.
6. <span class="type">@{number}|@{nil}</span>: The distance between the sender and the receiver in blocks, or @{nil} if the message was sent between dimensions.
6. @{number}: The distance between the sender and the receiver, in blocks.
## Example
Wraps a @{modem} peripheral, opens channel 0 for listening, and prints all received messages.
@@ -20,9 +20,7 @@ local modem = peripheral.find("modem") or error("No modem attached", 0)
modem.open(0)
while true do
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(
side, channel, replyChannel, distance, tostring(message)
))
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(side, channel, replyChannel, distance, tostring(message)))
end
```

View File

@@ -6,11 +6,10 @@ The @{monitor_resize} event is fired when an adjacent or networked monitor's siz
## Return Values
1. @{string}: The event name.
2. @{string}: The side or network ID of the monitor that was resized.
2. @{string}: The side or network ID of the monitor that resized.
## Example
Prints a message when a monitor is resized:
```lua
while true do
local event, side = os.pullEvent("monitor_resize")

View File

@@ -14,7 +14,7 @@ This event is usually handled by @{rednet.receive}, but it can also be pulled ma
1. @{string}: The event name.
2. @{number}: The ID of the sending computer.
3. @{any}: The message sent.
4. <span class="type">@{string}|@{nil}</span>: The protocol of the message, if provided.
4. @{string|nil}: The protocol of the message, if provided.
## Example
Prints a message when one is sent:

View File

@@ -4,9 +4,6 @@ module: [kind=event] redstone
The @{event!redstone} event is fired whenever any redstone inputs on the computer change.
## Return values
1. @{string}: The event name.
## Example
Prints a message when a redstone input changes:
```lua

View File

@@ -10,7 +10,7 @@ The @{task_complete} event is fired when an asynchronous task completes. This is
2. @{number}: The ID of the task that completed.
3. @{boolean}: Whether the command succeeded.
4. @{string}: If the command failed, an error message explaining the failure. (This is not present if the command succeeded.)
5. <abbr title="Variable number of arguments">&hellip;</abbr>: Any parameters returned from the command.
...: Any parameters returned from the command.
## Example
Prints the results of an asynchronous command:

View File

@@ -9,12 +9,8 @@ The @{term_resize} event is fired when the main terminal is resized. For instanc
When this event fires, some parts of the terminal may have been moved or deleted. Simple terminal programs (those
not using @{term.setCursorPos}) can ignore this event, but more complex GUI programs should redraw the entire screen.
## Return values
1. @{string}: The event name.
## Example
Print a message each time the terminal is resized.
Prints :
```lua
while true do
os.pullEvent("term_resize")

View File

@@ -8,9 +8,6 @@ This event is normally handled by @{os.pullEvent}, and will not be returned. How
@{terminate} will be sent even when a filter is provided to @{os.pullEventRaw}. When using @{os.pullEventRaw} with a filter, make sure to check that the event is not @{terminate}.
## Return values
1. @{string}: The event name.
## Example
Prints a message when Ctrl-T is held:
```lua

View File

@@ -10,12 +10,12 @@ The @{timer} event is fired when a timer started with @{os.startTimer} completes
2. @{number}: The ID of the timer that finished.
## Example
Start and wait for a timer to finish.
Starts a timer and then prints its ID:
```lua
local timer_id = os.startTimer(2)
local timerID = os.startTimer(2)
local event, id
repeat
event, id = os.pullEvent("timer")
until id == timer_id
until id == timerID
print("Timer with ID " .. id .. " was fired")
```

View File

@@ -4,9 +4,6 @@ module: [kind=event] turtle_inventory
The @{turtle_inventory} event is fired when a turtle's inventory is changed.
## Return values
1. @{string}: The event name.
## Example
Prints a message when the inventory is changed:
```lua

View File

@@ -25,7 +25,7 @@ single-player or multiplayer. Look for lines that look like this:
```
On 1.95.0 and later, this will be a single entry with `host = "$private"`. On earlier versions, this will be a number of
`[[http.rules]]` with various IP addresses. You will want to remove all of the `[[http.rules]]` entries that have
`[[http.rules]]` with various IP addresses. You will want to remove all of the `[[http.rules]]` entires that have
`action = "deny"`. Then save the file and relaunch Minecraft (Server).
Here's what it should look like after removing:
@@ -54,7 +54,7 @@ like this:
```toml
#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
#You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
blacklist = ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"]
```
@@ -65,7 +65,7 @@ Here's what it should look like after removing:
```toml
#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
#You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
blacklist = []
```

View File

@@ -5,8 +5,7 @@ kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=false
modVersion=1.103.1
modVersion=1.101.2
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.19.3
mcVersion=1.18.2

View File

@@ -2,153 +2,70 @@
# Minecraft
# MC version is specified in gradle.properties, as we need that in settings.gradle.
fabric-api = "0.68.1+1.19.3"
fabric-loader = "0.14.11"
forge = "44.1.0"
forgeSpi = "6.0.0"
mixin = "0.8.5"
parchment = "2022.11.27"
parchmentMc = "1.19.2"
forge = "40.1.0"
parchment = "2022.03.13"
parchmentMc = "1.18.2"
# Normal dependencies
asm = "9.3"
autoService = "1.0.1"
checkerFramework = "3.12.0"
cobalt = "0.6.0"
fastutil = "8.5.9"
guava = "31.1-jre"
jetbrainsAnnotations = "23.0.0"
jsr305 = "3.0.2"
kotlin = "1.8.0"
kotlin-coroutines = "1.6.4"
netty = "4.1.82.Final"
nightConfig = "3.6.5"
slf4j = "1.7.36"
# Minecraft mods
forgeConfig = "5.0.4"
iris = "1.19.3-v1.4.6"
jei = "11.3.0.262"
modmenu = "5.0.1"
oculus = "1.2.5"
rei = "10.0.578"
rubidium = "0.6.1"
sodium = "mc1.19.3-0.4.6"
kotlin = "1.7.10"
kotlin-coroutines = "1.6.0"
# Testing
byteBuddy = "1.12.19"
hamcrest = "2.2"
jqwik = "1.7.0"
junit = "5.9.1"
# Build tools
cctJavadoc = "1.6.0"
cctJavadoc = "1.5.2"
checkstyle = "10.3.4"
curseForgeGradle = "1.0.11"
errorProne-core = "2.14.0"
errorProne-plugin = "2.0.2"
fabric-loom = "1.0-SNAPSHOT"
forgeGradle = "5.1.+"
githubRelease = "2.2.12"
ideaExt = "1.1.6"
illuaminate = "0.1.0-13-g689d73d"
illuaminate = "0.1.0-20-g8c483a4"
librarian = "1.+"
minotaur = "2.+"
mixinGradle = "0.7.+"
nullAway = "0.9.9"
quiltflower = "1.7.3"
shadow = "7.1.2"
spotless = "6.8.0"
taskTree = "2.1.0"
vanillaGradle = "0.2.1-SNAPSHOT"
[libraries]
# Normal dependencies
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
cobalt = { module = "org.squiddev:Cobalt", version.ref = "cobalt" }
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
netty-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
nightConfig-core = { module = "com.electronwill.night-config:core", version.ref = "nightConfig" }
nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref = "nightConfig" }
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
# Minecraft mods
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
forgeConfig = { module = "fuzs.forgeconfigapiport:forgeconfigapiport-fabric", version.ref = "forgeConfig" }
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
jei-api = { module = "mezz.jei:jei-1.19.2-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.19.2-fabric", version.ref = "jei" }
jei-forge = { module = "mezz.jei:jei-1.19.2-forge", version.ref = "jei" }
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
rei-api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" }
rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" }
rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
# Testing
byteBuddyAgent = { module ="net.bytebuddy:byte-buddy-agent", version.ref = "byteBuddy" }
byteBuddy = { module ="net.bytebuddy:byte-buddy", version.ref = "byteBuddy" }
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
# Build tools
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
errorProne-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "errorProne-core" }
errorProne-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "errorProne-core" }
errorProne-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne-core" }
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
quiltflower = { module = "io.github.juuxel:loom-quiltflower", version.ref = "quiltflower" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
[plugins]
curseForgeGradle = { id = "net.darkhax.curseforgegradle", version.ref = "curseForgeGradle" }
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" }
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
curseForgeGradle = { id = "net.darkhax.curseforgegradle", version.ref = "curseForgeGradle" }
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" }
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
[bundles]
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
# Minecraft
externalMods-common = ["jei-api", "forgeConfig", "nightConfig-core", "nightConfig-toml"]
externalMods-forge-compile = ["oculus", "jei-api"]
externalMods-forge-runtime = []
externalMods-fabric = ["fabric-loader", "fabric-api", "forgeConfig", "nightConfig-core", "nightConfig-toml"]
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"]
externalMods-fabric-runtime = ["modmenu"]
# Testing
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]

View File

@@ -2,26 +2,25 @@
(sources
/doc/
/projects/forge/build/docs/luaJavadoc/
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/
/projects/core/src/test/resources/test-rom
/projects/web/src/mount)
/build/docs/luaJavadoc/
/src/main/resources/*/computercraft/lua/bios.lua
/src/main/resources/*/computercraft/lua/rom/
/src/test/resources/test-rom
/src/web/mount)
(doc
; Also defined in projects/web/build.gradle.kts
(destination /projects/web/build/illuaminate)
(destination build/illuaminate)
(index doc/index.md)
(site
(title "CC: Tweaked")
(logo projects/common/src/main/resources/pack.png)
(logo src/main/resources/pack.png)
(url https://tweaked.cc/)
(source-link https://github.com/cc-tweaked/CC-Tweaked/blob/${commit}/${path}#L${line})
(styles /projects/web/src/styles.css)
(scripts /projects/web/build/rollup/index.js)
(styles src/web/styles.css)
(scripts build/rollup/index.js)
(head doc/head.html))
(module-kinds
@@ -33,15 +32,15 @@
(library-path
/doc/stub/
/projects/forge/build/docs/luaJavadoc/
/build/docs/luaJavadoc/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/
/src/main/resources/*/computercraft/lua/rom/apis/
/src/main/resources/*/computercraft/lua/rom/apis/command/
/src/main/resources/*/computercraft/lua/rom/apis/turtle/
/projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/
/projects/core/src/main/resources/data/computercraft/lua/rom/modules/command/
/projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/))
/src/main/resources/*/computercraft/lua/rom/modules/main/
/src/main/resources/*/computercraft/lua/rom/modules/command/
/src/main/resources/*/computercraft/lua/rom/modules/turtle/))
(at /
(linters
@@ -81,31 +80,31 @@
;; We disable the unused global linter in bios.lua and the APIs. In the future
;; hopefully we'll get illuaminate to handle this.
(at
(/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/)
(/src/main/resources/*/computercraft/lua/bios.lua
/src/main/resources/*/computercraft/lua/rom/apis/)
(linters -var:unused-global)
(lint (allow-toplevel-global true)))
;; Silence some variable warnings in documentation stubs.
(at (/doc/stub/ /projects/forge/build/docs/luaJavadoc/)
(at (/doc/stub/ /build/docs/luaJavadoc/)
(linters -var:unused-global)
(lint (allow-toplevel-global true)))
;; Suppress warnings for currently undocumented modules.
(at
(; Lua APIs
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/io.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/window.lua)
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
/src/main/resources/*/computercraft/lua/rom/apis/window.lua)
(linters -doc:undocumented -doc:undocumented-arg -doc:undocumented-return))
;; Suppress warnings for various APIs using its own deprecated members.
(at
(/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua)
(/src/main/resources/*/computercraft/lua/bios.lua
/src/main/resources/*/computercraft/lua/rom/apis/turtle/turtle.lua)
(linters -var:deprecated))
(at /projects/core/src/test/resources/test-rom
(at /src/test/resources/test-rom
; We should still be able to test deprecated members.
(linters -var:deprecated)
@@ -114,4 +113,4 @@
:max sleep write
cct_test describe expect howlci fail it pending stub before_each)))
(at /projects/web/src/mount/expr_template.lua (lint (globals :max __expr__)))
(at /src/web/mount/expr_template.lua (lint (globals :max __expr__)))

View File

@@ -1,159 +0,0 @@
# Architecture
CC: Tweaked has a rather complex project layout, as there's several use-cases we want to support (multiple mod loaders,
usable outside of Minecraft). As such, it can be tricky to understand how the code is structured and how the various
sub-projects interact. This document provides a high-level overview of the entire mod.
## Project Outline
CC: Tweaked is split into 4 primary modules (`core`, `common`, `fabric`, `forge`). These themselves are then split into
a public API (i.e `core-api`) and the actual implementation (i.e. `core`).
- `core`: This contains the core "computer" part of ComputerCraft, such as the Lua VM, filesystem and builtin APIs.
This is also where the Lua ROM is located (`projects/core/src/main/resources/data/computercraft/lua`). Notably this
project does _not_ depend on Minecraft, making it possible to use it in emulators and other tooling.
- `common`: This contains all non mod-loader-specific Minecraft code. This is where computers, turtles and peripherals
are defined (and everything else Minecraft-related!).
This project is separates client code into its own separate source set (suitably named `client`). This helps us
ensure that server code can never reference client-only code (such as LWJGL).
- `forge` and `fabric`: These contain any mod-loader specific code.
When we need to call loader-specific code from our own code (for instance, sending network messages or firing
loader-specific events), we use a `PlatformHelper` interface (defined in
`projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java`). This abstracts over most
loader-specific code we need to use, and is then implemented by each mod-loader-specific project. The concrete
implementation is then loaded with Java's [`ServiceLoader`][ServiceLoader], in a design based on [jaredlll08's
multi-loader template][MultiLoader-Template]. We use a similar system for communicating between the API and its
implementation.
```mermaid
flowchart LR
subgraph Common
platform(PlatformHelper)
impl[AbstractComputerCraftAPI]
end
subgraph API
api(ComputerCraft API) --> impl
end
subgraph Forge[Forge]
platform --> forgePlatform[PlatformHelperImpl]
impl -.-> forgeImpl[ComputerCraftAPIImpl]
end
subgraph Fabric
platform --> fabricPlatform[PlatformHelperImpl]
impl -.-> fabricImpl[ComputerCraftAPIImpl]
end
```
Note the `PlatformHelper` is only used when calling from our code into loader-specific code. While we use this to _fire_
events, we do not use it to _subscribe_ to events. For that we just subscribe to the events in the loader-specific
project, and then dispatch to the common `CommonHooks` (for shared code) and `ClientHooks` (for client-specific code).
You may notice there's a couple of other, smaller modules in the codebase. These you can probably ignore, but are worth
mentioning:
- `lints`: This defines an [ErrorProne] plugin which adds a couple of compile-time checks to our code. This is what
enforces that no client-specific code is used inside the `main` source set (and a couple of other things!).
- `web`: This contains the additional tooling for building [the documentation website][tweaked.cc], such as support for
rendering recipes
- `buildSrc` (in the base directory, not in `projects/`): This contains any build logic shared between modules. For
instance, `cc-tweaked.java-convention.gradle.kts` sets up the defaults for Java that we use across the whole project.
> **Note**
> The Forge and Fabric modules (and their API counterparts) depend on the common modules. However, in order to correctly
> process mixins we need to compile the common code along with the Forge/Fabric code. This leads to a slightly strange
> build process:
>
> - In your IDE, Forge/Fabric depend on the common as normal.
> - When building via Gradle, the common code is compiled alongside Forge/Fabric.
>
> You shouldn't need to worry about this - it should all be set up automatically - but hopefully explains a little bit
> why our Gradle scripts are slightly odd!
## Testing
CC: Tweaked has a small (though growing!) test suite to ensure various features behave correctly. Most tests are written
in Java using [JUnit], though we also make use of [jqwik] for property testing.
### Test Fixtures
Some projects define an additional `testFixtures` folder alongside their main `test` code (i.e.
`projects/core/src/testFixtures`). This source set contains test-related code which might be consumed in dependent
projects. For instance, core's test fixtures defines additional [Hamcrest] matchers, which are used in both `core` and
`common`'s test suite.
Test fixtures may also define [Test Interfaces]. This is a pattern for writing tests to ensure that an implementation
obeys its interface's contract. For instance, we might have a `ListContract` test, which asserts an abstract list
behaves as expected:
```java
interface ListContract<T extends List<Integer>> {
T newList();
@Test
default void testAddInsert() {
var list = newList();
assertTrue(list.add(123));
assertTrue(list.contains(123));
}
}
```
We can then use this interface to create tests for a specific implementation:
```java
class ArrayListTest implements ListContract<ArrayList<Integer>> {
@Override public ArrayList<Integer> newList() { return new ArrayList<>(); }
}
```
This is especially useful when testing `PlatformHelper` and other mod loader abstractions.
### Lua tests
While the majority of CC: Tweaked is written in Java, a significant portion of the code is written in Lua. As such, it's
also useful to test that.
This is done by starting a Lua VM with all of ComputerCraft's APIs loaded, then starting a custom test framework
(`mcfly.lua`). This test framework discovers tests and sends them back to the Java side. These are turned into JUnit
tests which are then in turn run on the computer again. This allows the tests to integrate with existing Java testing
tooling (for instance, XML test reports and IDE integration).
There's a slightly more detailed description of the process at `ComputerTestDelegate.java`.
### Game tests
CC: Tweaked also runs several tests in-game using Minecraft's [gametest framework][mc-test]. These work by starting
a Minecraft server and then, for each test, spawning a structure and then interacting with the blocks inside the
structure, asserting they behave as expected.
Unlike most of our other tests, these are written in Kotlin. We make extensive use of [extension methods] to augment
vanilla's own test classes, which helps give a more consistent feel to the API.
Each test works by defining a sequence of steps. Each step can either run an action (`thenExecute`), sleep for a period
(`thenIdle`) or sleep until a condition is met (`thenWaitUntil`).
```kotlin
fun Some_test(context: GameTestHelper) = context.sequence {
thenExecute { context.setBlock(BlockPos(2, 2, 2), Blocks.AIR) }
thenIdle(4)
thenExecute { context.assertBlockHas(lamp, RedstoneLampBlock.LIT, false, "Lamp should not be lit") }
}
```
Some tests need to use Lua APIs from a computer, such as when testing `turtle.dig`. In order to do this, we install
a custom "Lua" runtime (see `ManagedComputers.kt`) which actually runs Java functions. Tests can then enqueue a function
to run on a particular computer and then wait for it to finish.
While the internals of this is quite complex, it ends up being a much nicer workflow than writing parts of the test in
Lua. It also ends up being much more efficient, which is important when running a dozen tests at once!
[MultiLoader-Template]: https://github.com/jaredlll08/MultiLoader-Template/ "MultiLoader-Template - A template for a Forge + Fabric project setup using a Common source set."
[ServiceLoader]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html "ServiceLoader (Java SE 17 and JDK 17)"
[ErrorProne]: https://errorprone.info/ "ErrorProne"
[tweaked.cc]: https://tweaked.cc "CC: Tweaked"
[JUnit]: https://junit.org/junit5/ "JUnit 5"
[jqwik]: https://jqwik.net/
[Hamcrest]: https://hamcrest.org/JavaHamcrest/ "Java Hamcrest"
[Test Interfaces]: https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-interfaces-and-default-methods
[mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg "Testing Minecraft in Minecraft on YouTube"
[extension methods]: https://kotlinlang.org/docs/extensions.html "Extensions | Kotlin"

View File

@@ -1,20 +0,0 @@
plugins {
id("cc-tweaked.java-convention")
id("cc-tweaked.publishing")
id("cc-tweaked.vanilla")
}
java {
withJavadocJar()
}
dependencies {
api(project(":core-api"))
}
tasks.javadoc {
include("dan200/computercraft/api/**/*.java")
// Include the core-api in our javadoc export. This is wrong, but it means we can export a single javadoc dump.
source(project(":core-api").sourceSets.main.map { it.allJava })
}

View File

@@ -1,35 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.client;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
public final class ComputerCraftAPIClient {
private ComputerCraftAPIClient() {
}
/**
* Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
* <p>
* This may be called at any point after registry creation, though it is recommended to call it within your client
* setup step.
*
* @param serialiser The turtle upgrade serialiser.
* @param modeller The upgrade modeller.
* @param <T> The type of the turtle upgrade.
*/
public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
}
private static ComputerCraftAPIClientService getInstance() {
return ComputerCraftAPIClientService.get();
}
}

View File

@@ -1,57 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.client;
import com.mojang.math.Transformation;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/**
* A model to render, combined with a transformation matrix to apply.
*/
public final class TransformedModel {
private final BakedModel model;
private final Transformation matrix;
public TransformedModel(BakedModel model, Transformation matrix) {
this.model = Objects.requireNonNull(model);
this.matrix = Objects.requireNonNull(matrix);
}
public TransformedModel(BakedModel model) {
this.model = Objects.requireNonNull(model);
matrix = Transformation.identity();
}
public static TransformedModel of(ModelResourceLocation location) {
var modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel(modelManager.getModel(location));
}
public static TransformedModel of(ResourceLocation location) {
var modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel(ClientPlatformHelper.get().getModel(modelManager, location));
}
public static TransformedModel of(ItemStack item, Transformation transform) {
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(item);
return new TransformedModel(model, transform);
}
public BakedModel getModel() {
return model;
}
public Transformation getMatrix() {
return matrix;
}
}

View File

@@ -1,76 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.client.turtle;
import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
/**
* Provides models for a {@link ITurtleUpgrade}.
*
* @param <T> The type of turtle upgrade this modeller applies to.
* @see ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller) To register a modeller.
*/
public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
/**
* Obtain the model to be used when rendering a turtle peripheral.
* <p>
* When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
*
* @param upgrade The upgrade that you're getting the model for.
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models!
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade.
*/
TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
/**
* A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
* crafting item}.
* <p>
* This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
* model type. It will not appear correct for 3D models with additional depth, such as blocks.
*
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
@SuppressWarnings("unchecked")
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.FLAT_ITEM;
}
/**
* Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
*
* @param left The model to use on the left.
* @param right The model to use on the right.
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}
/**
* Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
*
* @param left The model to use on the left.
* @param right The model to use on the right.
* @param <T> The type of the turtle upgrade.
* @return The constructed modeller.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
}
}

View File

@@ -1,32 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.client.turtle;
import com.mojang.math.Transformation;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import org.joml.Matrix4f;
class TurtleUpgradeModellers {
private static final Transformation leftTransform = getMatrixFor(-0.40625f);
private static final Transformation rightTransform = getMatrixFor(0.40625f);
private static Transformation getMatrixFor(float offset) {
var matrix = new Matrix4f();
matrix.set(new float[]{
0.0f, 0.0f, -1.0f, 1.0f + offset,
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
});
matrix.transpose();
return new Transformation(matrix);
}
static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->
TransformedModel.of(upgrade.getCraftingItem(), side == TurtleSide.LEFT ? leftTransform : rightTransform);
}

View File

@@ -1,46 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl.client;
import dan200.computercraft.impl.Services;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
@ApiStatus.Internal
public interface ClientPlatformHelper {
/**
* Equivalent to {@link ModelManager#getModel(ModelResourceLocation)} but for arbitrary {@link ResourceLocation}s.
*
* @param manager The model manager.
* @param location The model location.
* @return The baked model.
*/
BakedModel getModel(ModelManager manager, ResourceLocation location);
static ClientPlatformHelper get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
}
final class Instance {
static final @Nullable ClientPlatformHelper INSTANCE;
static final @Nullable Throwable ERROR;
static {
var helper = Services.tryLoad(ClientPlatformHelper.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance() {
}
}
}

View File

@@ -1,44 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl.client;
import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.Services;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
/**
* Backing interface for {@link ComputerCraftAPIClient}
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
public interface ComputerCraftAPIClientService {
static ComputerCraftAPIClientService get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ComputerCraftAPIClientService.class, Instance.ERROR) : instance;
}
<T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
final class Instance {
static final @Nullable ComputerCraftAPIClientService INSTANCE;
static final @Nullable Throwable ERROR;
static {
var helper = Services.tryLoad(ComputerCraftAPIClientService.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance() {
}
}
}

View File

@@ -1,203 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
/**
* The static entry point to the ComputerCraft API.
* <p>
* Members in this class must be called after ComputerCraft has been initialised, but may be called before it is
* fully loaded.
*/
public final class ComputerCraftAPI {
public static final String MOD_ID = "computercraft";
public static String getInstalledVersion() {
return getInstance().getInstalledVersion();
}
/**
* Creates a numbered directory in a subfolder of the save directory for a given world, and returns that number.
* <p>
* Use in conjunction with createSaveDirMount() to create a unique place for your peripherals or media items to store files.
*
* @param server The server for which the save dir should be created.
* @param parentSubPath The folder path within the save directory where the new directory should be created. eg: "computercraft/disk"
* @return The numerical value of the name of the new folder, or -1 if the folder could not be created for some reason.
* <p>
* eg: if createUniqueNumberedSaveDir( world, "computer/disk" ) was called returns 42, then "computer/disk/42" is now
* available for writing.
* @see #createSaveDirMount(MinecraftServer, String, long)
*/
public static int createUniqueNumberedSaveDir(MinecraftServer server, String parentSubPath) {
return getInstance().createUniqueNumberedSaveDir(server, parentSubPath);
}
/**
* Creates a file system mount that maps to a subfolder of the save directory for a given world, and returns it.
* <p>
* Use in conjunction with Use {@link IComputerAccess#mount(String, Mount)} or {@link IComputerAccess#mountWritable(String, WritableMount)}
* to mount this on a computer's file system.
* <p>
* If the same folder may be mounted on multiple computers at once (for instance, if you provide a network file share),
* the same mount instance should be used for all computers. You should NOT have multiple mount instances for the
* same folder.
*
* @param server The server which the save dir can be found.
* @param subPath The folder path within the save directory that the mount should map to. eg: "disk/42".
* Use {@link #createUniqueNumberedSaveDir(MinecraftServer, String)} to create a new numbered folder
* to use.
* @param capacity The amount of data that can be stored in the directory before it fills up, in bytes.
* @return The newly created mount.
* @see #createUniqueNumberedSaveDir(MinecraftServer, String)
* @see IComputerAccess#mount(String, Mount)
* @see IComputerAccess#mountWritable(String, WritableMount)
* @see Mount
* @see WritableMount
*/
public static WritableMount createSaveDirMount(MinecraftServer server, String subPath, long capacity) {
return getInstance().createSaveDirMount(server, subPath, capacity);
}
/**
* Creates a file system mount to a resource folder, and returns it.
* <p>
* Use in conjunction with {@link IComputerAccess#mount} or {@link IComputerAccess#mountWritable} to mount a
* resource folder onto a computer's file system.
* <p>
* The files in this mount will be a combination of files in all mod jar, and data packs that contain
* resources with the same domain and path. For instance, ComputerCraft's resources are stored in
* "/data/computercraft/lua/rom". We construct a mount for that with
* {@code createResourceMount("computercraft", "lua/rom")}.
*
* @param server The current Minecraft server, from which to read resources from.
* @param domain The domain under which to look for resources. eg: "mymod".
* @param subPath The subPath under which to look for resources. eg: "lua/myfiles".
* @return The mount, or {@code null} if it could be created for some reason.
* @see IComputerAccess#mount(String, Mount)
* @see IComputerAccess#mountWritable(String, WritableMount)
* @see Mount
*/
@Nullable
public static Mount createResourceMount(MinecraftServer server, String domain, String subPath) {
return getInstance().createResourceMount(server, domain, subPath);
}
/**
* Registers a method source for generic peripherals.
*
* @param source The method source to register.
* @see GenericSource
*/
public static void registerGenericSource(GenericSource source) {
getInstance().registerGenericSource(source);
}
/**
* Registers a bundled redstone provider to provide bundled redstone output for blocks.
*
* @param provider The bundled redstone provider to register.
* @see BundledRedstoneProvider
*/
public static void registerBundledRedstoneProvider(BundledRedstoneProvider provider) {
getInstance().registerBundledRedstoneProvider(provider);
}
/**
* If there is a Computer or Turtle at a certain position in the world, get it's bundled redstone output.
*
* @param world The world this block is in.
* @param pos The position this block is at.
* @param side The side to extract the bundled redstone output from.
* @return If there is a block capable of emitting bundled redstone at the location, it's signal (0-65535) will be returned.
* If there is no block capable of emitting bundled redstone at the location, -1 will be returned.
* @see BundledRedstoneProvider
*/
public static int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side) {
return getInstance().getBundledRedstoneOutput(world, pos, side);
}
/**
* Registers a media provider to provide {@link IMedia} implementations for Items.
*
* @param provider The media provider to register.
* @see MediaProvider
*/
public static void registerMediaProvider(MediaProvider provider) {
getInstance().registerMediaProvider(provider);
}
/**
* Attempt to get the game-wide wireless network.
*
* @param server The current Minecraft server.
* @return The global wireless network, or {@code null} if it could not be fetched.
*/
public static PacketNetwork getWirelessNetwork(MinecraftServer server) {
return getInstance().getWirelessNetwork(server);
}
/**
* Register a custom {@link ILuaAPI}, which may be added onto all computers without requiring a peripheral.
* <p>
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
* to use peripherals to provide functionality to users.
*
* @param factory The factory for your API subclass.
* @see ILuaAPIFactory
*/
public static void registerAPIFactory(ILuaAPIFactory factory) {
getInstance().registerAPIFactory(factory);
}
/**
* Construct a new wired node for a given wired element.
*
* @param element The element to construct it for
* @return The element's node
* @see WiredElement#getNode()
*/
public static WiredNode createWiredNodeForElement(WiredElement element) {
return getInstance().createWiredNodeForElement(element);
}
/**
* Register a refuel handler for turtles. This may be used to provide alternative fuel sources, such as consuming RF
* batteries.
*
* @param handler The turtle refuel handler.
* @see TurtleRefuelHandler#refuel(ITurtleAccess, ItemStack, int, int)
*/
public static void registerRefuelHandler(TurtleRefuelHandler handler) {
getInstance().registerRefuelHandler(handler);
}
private static ComputerCraftAPIService getInstance() {
return ComputerCraftAPIService.get();
}
}

View File

@@ -1,72 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
/**
* Tags provided by ComputerCraft.
*/
public class ComputerCraftTags {
public static class Items {
public static final TagKey<Item> COMPUTER = make("computer");
public static final TagKey<Item> TURTLE = make("turtle");
public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
public static final TagKey<Item> MONITOR = make("monitor");
/**
* Items which can be {@linkplain Item#use(Level, Player, InteractionHand) used} when calling
* {@code turtle.place()}.
* <p>
* This does not cover items who handle placing inside {@link Item#useOn(UseOnContext)}, as that is always
* called.
*/
public static final TagKey<Item> TURTLE_CAN_PLACE = make("turtle_can_place");
private static TagKey<Item> make(String name) {
return TagKey.create(Registries.ITEM, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
}
}
public static class Blocks {
public static final TagKey<Block> COMPUTER = make("computer");
public static final TagKey<Block> TURTLE = make("turtle");
public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
public static final TagKey<Block> MONITOR = make("monitor");
/**
* Blocks which can be broken by any turtle tool.
*/
public static final TagKey<Block> TURTLE_ALWAYS_BREAKABLE = make("turtle_always_breakable");
/**
* Blocks which can be broken by the default shovel tool.
*/
public static final TagKey<Block> TURTLE_SHOVEL_BREAKABLE = make("turtle_shovel_harvestable");
/**
* Blocks which can be broken with the default sword tool.
*/
public static final TagKey<Block> TURTLE_SWORD_BREAKABLE = make("turtle_sword_harvestable");
/**
* Blocks which can be broken with the default hoe tool.
*/
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
private static TagKey<Block> make(String name) {
return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
}
}
}

View File

@@ -1,75 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.detail;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* An item detail provider for {@link ItemStack}'s whose {@link Item} has a specific type.
*
* @param <T> The type the stack's item must have.
*/
public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemStack> {
private final Class<T> itemType;
private final @Nullable String namespace;
/**
* Create a new item detail provider. Meta will be inserted into a new sub-map named as per {@code namespace}.
*
* @param itemType The type the stack's item must have.
* @param namespace The namespace to use for this provider.
*/
public BasicItemDetailProvider(@Nullable String namespace, Class<T> itemType) {
Objects.requireNonNull(itemType);
this.itemType = itemType;
this.namespace = namespace;
}
/**
* Create a new item detail provider. Meta will be inserted directly into the results.
*
* @param itemType The type the stack's item must have.
*/
public BasicItemDetailProvider(Class<T> itemType) {
this(null, itemType);
}
/**
* Provide additional details for the given {@link Item} and {@link ItemStack}. This method is called by
* {@code turtle.getItemDetail()}. New properties should be added to the given {@link Map}, {@code data}.
* <p>
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
* take care to avoid long blocking operations as this will stall the server and other computers.
*
* @param data The full details to be returned for this item stack. New properties should be added to this map.
* @param stack The item stack to provide details for.
* @param item The item to provide details for.
*/
public abstract void provideDetails(
Map<? super String, Object> data, ItemStack stack, T item
);
@Override
public void provideDetails(Map<? super String, Object> data, ItemStack stack) {
var item = stack.getItem();
if (!itemType.isInstance(item)) return;
// If `namespace` is specified, insert into a new data map instead of the existing one.
Map<? super String, Object> child = namespace == null ? data : new HashMap<>();
provideDetails(child, stack, itemType.cast(item));
if (namespace != null) {
data.put(namespace, child);
}
}
}

View File

@@ -1,33 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.detail;
import java.util.Map;
/**
* Provide details about a block, fluid, or item.
* <p>
* When implementing this interface, be careful to only expose information the player can see through normal gameplay.
* Computers shouldn't break progression or mechanics of other mods.
*
* @param <T> The type of object that this provider can provide details for.
* @see DetailRegistry
*/
@FunctionalInterface
public interface DetailProvider<T> {
/**
* Provide additional details for the given object. This method is called by functions such as
* {@code turtle.getItemDetail()} and {@code turtle.inspect()}. New properties should be added to the given
* {@link Map}, {@code data}.
* <p>
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
* take care to avoid long blocking operations as this will stall the server and other computers.
*
* @param data The full details to be returned. New properties should be added to this map.
* @param object The object to provide details for.
*/
void provideDetails(Map<? super String, Object> data, T object);
}

View File

@@ -1,31 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.detail;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
/**
* {@link DetailRegistry}s for built-in Minecraft types.
*/
public class VanillaDetailRegistries {
/**
* Provides details for {@link ItemStack}s.
* <p>
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe (assuming the stack is immutable)
* and may be called from the computer thread.
*/
public static final DetailRegistry<ItemStack> ITEM_STACK = ComputerCraftAPIService.get().getItemStackDetailRegistry();
/**
* Provides details for {@link BlockReference}, a reference to a {@link Block} in the world.
* <p>
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe and may be called from the computer
* thread.
*/
public static final DetailRegistry<BlockReference> BLOCK_IN_WORLD = ComputerCraftAPIService.get().getBlockInWorldDetailRegistry();
}

View File

@@ -1,86 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.media;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
/**
* Represents an item that can be placed in a disk drive and used by a Computer.
* <p>
* Implement this interface on your {@link Item} class to allow it to be used in the drive. Alternatively, register
* a {@link MediaProvider}.
*/
public interface IMedia {
/**
* Get a string representing the label of this item. Will be called via {@code disk.getLabel()} in lua.
*
* @param stack The {@link ItemStack} to inspect.
* @return The label. ie: "Dan's Programs".
*/
@Nullable
String getLabel(ItemStack stack);
/**
* Set a string representing the label of this item. Will be called vi {@code disk.setLabel()} in lua.
*
* @param stack The {@link ItemStack} to modify.
* @param label The string to set the label to.
* @return true if the label was updated, false if the label may not be modified.
*/
default boolean setLabel(ItemStack stack, @Nullable String label) {
return false;
}
/**
* If this disk represents an item with audio (like a record), get the readable name of the audio track. ie:
* "Jonathan Coulton - Still Alive"
*
* @param stack The {@link ItemStack} to modify.
* @return The name, or null if this item does not represent an item with audio.
*/
@Nullable
default String getAudioTitle(ItemStack stack) {
return null;
}
/**
* If this disk represents an item with audio (like a record), get the resource name of the audio track to play.
*
* @param stack The {@link ItemStack} to modify.
* @return The name, or null if this item does not represent an item with audio.
*/
@Nullable
default SoundEvent getAudio(ItemStack stack) {
return null;
}
/**
* If this disk represents an item with data (like a floppy disk), get a mount representing it's contents. This will
* be mounted onto the filesystem of the computer while the media is in the disk drive.
*
* @param stack The {@link ItemStack} to modify.
* @param level The world in which the item and disk drive reside.
* @return The mount, or null if this item does not represent an item with data. If the mount returned also
* implements {@link WritableMount}, it will mounted using mountWritable()
* @see Mount
* @see WritableMount
* @see ComputerCraftAPI#createSaveDirMount(MinecraftServer, String, long)
* @see ComputerCraftAPI#createResourceMount(MinecraftServer, String, String)
*/
@Nullable
default Mount createDataMount(ItemStack stack, ServerLevel level) {
return null;
}
}

View File

@@ -1,28 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.media;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
/**
* This interface is used to provide {@link IMedia} implementations for {@link ItemStack}.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
*/
@FunctionalInterface
public interface MediaProvider {
/**
* Produce an IMedia implementation from an ItemStack.
*
* @param stack The stack from which to extract the media information.
* @return An {@link IMedia} implementation, or {@code null} if the item is not something you wish to handle
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
*/
@Nullable
IMedia getMedia(ItemStack stack);
}

View File

@@ -1,29 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network;
/**
* Represents a packet which may be sent across a {@link PacketNetwork}.
*
* @param channel The channel to send the packet along. Receiving devices should only process packets from on
* channels they are listening to.
* @param replyChannel The channel to reply on.
* @param payload The contents of this packet. This should be a "valid" Lua object, safe for queuing as an
* event or returning from a peripheral call.
* @param sender The object which sent this packet.
* @see PacketSender
* @see PacketNetwork#transmitSameDimension(Packet, double)
* @see PacketNetwork#transmitInterdimensional(Packet)
* @see PacketReceiver#receiveDifferentDimension(Packet)
* @see PacketReceiver#receiveSameDimension(Packet, double)
*/
public record Packet(
int channel,
int replyChannel,
Object payload,
PacketSender sender
) {
}

View File

@@ -1,57 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network;
/**
* A packet network represents a collection of devices which can send and receive packets.
*
* @see Packet
* @see PacketReceiver
*/
public interface PacketNetwork {
/**
* Add a receiver to the network.
*
* @param receiver The receiver to register to the network.
*/
void addReceiver(PacketReceiver receiver);
/**
* Remove a receiver from the network.
*
* @param receiver The device to remove from the network.
*/
void removeReceiver(PacketReceiver receiver);
/**
* Determine whether this network is wireless.
*
* @return Whether this network is wireless.
*/
boolean isWireless();
/**
* Submit a packet for transmitting across the network. This will route the packet through the network, sending it
* to all receivers within range (or any interdimensional ones).
*
* @param packet The packet to send.
* @param range The maximum distance this packet will be sent.
* @see #transmitInterdimensional(Packet)
* @see PacketReceiver#receiveSameDimension(Packet, double)
*/
void transmitSameDimension(Packet packet, double range);
/**
* Submit a packet for transmitting across the network. This will route the packet through the network, sending it
* to all receivers across all dimensions.
*
* @param packet The packet to send.
* @see #transmitSameDimension(Packet, double)
* @see PacketReceiver#receiveDifferentDimension(Packet)
*/
void transmitInterdimensional(Packet packet);
}

View File

@@ -1,80 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
/**
* An object on an {@link PacketNetwork}, capable of receiving packets.
*/
public interface PacketReceiver {
/**
* Get the world in which this packet receiver exists.
*
* @return The receivers's world.
*/
Level getLevel();
/**
* Get the position in the world at which this receiver exists.
*
* @return The receiver's position.
*/
Vec3 getPosition();
/**
* Get the maximum distance this receiver can send and receive messages.
* <p>
* When determining whether a receiver can receive a message, the largest distance of the packet and receiver is
* used - ensuring it is within range. If the packet or receiver is inter-dimensional, then the packet will always
* be received.
*
* @return The maximum distance this device can send and receive messages.
* @see #isInterdimensional()
* @see #receiveSameDimension(Packet packet, double)
* @see PacketNetwork#transmitInterdimensional(Packet)
*/
double getRange();
/**
* Determine whether this receiver can receive packets from other dimensions.
* <p>
* A device will receive an inter-dimensional packet if either it or the sending device is inter-dimensional.
*
* @return Whether this receiver receives packets from other dimensions.
* @see #getRange()
* @see #receiveDifferentDimension(Packet)
* @see PacketNetwork#transmitInterdimensional(Packet)
*/
boolean isInterdimensional();
/**
* Receive a network packet from the same dimension.
*
* @param packet The packet to receive. Generally you should check that you are listening on the given channel and,
* if so, queue the appropriate modem event.
* @param distance The distance this packet has travelled from the source.
* @see Packet
* @see #getRange()
* @see PacketNetwork#transmitSameDimension(Packet, double)
* @see PacketNetwork#transmitInterdimensional(Packet)
*/
void receiveSameDimension(Packet packet, double distance);
/**
* Receive a network packet from a different dimension.
*
* @param packet The packet to receive. Generally you should check that you are listening on the given channel and,
* if so, queue the appropriate modem event.
* @see Packet
* @see PacketNetwork#transmitInterdimensional(Packet)
* @see PacketNetwork#transmitSameDimension(Packet, double)
* @see #isInterdimensional()
*/
void receiveDifferentDimension(Packet packet);
}

View File

@@ -1,37 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
/**
* An object on a {@link PacketNetwork}, capable of sending packets.
*/
public interface PacketSender {
/**
* Get the world in which this packet sender exists.
*
* @return The sender's world.
*/
Level getLevel();
/**
* Get the position in the world at which this sender exists.
*
* @return The sender's position.
*/
Vec3 getPosition();
/**
* Get some sort of identification string for this sender. This does not strictly need to be unique, but you
* should be able to extract some identifiable information from it.
*
* @return This device's id.
*/
String getSenderID();
}

View File

@@ -1,31 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.ComputerCraftAPI;
/**
* An object which may be part of a wired network.
* <p>
* Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(WiredElement)}. This acts
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
* for its lifespan.
* <p>
* Elements are generally tied to a block or tile entity in world. In such as case, one should provide the
* {@link WiredElement} capability for the appropriate sides.
*/
public interface WiredElement extends WiredSender {
/**
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
* peripherals change.
*
* @param change The change which occurred.
* @see WiredNetworkChange
*/
default void networkChanged(WiredNetworkChange change) {
}
}

View File

@@ -1,83 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import java.util.Map;
/**
* A wired network is composed of one of more {@link WiredNode}s, a set of connections between them, and a series
* of peripherals.
* <p>
* Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
* there is some path between two nodes then they must be on the same network. {@link WiredNetwork} will automatically
* handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
* change.
* <p>
* This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
* it is generally preferred to use the methods provided by {@link WiredNode}.
*
* @see WiredNode#getNetwork()
*/
public interface WiredNetwork {
/**
* Create a connection between two nodes.
* <p>
* This should only be used on the server thread.
*
* @param left The first node to connect
* @param right The second node to connect
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @throws IllegalStateException If neither node is on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see WiredNode#connectTo(WiredNode)
* @see WiredNetwork#connect(WiredNode, WiredNode)
*/
boolean connect(WiredNode left, WiredNode right);
/**
* Destroy a connection between this node and another.
* <p>
* This should only be used on the server thread.
*
* @param left The first node in the connection.
* @param right The second node in the connection.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If either node is not on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see WiredNode#disconnectFrom(WiredNode)
* @see WiredNetwork#connect(WiredNode, WiredNode)
*/
boolean disconnect(WiredNode left, WiredNode right);
/**
* Sever all connections this node has, removing it from this network.
* <p>
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to remove
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see WiredNode#remove()
*/
boolean remove(WiredNode node);
/**
* Update the peripherals a node provides.
* <p>
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to attach peripherals for.
* @param peripherals The new peripherals for this node.
* @throws IllegalArgumentException If the node is not in the network.
* @see WiredNode#updatePeripherals(Map)
*/
void updatePeripherals(WiredNode node, Map<String, IPeripheral> peripherals);
}

View File

@@ -1,33 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import java.util.Map;
/**
* Represents a change to the objects on a wired network.
*
* @see WiredElement#networkChanged(WiredNetworkChange)
*/
public interface WiredNetworkChange {
/**
* A set of peripherals which have been removed. Note that there may be entries with the same name
* in the added and removed set, but with a different peripheral.
*
* @return The set of removed peripherals.
*/
Map<String, IPeripheral> peripheralsRemoved();
/**
* A set of peripherals which have been added. Note that there may be entries with the same name
* in the added and removed set, but with a different peripheral.
*
* @return The set of added peripherals.
*/
Map<String, IPeripheral> peripheralsAdded();
}

View File

@@ -1,100 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.peripheral.IPeripheral;
import java.util.Map;
/**
* Wired nodes act as a layer between {@link WiredElement}s and {@link WiredNetwork}s.
* <p>
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
* methods may be safely used on any thread.
* <p>
* When sending a packet, the system will attempt to find the shortest path between the two nodes based on their
* element's position. Note that packet senders and receivers can have different locations from their associated
* element: the distance between the two will be added to the total packet's distance.
* <p>
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
* be used on the main server thread.
*/
public interface WiredNode extends PacketNetwork {
/**
* The associated element for this network node.
*
* @return This node's element.
*/
WiredElement getElement();
/**
* The network this node is currently connected to. Note that this may change
* after any network operation, so it should not be cached.
* <p>
* This should only be used on the server thread.
*
* @return This node's network.
*/
WiredNetwork getNetwork();
/**
* Create a connection from this node to another.
* <p>
* This should only be used on the server thread.
*
* @param node The other node to connect to.
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @see WiredNetwork#connect(WiredNode, WiredNode)
* @see WiredNode#disconnectFrom(WiredNode)
*/
default boolean connectTo(WiredNode node) {
return getNetwork().connect(this, node);
}
/**
* Destroy a connection between this node and another.
* <p>
* This should only be used on the server thread.
*
* @param node The other node to disconnect from.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If {@code node} is not on the same network.
* @see WiredNetwork#disconnect(WiredNode, WiredNode)
* @see WiredNode#connectTo(WiredNode)
*/
default boolean disconnectFrom(WiredNode node) {
return getNetwork().disconnect(this, node);
}
/**
* Sever all connections this node has, removing it from this network.
* <p>
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see WiredNetwork#remove(WiredNode)
*/
default boolean remove() {
return getNetwork().remove(this);
}
/**
* Mark this node's peripherals as having changed.
* <p>
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param peripherals The new peripherals for this node.
* @see WiredNetwork#updatePeripherals(WiredNode, Map)
*/
default void updatePeripherals(Map<String, IPeripheral> peripherals) {
getNetwork().updatePeripherals(this, peripherals);
}
}

View File

@@ -1,27 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.PacketSender;
/**
* An object on a {@link WiredNetwork} capable of sending packets.
* <p>
* Unlike a regular {@link PacketSender}, this must be associated with the node you are attempting to
* to send the packet from.
*/
public interface WiredSender extends PacketSender {
/**
* The node in the network representing this object.
* <p>
* This should be used as a proxy for the main network. One should send packets
* and register receivers through this object.
*
* @return The node for this element.
*/
WiredNode getNode();
}

View File

@@ -1,47 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
/**
* A base class for {@link IPocketUpgrade}s.
* <p>
* One does not have to use this, but it does provide a convenient template.
*/
public abstract class AbstractPocketUpgrade implements IPocketUpgrade {
private final ResourceLocation id;
private final String adjective;
private final ItemStack stack;
protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) {
this.id = id;
this.adjective = adjective;
this.stack = stack;
}
protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
this(id, UpgradeBase.getDefaultAdjective(id), stack);
}
@Override
public final ResourceLocation getUpgradeID() {
return id;
}
@Override
public final String getUnlocalisedAdjective() {
return adjective;
}
@Override
public final ItemStack getCraftingItem() {
return stack;
}
}

View File

@@ -1,27 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import java.util.function.Consumer;
/**
* A data provider to generate pocket computer upgrades.
* <p>
* This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* @see PocketUpgradeSerialiser
*/
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
public PocketUpgradeDataProvider(PackOutput output) {
super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.REGISTRY_ID);
}
}

View File

@@ -1,76 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.pocket;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Reads a {@link IPocketUpgrade} from disk and reads/writes it to a network packet.
* <p>
* This follows the same format as {@link dan200.computercraft.api.turtle.TurtleUpgradeSerialiser} - consult the
* documentation there for more information.
*
* @param <T> The type of pocket computer upgrade this is responsible for serialising.
* @see IPocketUpgrade
* @see PocketUpgradeDataProvider
*/
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
*/
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
* @param factory Generate a new upgrade with a specific ID.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
final class Impl extends SimpleSerialiser<T> implements PocketUpgradeSerialiser<T> {
private Impl(Function<ResourceLocation, T> constructor) {
super(constructor);
}
}
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
final class Impl extends SerialiserWithCraftingItem<T> implements PocketUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
super(factory);
}
}
return new Impl(factory);
}
}

View File

@@ -1,31 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.redstone;
import dan200.computercraft.api.ComputerCraftAPI;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
/**
* This interface is used to provide bundled redstone output for blocks.
*
* @see ComputerCraftAPI#registerBundledRedstoneProvider(BundledRedstoneProvider)
*/
@FunctionalInterface
public interface BundledRedstoneProvider {
/**
* Produce an bundled redstone output from a block location.
*
* @param world The world this block is in.
* @param pos The position this block is at.
* @param side The side to extract the bundled redstone output from.
* @return A number in the range 0-65535 to indicate this block is providing output, or -1 if you do not wish to
* handle this block.
* @see ComputerCraftAPI#registerBundledRedstoneProvider(BundledRedstoneProvider)
*/
int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side);
}

View File

@@ -1,54 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
/**
* A base class for {@link ITurtleUpgrade}s.
* <p>
* One does not have to use this, but it does provide a convenient template.
*/
public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade {
private final ResourceLocation id;
private final TurtleUpgradeType type;
private final String adjective;
private final ItemStack stack;
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack) {
this.id = id;
this.type = type;
this.adjective = adjective;
this.stack = stack;
}
protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) {
this(id, type, UpgradeBase.getDefaultAdjective(id), stack);
}
@Override
public final ResourceLocation getUpgradeID() {
return id;
}
@Override
public final String getUnlocalisedAdjective() {
return adjective;
}
@Override
public final TurtleUpgradeType getType() {
return type;
}
@Override
public final ItemStack getCraftingItem() {
return stack;
}
}

View File

@@ -1,83 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.core.Direction;
import javax.annotation.Nullable;
/**
* The primary interface for defining an update for Turtles. A turtle update can either be a new tool, or a new
* peripheral.
* <p>
* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
* {@link TurtleUpgradeSerialiser} instance, which are then registered in a Forge registry.
* <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
* the upgrade registered internally. See the documentation in {@link TurtleUpgradeSerialiser} for details on this process
* and where files should be located.
*
* @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
*/
public interface ITurtleUpgrade extends UpgradeBase {
/**
* Return whether this turtle adds a tool or a peripheral to the turtle.
*
* @return The type of upgrade this is.
* @see TurtleUpgradeType for the differences between them.
*/
TurtleUpgradeType getType();
/**
* Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade.
* <p>
* The peripheral created will be stored for the lifetime of the upgrade and will be passed as an argument to
* {@link #update(ITurtleAccess, TurtleSide)}. It will be attached, detached and have methods called in the same
* manner as a Computer peripheral.
*
* @param turtle Access to the turtle that the peripheral is being created for.
* @param side Which side of the turtle (left or right) that the upgrade resides on.
* @return The newly created peripheral. You may return {@code null} if this upgrade is a Tool
* and this method is not expected to be called.
*/
@Nullable
default IPeripheral createPeripheral(ITurtleAccess turtle, TurtleSide side) {
return null;
}
/**
* Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
* by the turtle, and the tool is required to do some work.
* <p>
* Conforming implementations should fire loader-specific events when using the tool, for instance Forge's
* {@code AttackEntityEvent}.
*
* @param turtle Access to the turtle that the tool resides on.
* @param side Which side of the turtle (left or right) the tool resides on.
* @param verb Which action (dig or attack) the turtle is being called on to perform.
* @param direction Which world direction the action should be performed in, relative to the turtles
* position. This will either be up, down, or the direction the turtle is facing, depending on
* whether dig, digUp or digDown was called.
* @return Whether the turtle was able to perform the action, and hence whether the {@code turtle.dig()}
* or {@code turtle.attack()} lua method should return true. If true is returned, the tool will perform
* a swinging animation. You may return {@code null} if this turtle is a Peripheral and this method is not expected
* to be called.
*/
default TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) {
return TurtleCommandResult.failure();
}
/**
* Called once per tick for each turtle which has the upgrade equipped.
*
* @param turtle Access to the turtle that the upgrade resides on.
* @param side Which side of the turtle (left or right) the upgrade resides on.
*/
default void update(ITurtleAccess turtle, TurtleSide side) {
}
}

View File

@@ -1,30 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
/**
* An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(TurtleCommand)}.
*
* @see ITurtleAccess#executeCommand(TurtleCommand)
*/
@FunctionalInterface
public interface TurtleCommand {
/**
* Will be called by the turtle on the main thread when it is time to execute the custom command.
* <p>
* The handler should either perform the work of the command, and return success, or return
* failure with an error message to indicate the command cannot be executed at this time.
*
* @param turtle Access to the turtle for whom the command was issued.
* @return A result, indicating whether this action succeeded or not.
* @see ITurtleAccess#executeCommand(TurtleCommand)
* @see TurtleCommandResult#success()
* @see TurtleCommandResult#failure(String)
* @see TurtleCommandResult
*/
TurtleCommandResult execute(ITurtleAccess turtle);
}

View File

@@ -1,100 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import net.minecraft.core.Direction;
import javax.annotation.Nullable;
/**
* Used to indicate the result of executing a turtle command.
*
* @see TurtleCommand#execute(ITurtleAccess)
* @see ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)
*/
public final class TurtleCommandResult {
private static final TurtleCommandResult EMPTY_SUCCESS = new TurtleCommandResult(true, null, null);
private static final TurtleCommandResult EMPTY_FAILURE = new TurtleCommandResult(false, null, null);
/**
* Create a successful command result with no result.
*
* @return A successful command result with no values.
*/
public static TurtleCommandResult success() {
return EMPTY_SUCCESS;
}
/**
* Create a successful command result with the given result values.
*
* @param results The results of executing this command.
* @return A successful command result with the given values.
*/
public static TurtleCommandResult success(@Nullable Object[] results) {
if (results == null || results.length == 0) return EMPTY_SUCCESS;
return new TurtleCommandResult(true, null, results);
}
/**
* Create a failed command result with no error message.
*
* @return A failed command result with no message.
*/
public static TurtleCommandResult failure() {
return EMPTY_FAILURE;
}
/**
* Create a failed command result with an error message.
*
* @param errorMessage The error message to provide.
* @return A failed command result with a message.
*/
public static TurtleCommandResult failure(@Nullable String errorMessage) {
if (errorMessage == null) return EMPTY_FAILURE;
return new TurtleCommandResult(false, errorMessage, null);
}
private final boolean success;
private final @Nullable String errorMessage;
private final @Nullable Object[] results;
private TurtleCommandResult(boolean success, @Nullable String errorMessage, @Nullable Object[] results) {
this.success = success;
this.errorMessage = errorMessage;
this.results = results;
}
/**
* Determine whether the command executed successfully.
*
* @return If the command was successful.
*/
public boolean isSuccess() {
return success;
}
/**
* Get the error message of this command result.
*
* @return The command's error message, or {@code null} if it was a success.
*/
@Nullable
public String getErrorMessage() {
return errorMessage;
}
/**
* Get the resulting values of this command result.
*
* @return The command's result, or {@code null} if it was a failure.
*/
@Nullable
public Object[] getResults() {
return results;
}
}

View File

@@ -1,34 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import net.minecraft.world.item.ItemStack;
import java.util.OptionalInt;
/**
* A function called when a turtle attempts to refuel via {@code turtle.refuel()}. This may be used to provide
* alternative fuel sources, such as consuming RF batteries.
*
* @see ComputerCraftAPI#registerRefuelHandler(TurtleRefuelHandler)
*/
public interface TurtleRefuelHandler {
/**
* Refuel a turtle using an item.
*
* @param turtle The turtle to refuel.
* @param stack The stack to refuel with.
* @param slot The slot the stack resides within. This may be used to modify the inventory afterwards.
* @param limit The maximum number of refuel operations to perform. This will often correspond to the number of
* items to consume.
* <p>
* This value may be zero. In this case, you should still detect if the item can be handled (returning
* {@code OptionalInt#of(0)} if so), but should <em>NOT</em> modify the stack or inventory.
* @return The amount of fuel gained, or {@link OptionalInt#empty()} if this handler does not accept the given item.
*/
OptionalInt refuel(ITurtleAccess turtle, ItemStack stack, int slot, int limit);
}

View File

@@ -1,139 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import javax.annotation.Nullable;
import java.util.function.Consumer;
/**
* A data provider to generate turtle upgrades.
* <p>
* This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
* generate them.
*
* @see TurtleUpgradeSerialiser
*/
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool");
public TurtleUpgradeDataProvider(PackOutput output) {
super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.REGISTRY_ID);
}
/**
* Create a new turtle tool upgrade, such as a pickaxe or shovel.
*
* @param id The ID of this tool.
* @param item The item used for tool actions. Note, this doesn't inherit all properties of the tool, you may need
* to specify {@link ToolBuilder#damageMultiplier(float)} and {@link ToolBuilder#breakable(TagKey)}.
* @return A tool builder,
*/
public final ToolBuilder tool(ResourceLocation id, Item item) {
return new ToolBuilder(id, existingSerialiser(TOOL_ID), item);
}
/**
* A builder for custom turtle tool upgrades.
*
* @see #tool(ResourceLocation, Item)
*/
public static class ToolBuilder {
private final ResourceLocation id;
private final TurtleUpgradeSerialiser<?> serialiser;
private final Item toolItem;
private @Nullable String adjective;
private @Nullable Item craftingItem;
private @Nullable Float damageMultiplier = null;
private @Nullable TagKey<Block> breakable;
ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
this.id = id;
this.serialiser = serialiser;
this.toolItem = toolItem;
craftingItem = null;
}
/**
* Specify a custom adjective for this tool. By default this takes its adjective from the tool item.
*
* @param adjective The new adjective to use.
* @return The tool builder, for further use.
*/
public ToolBuilder adjective(String adjective) {
this.adjective = adjective;
return this;
}
/**
* Specify a custom item which is used to craft this upgrade. By default this is the same as the provided tool
* item, but you may wish to override it.
*
* @param craftingItem The item used to craft this upgrade.
* @return The tool builder, for further use.
*/
public ToolBuilder craftingItem(Item craftingItem) {
this.craftingItem = craftingItem;
return this;
}
/**
* The amount of damage a swing of this tool will do. This is multiplied by {@link Attributes#ATTACK_DAMAGE} to
* get the final damage.
*
* @param damageMultiplier The damage multiplier.
* @return The tool builder, for further use.
*/
public ToolBuilder damageMultiplier(float damageMultiplier) {
this.damageMultiplier = damageMultiplier;
return this;
}
/**
* Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
* in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
* be broken.
*
* @param breakable The tag containing all blocks breakable by this item.
* @return The tool builder, for further use.
* @see ComputerCraftTags.Blocks
*/
public ToolBuilder breakable(TagKey<Block> breakable) {
this.breakable = breakable;
return this;
}
/**
* Register this as an upgrade.
*
* @param add The callback given to {@link #addUpgrades(Consumer)}.
*/
public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
add.accept(new Upgrade<>(id, serialiser, s -> {
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, toolItem).toString());
if (adjective != null) s.addProperty("adjective", adjective);
if (craftingItem != null) {
s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registries.ITEM, craftingItem).toString());
}
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
if (breakable != null) s.addProperty("breakable", breakable.location().toString());
}));
}
}
}

View File

@@ -1,111 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Reads a {@link ITurtleUpgrade} from disk and reads/writes it to a network packet.
* <p>
* These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
* <p>
* If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
*
* <h2>Example (Forge)</h2>
* <pre>{@code
* static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
*
* // Register a new upgrade serialiser called "my_upgrade".
* public static final RegistryObject<TurtleUpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
* SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
*
* // Then in your constructor
* SERIALISERS.register( bus );
* }</pre>
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
*
* <pre>{@code
* {
* "type": my_mod:my_upgrade",
* }
* }</pre>
* <p>
* Finally, we need to register a model for our upgrade. This is done with
* {@link dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller}:
*
* <pre>{@code
* // Register our model inside FMLClientSetupEvent
* ComputerCraftAPIClient.registerTurtleUpgradeModeller(MY_UPGRADE.get(), TurtleUpgradeModeller.flatItem())
* }</pre>
* <p>
* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
*
* @param <T> The type of turtle upgrade this is responsible for serialising.
* @see ITurtleUpgrade
* @see TurtleUpgradeDataProvider
* @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
*/
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
*/
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
/**
* Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
* but for upgrades.
* <p>
* If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
*
* @param factory Generate a new upgrade with a specific ID.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade
*/
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
final class Impl extends SimpleSerialiser<T> implements TurtleUpgradeSerialiser<T> {
private Impl(Function<ResourceLocation, T> constructor) {
super(constructor);
}
}
return new Impl(factory);
}
/**
* Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
*
* @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
* {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
* @param <T> The type of the generated upgrade.
* @return The serialiser for this upgrade.
* @see #simple(Function) For upgrades whose crafting stack should not vary.
*/
static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
final class Impl extends SerialiserWithCraftingItem<T> implements TurtleUpgradeSerialiser<T> {
private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
super(factory);
}
}
return new Impl(factory);
}
}

View File

@@ -1,94 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.upgrades;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.Util;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import java.util.Objects;
/**
* Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
*/
public interface UpgradeBase {
/**
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem"
* or "my_mod:my_upgrade".
* <p>
* You should use a unique resource domain to ensure this upgrade is uniquely identified.
* The upgrade will fail registration if an already used ID is specified.
*
* @return The unique ID for this upgrade.
*/
ResourceLocation getUpgradeID();
/**
* Return an unlocalised string to describe this type of computer in item names.
* <p>
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
*
* @return The localisation key for this upgrade's adjective.
*/
String getUnlocalisedAdjective();
/**
* Return an item stack representing the type of item that a computer must be crafted
* with to create a version which holds this upgrade. This item stack is also used
* to determine the upgrade given by {@code turtle.equipLeft()} or {@code pocket.equipBack()}
* <p>
* This should be constant over a session (or at least a datapack reload). It is recommended
* that you cache the stack too, in order to prevent constructing it every time the method
* is called.
*
* @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
*/
ItemStack getCraftingItem();
/**
* Determine if an item is suitable for being used for this upgrade.
* <p>
* When un-equipping an upgrade, we return {@link #getCraftingItem()} rather than
* the original stack. In order to prevent people losing items with enchantments (or
* repairing items with non-0 damage), we impose additional checks on the item.
* <p>
* The default check requires that any non-capability NBT is exactly the same as the
* crafting item, but this may be relaxed for your upgrade.
* <p>
* This is based on {@code net.minecraftforge.common.crafting.StrictNBTIngredient}'s check.
*
* @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
* {@link #getCraftingItem()}.
* @return If this stack may be used to equip this upgrade.
*/
default boolean isItemSuitable(ItemStack stack) {
var crafting = getCraftingItem();
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
// null one.
var shareTag = PlatformHelper.get().getShareTag(stack);
var craftingShareTag = PlatformHelper.get().getShareTag(crafting);
if (shareTag == craftingShareTag) return true;
if (shareTag == null) return Objects.requireNonNull(craftingShareTag).isEmpty();
if (craftingShareTag == null) return shareTag.isEmpty();
return shareTag.equals(craftingShareTag);
}
/**
* Get a suitable default unlocalised adjective for an upgrade ID. This converts "modid:some_upgrade" to
* "upgrade.modid.some_upgrade.adjective".
*
* @param id The upgrade ID.
* @return The generated adjective.
* @see #getUnlocalisedAdjective()
*/
static String getDefaultAdjective(ResourceLocation id) {
return Util.makeDescriptionId("upgrade", id) + ".adjective";
}
}

View File

@@ -1,171 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.upgrades;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.PlatformHelper;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.Util;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* A data generator/provider for turtle and pocket computer upgrades. This should not be extended directly, instead see
* the other subclasses.
*
* @param <T> The base class of upgrades.
* @param <R> The upgrade serialiser to register for.
*/
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
private static final Logger LOGGER = LogManager.getLogger();
private final PackOutput output;
private final String name;
private final String folder;
private final ResourceKey<Registry<R>> registry;
private @Nullable List<T> upgrades;
protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<R>> registry) {
this.output = output;
this.name = name;
this.folder = folder;
this.registry = registry;
}
/**
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
*
* @param id The ID of the upgrade to create.
* @param serialiser The simple serialiser.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simple(ResourceLocation id, R serialiser) {
if (!(serialiser instanceof SimpleSerialiser)) {
throw new IllegalStateException(serialiser + " must be a simple() seriaiser.");
}
return new Upgrade<>(id, serialiser, s -> {
});
}
/**
* Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
*
* @param id The ID of the upgrade to create.
* @param serialiser The simple serialiser.
* @param item The crafting upgrade for this item.
* @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
*/
public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser, Item item) {
if (!(serialiser instanceof SerialiserWithCraftingItem)) {
throw new IllegalStateException(serialiser + " must be a simpleWithCustomItem() serialiser.");
}
return new Upgrade<>(id, serialiser, s ->
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, item).toString())
);
}
/**
* Add all turtle or pocket computer upgrades.
* <p>
* <strong>Example usage:</strong>
* <pre>{@code
* protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
* simple(new ResourceLocation("mymod", "speaker"), SPEAKER_SERIALISER.get()).add(addUpgrade);
* }
* }</pre>
*
* @param addUpgrade A callback used to register an upgrade.
*/
protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
@Override
public final CompletableFuture<?> run(CachedOutput cache) {
var base = output.getOutputFolder().resolve("data");
Set<ResourceLocation> seen = new HashSet<>();
List<T> upgrades = new ArrayList<>();
List<CompletableFuture<?>> futures = new ArrayList<>();
addUpgrades(upgrade -> {
if (!seen.add(upgrade.id())) throw new IllegalStateException("Duplicate upgrade " + upgrade.id());
var json = new JsonObject();
json.addProperty("type", PlatformHelper.get().getRegistryKey(registry, upgrade.serialiser()).toString());
upgrade.serialise().accept(json);
futures.add(DataProvider.saveStable(cache, json, base.resolve(upgrade.id().getNamespace() + "/" + folder + "/" + upgrade.id().getPath() + ".json")));
try {
var result = upgrade.serialiser().fromJson(upgrade.id(), json);
upgrades.add(result);
} catch (IllegalArgumentException | JsonParseException e) {
LOGGER.error("Failed to parse {} {}", name, upgrade.id(), e);
}
});
this.upgrades = upgrades;
return Util.sequenceFailFast(futures);
}
@Override
public final String getName() {
return name;
}
public final R existingSerialiser(ResourceLocation id) {
var result = PlatformHelper.get().getRegistryObject(registry, id);
if (result == null) throw new IllegalArgumentException("No such serialiser " + registry);
return result;
}
public List<T> getGeneratedUpgrades() {
if (upgrades == null) throw new IllegalStateException("Upgrades have not been generated yet");
return upgrades;
}
/**
* A constructed upgrade instance, produced {@link #addUpgrades(Consumer)}.
*
* @param id The ID for this upgrade.
* @param serialiser The serialiser which reads and writes this upgrade.
* @param serialise Augment the generated JSON with additional fields.
* @param <R> The type of upgrade serialiser.
*/
public record Upgrade<R extends UpgradeSerialiser<?>>(
ResourceLocation id, R serialiser, Consumer<JsonObject> serialise
) {
/**
* Convenience method for registering an upgrade.
*
* @param add The callback given to {@link #addUpgrades(Consumer)}
*/
public void add(Consumer<Upgrade<R>> add) {
add.accept(this);
}
}
}

View File

@@ -1,53 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.upgrades;
import com.google.gson.JsonObject;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
/**
* Base interface for upgrade serialisers. This should generally not be implemented directly, instead implementing one
* of {@link TurtleUpgradeSerialiser} or {@link PocketUpgradeSerialiser}.
* <p>
* However, it may sometimes be useful to implement this if you have some shared logic between upgrade types.
*
* @param <T> The upgrade that this class can serialise and deserialise.
* @see TurtleUpgradeSerialiser
* @see PocketUpgradeSerialiser
*/
public interface UpgradeSerialiser<T extends UpgradeBase> {
/**
* Read this upgrade from a JSON file in a datapack.
*
* @param id The ID of this upgrade.
* @param object The JSON object to load this upgrade from.
* @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
* @see net.minecraft.util.GsonHelper For additional JSON helper methods.
*/
T fromJson(ResourceLocation id, JsonObject object);
/**
* Read this upgrade from a network packet, sent from the server.
*
* @param id The ID of this upgrade.
* @param buffer The buffer object to read this upgrade from.
* @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
*/
T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer);
/**
* Write this upgrade to a network packet, to be sent to the client.
*
* @param buffer The buffer object to write this upgrade to
* @param upgrade The upgrade to write.
*/
void toNetwork(FriendlyByteBuf buffer, T upgrade);
}

View File

@@ -1,84 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.BlockReference;
import dan200.computercraft.api.detail.DetailRegistry;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
/**
* Backing interface for {@link ComputerCraftAPI}
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
public interface ComputerCraftAPIService {
static ComputerCraftAPIService get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ComputerCraftAPIService.class, Instance.ERROR) : instance;
}
String getInstalledVersion();
int createUniqueNumberedSaveDir(MinecraftServer server, String parentSubPath);
WritableMount createSaveDirMount(MinecraftServer server, String subPath, long capacity);
@Nullable
Mount createResourceMount(MinecraftServer server, String domain, String subPath);
void registerGenericSource(GenericSource source);
void registerBundledRedstoneProvider(BundledRedstoneProvider provider);
int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side);
void registerMediaProvider(MediaProvider provider);
PacketNetwork getWirelessNetwork(MinecraftServer server);
void registerAPIFactory(ILuaAPIFactory factory);
WiredNode createWiredNodeForElement(WiredElement element);
void registerRefuelHandler(TurtleRefuelHandler handler);
DetailRegistry<ItemStack> getItemStackDetailRegistry();
DetailRegistry<BlockReference> getBlockInWorldDetailRegistry();
final class Instance {
static final @Nullable ComputerCraftAPIService INSTANCE;
static final @Nullable Throwable ERROR;
static {
var helper = Services.tryLoad(ComputerCraftAPIService.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance() {
}
}
}

View File

@@ -1,82 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
/**
* Abstraction layer for Forge and Fabric. See implementations for more details.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
public interface PlatformHelper {
/**
* Get the current {@link PlatformHelper} instance.
*
* @return The current instance.
*/
static PlatformHelper get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(PlatformHelper.class, Instance.ERROR) : instance;
}
/**
* Get the unique ID for a registered object.
*
* @param registry The registry to look up this object in.
* @param object The object to look up.
* @param <T> The type of object the registry stores.
* @return The registered object's ID.
* @throws IllegalArgumentException If the registry or object are not registered.
*/
<T> ResourceLocation getRegistryKey(ResourceKey<Registry<T>> registry, T object);
/**
* Look up an ID in a registry, returning the registered object.
*
* @param registry The registry to look up this object in.
* @param id The ID to look up.
* @param <T> The type of object the registry stores.
* @return The resolved registry object.
* @throws IllegalArgumentException If the registry or object are not registered.
*/
<T> T getRegistryObject(ResourceKey<Registry<T>> registry, ResourceLocation id);
/**
* Get the subset of an {@link ItemStack}'s {@linkplain ItemStack#getTag() tag} which is synced to the client.
*
* @param item The stack.
* @return The item's tag.
*/
@Nullable
default CompoundTag getShareTag(ItemStack item) {
return item.getTag();
}
final class Instance {
static final @Nullable PlatformHelper INSTANCE;
static final @Nullable Throwable ERROR;
static {
// We don't want class initialisation to fail here (as that results in confusing errors). Instead, capture
// the error and rethrow it when accessing. This should be JITted away in the common case.
var helper = Services.tryLoad(PlatformHelper.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance() {
}
}
}

View File

@@ -1,95 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
/**
* Utilities for loading services.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
public final class Services {
private Services() {
}
/**
* Load a service, asserting that only a single instance is registered.
*
* @param klass The class of the service to load.
* @param <T> The class of the service to load.
* @return The constructed service instance.
* @throws IllegalStateException When the service cannot be loaded.
*/
public static <T> T load(Class<T> klass) {
var services = ServiceLoader.load(klass).stream().toList();
return switch (services.size()) {
case 1 -> services.get(0).get();
case 0 -> throw new IllegalStateException("Cannot find service for " + klass.getName());
default -> {
var serviceTypes = services.stream().map(x -> x.type().getName()).collect(Collectors.joining(", "));
throw new IllegalStateException("Multiple services for " + klass.getName() + ": " + serviceTypes);
}
};
}
/**
* Attempt to load a service with {@link #load(Class)}.
*
* @param klass The class of the service to load.
* @param <T> The class of the service to load.
* @return The result type, either containing the service or an exception.
* @see ComputerCraftAPIService Intended usage of this class.
*/
public static <T> LoadedService<T> tryLoad(Class<T> klass) {
try {
return new LoadedService<>(load(klass), null);
} catch (Exception | LinkageError e) {
return new LoadedService<>(null, e);
}
}
/**
* Raise an exception from trying to load a specific service.
*
* @param klass The class of the service we failed to load.
* @param e The original exception caused by loading this class.
* @param <T> The class of the service to load.
* @return Never
* @see #tryLoad(Class)
* @see LoadedService#error()
*/
@SuppressWarnings("DoNotCallSuggester")
public static <T> T raise(Class<T> klass, @Nullable Throwable e) {
// Throw a new exception so there's a useful stack trace there somewhere!
throw new ServiceException("Failed to instantiate " + klass.getName(), e);
}
public static class LoadedService<T> {
private final @Nullable T instance;
private final @Nullable Throwable error;
LoadedService(@Nullable T instance, @Nullable Throwable error) {
this.instance = instance;
this.error = error;
}
@Nullable
public T instance() {
return instance;
}
@Nullable
public Throwable error() {
return error;
}
}
}

View File

@@ -1,21 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
/**
* Internal interfaces for ComputerCraft's API.
*/
@ApiStatus.Internal
@DefaultQualifier(value = NonNull.class, locations = {
TypeUseLocation.RETURN,
TypeUseLocation.PARAMETER,
TypeUseLocation.FIELD,
})
package dan200.computercraft.impl;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.TypeUseLocation;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -1,50 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl.upgrades;
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import java.util.function.BiFunction;
/**
* Simple serialiser which returns a constant upgrade with a custom crafting item.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public abstract class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
private final BiFunction<ResourceLocation, ItemStack, T> factory;
protected SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
this.factory = factory;
}
@Override
public final T fromJson(ResourceLocation id, JsonObject object) {
var item = GsonHelper.getAsItem(object, "item");
return factory.apply(id, new ItemStack(item));
}
@Override
public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
var item = buffer.readItem();
return factory.apply(id, item);
}
@Override
public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
buffer.writeItem(upgrade.getCraftingItem());
}
}

View File

@@ -1,45 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl.upgrades;
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import java.util.function.Function;
/**
* Simple serialiser which returns a constant upgrade.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public abstract class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
private final Function<ResourceLocation, T> constructor;
public SimpleSerialiser(Function<ResourceLocation, T> constructor) {
this.constructor = constructor;
}
@Override
public final T fromJson(ResourceLocation id, JsonObject object) {
return constructor.apply(id);
}
@Override
public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
return constructor.apply(id);
}
@Override
public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
}
}

View File

@@ -1,36 +0,0 @@
import cc.tweaked.gradle.annotationProcessorEverywhere
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
plugins {
id("cc-tweaked.vanilla")
id("cc-tweaked.gametest")
}
minecraft {
accessWideners(
"src/main/resources/computercraft.accesswidener",
"src/main/resources/computercraft-common.accesswidener",
)
}
dependencies {
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
implementation(project(":core"))
implementation(commonClasses(project(":common-api")))
clientImplementation(clientClasses(project(":common-api")))
compileOnly(libs.bundles.externalMods.common)
compileOnly(libs.mixin)
annotationProcessorEverywhere(libs.autoService)
testFixturesAnnotationProcessor(libs.autoService)
testImplementation(testFixtures(project(":core")))
testImplementation(libs.bundles.test)
testRuntimeOnly(libs.bundles.testRuntime)
testModImplementation(testFixtures(project(":core")))
testModImplementation(testFixtures(project(":common")))
testModImplementation(libs.bundles.kotlin)
}

View File

@@ -1,190 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client;
import com.mojang.blaze3d.audio.Channel;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.CableHighlightRenderer;
import dan200.computercraft.client.render.PocketItemRenderer;
import dan200.computercraft.client.render.PrintoutItemRenderer;
import dan200.computercraft.client.render.monitor.MonitorHighlightRenderer;
import dan200.computercraft.client.render.monitor.MonitorRenderState;
import dan200.computercraft.client.sound.SpeakerManager;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.PauseAwareTimer;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.Util;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.sounds.AudioStream;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable;
import java.io.File;
import java.util.function.Consumer;
/**
* Event listeners for client-only code.
* <p>
* This is the client-only version of {@link CommonHooks}, and so should be where all client-specific event handlers are
* defined.
*/
public final class ClientHooks {
private ClientHooks() {
}
public static void onTick() {
FrameInfo.onTick();
}
public static void onRenderTick() {
PauseAwareTimer.tick(Minecraft.getInstance().isPaused());
FrameInfo.onRenderTick();
}
public static void onWorldUnload() {
MonitorRenderState.destroyAll();
SpeakerManager.reset();
ClientPocketComputers.reset();
}
public static boolean onChatMessage(String message) {
return handleOpenComputerCommand(message);
}
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
|| MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
}
public static boolean onRenderHeldItem(
PoseStack transform, MultiBufferSource render, int lightTexture, InteractionHand hand,
float pitch, float equipProgress, float swingProgress, ItemStack stack
) {
if (stack.getItem() instanceof PocketComputerItem) {
PocketItemRenderer.INSTANCE.renderItemFirstPerson(transform, render, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
return true;
}
if (stack.getItem() instanceof PrintoutItem) {
PrintoutItemRenderer.INSTANCE.renderItemFirstPerson(transform, render, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
return true;
}
return false;
}
public static boolean onRenderItemFrame(PoseStack transform, MultiBufferSource render, ItemFrame frame, ItemStack stack, int light) {
if (stack.getItem() instanceof PrintoutItem) {
PrintoutItemRenderer.onRenderInFrame(transform, render, frame, stack, light);
return true;
}
return false;
}
public static void onPlayStreaming(SoundEngine engine, Channel channel, AudioStream stream) {
SpeakerManager.onPlayStreaming(engine, channel, stream);
}
/**
* Handle the {@link CommandComputerCraft#OPEN_COMPUTER} "clientside command". This isn't a true command, as we
* don't want it to actually be visible to the user.
*
* @param message The current chat message.
* @return Whether to cancel sending this message.
*/
private static boolean handleOpenComputerCommand(String message) {
if (!message.startsWith(CommandComputerCraft.OPEN_COMPUTER)) return false;
var server = Minecraft.getInstance().getSingleplayerServer();
if (server == null) return false;
var idStr = message.substring(CommandComputerCraft.OPEN_COMPUTER.length()).trim();
int id;
try {
id = Integer.parseInt(idStr);
} catch (NumberFormatException ignore) {
return false;
}
var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
if (!file.isDirectory()) return false;
Util.getPlatform().openFile(file);
return true;
}
/**
* Add additional information about the currently targeted block to the debug screen.
*
* @param addText A callback which adds a single line of text.
*/
public static void addDebugInfo(Consumer<String> addText) {
var minecraft = Minecraft.getInstance();
if (!minecraft.options.renderDebug || minecraft.level == null) return;
if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
if (tile instanceof MonitorBlockEntity monitor) {
addText.accept("");
addText.accept(
String.format("Targeted monitor: (%d, %d), %d x %d", monitor.getXIndex(), monitor.getYIndex(), monitor.getWidth(), monitor.getHeight())
);
} else if (tile instanceof TurtleBlockEntity turtle) {
addText.accept("");
addText.accept("Targeted turtle:");
addText.accept(String.format("Id: %d", turtle.getComputerID()));
addTurtleUpgrade(addText, turtle, TurtleSide.LEFT);
addTurtleUpgrade(addText, turtle, TurtleSide.RIGHT);
}
}
private static void addTurtleUpgrade(Consumer<String> out, TurtleBlockEntity turtle, TurtleSide side) {
var upgrade = turtle.getUpgrade(side);
if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.getUpgradeID()));
}
public static @Nullable BlockState getBlockBreakingState(BlockState state, BlockPos pos) {
// Only apply to cables which have both a cable and modem
if (state.getBlock() != ModRegistry.Blocks.CABLE.get()
|| !state.getValue(CableBlock.CABLE)
|| state.getValue(CableBlock.MODEM) == CableModemVariant.None
) {
return null;
}
var hit = Minecraft.getInstance().hitResult;
if (hit == null || hit.getType() != HitResult.Type.BLOCK) return null;
var hitPos = ((BlockHitResult) hit).getBlockPos();
if (!hitPos.equals(pos)) return null;
return WorldUtil.isVecInside(CableShapes.getModemShape(state), hit.getLocation().subtract(pos.getX(), pos.getY(), pos.getZ()))
? state.getBlock().defaultBlockState().setValue(CableBlock.MODEM, state.getValue(CableBlock.MODEM))
: state.setValue(CableBlock.MODEM, CableModemVariant.None);
}
}

Some files were not shown because too many files have changed in this diff Show More