mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-15 22:17:39 +00:00
Compare commits
233 Commits
v1.16.5-1.
...
v1.19.3-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f561572509 | ||
![]() |
edb21f33be | ||
![]() |
02b68b259e | ||
![]() |
28a55349a9 | ||
![]() |
2457a31728 | ||
![]() |
cdc91a8e5d | ||
![]() |
8024017f53 | ||
![]() |
592ff84aea | ||
![]() |
4360458416 | ||
![]() |
717e096b94 | ||
![]() |
34a31abd9c | ||
![]() |
bdecb88cca | ||
![]() |
af15030fa4 | ||
![]() |
3a883db49e | ||
![]() |
8ea5b64f64 | ||
![]() |
7b6caf76e4 | ||
![]() |
230c7ee904 | ||
![]() |
aa203802c6 | ||
![]() |
1259e29f21 | ||
![]() |
77f62dac94 | ||
![]() |
7f34aff6bb | ||
![]() |
3047e3cdf4 | ||
![]() |
7a83a403f0 | ||
![]() |
a1d5c76d00 | ||
![]() |
bcdfa7c5ff | ||
![]() |
2c59b9122b | ||
![]() |
d2c7b944ab | ||
![]() |
e241575329 | ||
![]() |
86c4c7483d | ||
![]() |
9010219b9c | ||
![]() |
172d1824fc | ||
![]() |
9d394f44d3 | ||
![]() |
6e5b7243f4 | ||
![]() |
27b732f835 | ||
![]() |
4fa7f50534 | ||
![]() |
eeac86b07c | ||
![]() |
36ce490566 | ||
![]() |
e7fe22d4f8 | ||
![]() |
2b237332ce | ||
![]() |
1276478deb | ||
![]() |
551f6ba60c | ||
![]() |
99a2b26fc5 | ||
![]() |
0787e17ebe | ||
![]() |
06163e4f25 | ||
![]() |
18fbd96c10 | ||
![]() |
367773e173 | ||
![]() |
8007a30849 | ||
![]() |
df38f3e887 | ||
![]() |
c3fe9f00d4 | ||
![]() |
3b42f22a4f | ||
![]() |
9962ce1a5c | ||
![]() |
9f48395596 | ||
![]() |
020c5cd2d3 | ||
![]() |
a9c0b02e3c | ||
![]() |
fc5f296eeb | ||
![]() |
c96172e78d | ||
![]() |
fa122a56cf | ||
![]() |
87c6d3aef6 | ||
![]() |
95c57e843d | ||
![]() |
b13998dd96 | ||
![]() |
47816805fb | ||
![]() |
b8fce1eecc | ||
![]() |
ee2670d53b | ||
![]() |
3a96aea894 | ||
![]() |
0fc78acd49 | ||
![]() |
737d8a2585 | ||
![]() |
e2447bb0fd | ||
![]() |
2255d49d16 | ||
![]() |
3fa39b5f98 | ||
![]() |
08df68dcc0 | ||
![]() |
8f92417a2f | ||
![]() |
b58b9b7df3 | ||
![]() |
8d2e150f05 | ||
![]() |
8152f19b6e | ||
![]() |
b2b58892e3 | ||
![]() |
8360e8234d | ||
![]() |
77624fc6fd | ||
![]() |
1d335f7290 | ||
![]() |
f04acdc199 | ||
![]() |
bdf590fa30 | ||
![]() |
0c4fd2b29e | ||
![]() |
8a7156785d | ||
![]() |
4d50b48ea6 | ||
![]() |
48285404b9 | ||
![]() |
b36b96e0bc | ||
![]() |
34c7fcf750 | ||
![]() |
cc73fcd85d | ||
![]() |
22729f6f16 | ||
![]() |
55494b7671 | ||
![]() |
7d47b219c5 | ||
![]() |
320007dbc6 | ||
![]() |
0908acbe9b | ||
![]() |
e8f9cdd221 | ||
![]() |
53abe5e56e | ||
![]() |
564752c8dd | ||
![]() |
6d665ad841 | ||
![]() |
9cd728fea9 | ||
![]() |
1c890e5a5c | ||
![]() |
955b9c7d28 | ||
![]() |
76710eec9d | ||
![]() |
d8e2161f15 | ||
![]() |
c82f37d3bf | ||
![]() |
c8c128d335 | ||
![]() |
acc254a1ef | ||
![]() |
a17b001950 | ||
![]() |
e4e528e5bf | ||
![]() |
6cc86b0ae5 | ||
![]() |
f478c4ffc4 | ||
![]() |
7df0412c2d | ||
![]() |
998efcc950 | ||
![]() |
45c5de73bb | ||
![]() |
c919011a7e | ||
![]() |
0f1f5247ca | ||
![]() |
0db32bd0fe | ||
![]() |
c1bf9f0b24 | ||
![]() |
629abb65e3 | ||
![]() |
11ac865877 | ||
![]() |
5d4c34fbac | ||
![]() |
c4184a33bc | ||
![]() |
b3702fed78 | ||
![]() |
b5056fc3b8 | ||
![]() |
38b2c944f3 | ||
![]() |
a9ef874174 | ||
![]() |
a2911038c5 | ||
![]() |
158850be09 | ||
![]() |
be827a21db | ||
![]() |
c8e15f201c | ||
![]() |
bc79100a2f | ||
![]() |
9ed5ebb868 | ||
![]() |
66dff1523b | ||
![]() |
08895cdecc | ||
![]() |
76f8dd2d14 | ||
![]() |
34a2e87735 | ||
![]() |
feb7681c9c | ||
![]() |
ad4a2aa68d | ||
![]() |
4228011b84 | ||
![]() |
c43d851e63 | ||
![]() |
50fe7935a3 | ||
![]() |
bd19fdf350 | ||
![]() |
c3615d9c5b | ||
![]() |
25a44bea6e | ||
![]() |
69b211b4fb | ||
![]() |
48147fa61c | ||
![]() |
4a273ae8e5 | ||
![]() |
f25a73b8f2 | ||
![]() |
e906f3ebc3 | ||
![]() |
92c613a7a2 | ||
![]() |
d2f94f2653 | ||
![]() |
bb0e449560 | ||
![]() |
ee495b3359 | ||
![]() |
8fc7820a12 | ||
![]() |
a2e3d9d9bd | ||
![]() |
755f8eff93 | ||
![]() |
a879efc3d0 | ||
![]() |
a913232e62 | ||
![]() |
557765d8f0 | ||
![]() |
5382d34d29 | ||
![]() |
8f7719a8dc | ||
![]() |
a07bba4ece | ||
![]() |
83a1af6526 | ||
![]() |
5052718428 | ||
![]() |
caa412b7d2 | ||
![]() |
159f90896e | ||
![]() |
2a4f75ba15 | ||
![]() |
42b98bce28 | ||
![]() |
59e3608d2a | ||
![]() |
41fa95bce4 | ||
![]() |
ba7598c689 | ||
![]() |
70c5cbafec | ||
![]() |
7731759c77 | ||
![]() |
e6339b2847 | ||
![]() |
6353e8d930 | ||
![]() |
78cce4981a | ||
![]() |
97c953a9be | ||
![]() |
52df7cb8a4 | ||
![]() |
6735cfd12e | ||
![]() |
f994696161 | ||
![]() |
4a4e8bb4b6 | ||
![]() |
9edce36efd | ||
![]() |
e05588c662 | ||
![]() |
79366bf2f5 | ||
![]() |
413fa5bcc8 | ||
![]() |
2b901f2d5e | ||
![]() |
62f2cd5cb2 | ||
![]() |
901d8d4c3b | ||
![]() |
f794ce42ab | ||
![]() |
2562642664 | ||
![]() |
632db1cfa5 | ||
![]() |
aa0d544bba | ||
![]() |
2f6ad00764 | ||
![]() |
05da4dd362 | ||
![]() |
fe3c42ce22 | ||
![]() |
f6fcba7a39 | ||
![]() |
82a7edee12 | ||
![]() |
7c373c6e06 | ||
![]() |
6196aae488 | ||
![]() |
57c5d19f95 | ||
![]() |
23c17075be | ||
![]() |
87988a705b | ||
![]() |
179da1d8cf | ||
![]() |
92fd93c0e0 | ||
![]() |
af966179ce | ||
![]() |
2418cfb87b | ||
![]() |
095101831c | ||
![]() |
7b7527ec80 | ||
![]() |
a4c5ecf8df | ||
![]() |
99de00e16e | ||
![]() |
600227e481 | ||
![]() |
cf3f1d3d48 | ||
![]() |
bca964629a | ||
![]() |
0e94355a85 | ||
![]() |
0d35331b82 | ||
![]() |
076b454c8f | ||
![]() |
36e0dcbad0 | ||
![]() |
0b5fe990e5 | ||
![]() |
29ece2a6e3 | ||
![]() |
eba26dedab | ||
![]() |
3eb601e554 | ||
![]() |
d0e79f310e | ||
![]() |
0d6528aaf0 | ||
![]() |
b447b0e308 | ||
![]() |
b2273c9b29 | ||
![]() |
bbf3e48763 | ||
![]() |
92fe1d4bc2 | ||
![]() |
4d591c600c | ||
![]() |
0a8e427c61 | ||
![]() |
0a537eaeee | ||
![]() |
aa857c1be3 | ||
![]() |
e4ced551eb | ||
![]() |
6eec9ba1a3 | ||
![]() |
a8f675c59d | ||
![]() |
bb1ebaee4f | ||
![]() |
bb1183d274 |
@@ -8,6 +8,17 @@ 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
|
||||
|
||||
|
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
||||
# Reformat everything
|
||||
f478c4ffc4fb9fc2200ec9b0bc751d047057ce81
|
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,6 +1,6 @@
|
||||
# Ignore changes in generated files
|
||||
src/generated/** linguist-generated
|
||||
src/testMod/server-files/structures linguist-generated
|
||||
projects/*/src/generated/** linguist-generated
|
||||
projects/common/src/testMod/resources/data/cctest/structures/* linguist-generated
|
||||
|
||||
* text=auto
|
||||
|
||||
|
56
.github/workflows/main-ci.yml
vendored
56
.github/workflows/main-ci.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 8
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
@@ -33,14 +33,25 @@ 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@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: CC-Tweaked
|
||||
path: build/libs
|
||||
path: ./jars
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v2
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
- name: Parse test reports
|
||||
run: ./tools/parse-reports.py
|
||||
@@ -48,3 +59,40 @@ 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() }}
|
||||
|
4
.github/workflows/make-doc.sh
vendored
4
.github/workflows/make-doc.sh
vendored
@@ -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/build/docs/site/" \
|
||||
"$GITHUB_WORKSPACE/projects/web/build/site/" \
|
||||
"$SSH_USER@$SSH_HOST:/$DEST"
|
||||
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
|
||||
"$GITHUB_WORKSPACE/build/docs/javadoc/" \
|
||||
"$GITHUB_WORKSPACE/projects/common-api/build/docs/javadoc/" \
|
||||
"$SSH_USER@$SSH_HOST:/$DEST/javadoc"
|
||||
|
6
.github/workflows/make-doc.yml
vendored
6
.github/workflows/make-doc.yml
vendored
@@ -3,7 +3,7 @@ name: Build documentation
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- mc-1.16.x
|
||||
- mc-1.19.x
|
||||
|
||||
jobs:
|
||||
make_doc:
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
run: ./gradlew compileJava --no-daemon || ./gradlew compileJava --no-daemon
|
||||
|
||||
- name: Generate documentation
|
||||
run: ./gradlew docWebsite javadoc --no-daemon
|
||||
run: ./gradlew docWebsite :common-api:javadoc --no-daemon
|
||||
|
||||
- name: Upload documentation
|
||||
run: .github/workflows/make-doc.sh 2> /dev/null
|
||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@@ -2,15 +2,17 @@
|
||||
/classes
|
||||
/logs
|
||||
/build
|
||||
/projects/*/logs
|
||||
/projects/*/build
|
||||
/buildSrc/build
|
||||
/out
|
||||
/doc/out/
|
||||
/node_modules
|
||||
/.jqwik-database
|
||||
.jqwik-database
|
||||
|
||||
# Runtime directories
|
||||
/run
|
||||
/run-*
|
||||
/projects/*/run
|
||||
|
||||
*.ipr
|
||||
*.iws
|
||||
@@ -23,8 +25,6 @@
|
||||
/.project
|
||||
/.settings
|
||||
/.vscode
|
||||
bin/
|
||||
*.launch
|
||||
|
||||
/src/generated/resources/.cache
|
||||
/src/web/mount/*.d.ts
|
||||
/projects/*/src/generated/resources/.cache
|
||||
|
@@ -48,9 +48,7 @@ repos:
|
||||
|
||||
exclude: |
|
||||
(?x)^(
|
||||
src/generated|
|
||||
src/test/resources/test-rom/data/json-parsing/|
|
||||
src/testMod/server-files/|
|
||||
config/idea/|
|
||||
projects/[a-z]+/src/generated|
|
||||
projects/core/src/test/resources/test-rom/data/json-parsing/|
|
||||
.*\.dfpwm
|
||||
)
|
||||
|
139
CONTRIBUTING.md
139
CONTRIBUTING.md
@@ -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!
|
||||
|
||||
## 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`.
|
||||
## Setting up a development environment
|
||||
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
|
||||
|
||||
- **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: [](https://gitpod.io/#https://github.com/cc-tweaked/CC-Tweaked/)
|
||||
- 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].
|
||||
|
||||
Do note you will need to download the mod after compiling to test.
|
||||
- Download CC: Tweaked's source code:
|
||||
```
|
||||
git clone https://github.com/cc-tweaked/CC-Tweaked.git
|
||||
cd CC-Tweaked
|
||||
```
|
||||
|
||||
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.
|
||||
- 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!
|
||||
|
||||
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.
|
||||
- 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.
|
||||
|
||||
### 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.
|
||||
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).
|
||||
|
||||
- **[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`.
|
||||
## 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]!
|
||||
|
||||
### Documentation
|
||||
### 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
|
||||
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.
|
||||
|
||||
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:
|
||||
You'll first need to [set up a development environment as above](#setting-up-a-development-environment).
|
||||
|
||||
- Install [Node/npm][node].
|
||||
- Run `npm ci` to install our Node dependencies.
|
||||
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.
|
||||
|
||||
You can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code, writing the resulting
|
||||
HTML into `./build/docs/site`.
|
||||
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!
|
||||
|
||||
#### 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"`.
|
||||
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!
|
||||
|
||||
[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."
|
||||
[community]: README.md#community "Get in touch with the community."
|
||||
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
|
||||
[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,3 +102,4 @@ asserts that your variable `foo` is equal to the expected value `"bar"`.
|
||||
[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
|
||||
|
18
README.md
18
README.md
@@ -26,16 +26,26 @@ on is present.
|
||||
```groovy
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://squiddev.cc/maven/'
|
||||
url "https://squiddev.cc/maven/"
|
||||
content {
|
||||
includeGroup 'org.squiddev'
|
||||
includeGroup("cc.tweaked")
|
||||
includeModule("org.squiddev", "Cobalt")
|
||||
includeModule("fuzs.forgeconfigapiport", "forgeconfigapiport-fabric")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly fg.deobf("org.squiddev:cc-tweaked-${mc_version}:${cct_version}:api")
|
||||
runtimeOnly fg.deobf("org.squiddev:cc-tweaked-${mc_version}:${cct_version}")
|
||||
// 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")
|
||||
}
|
||||
```
|
||||
|
||||
|
442
build.gradle.kts
442
build.gradle.kts
@@ -1,394 +1,18 @@
|
||||
import cc.tweaked.gradle.*
|
||||
import net.darkhax.curseforgegradle.TaskPublishCurseForge
|
||||
import net.minecraftforge.gradle.common.util.RunConfig
|
||||
import org.jetbrains.gradle.ext.compiler
|
||||
import org.jetbrains.gradle.ext.settings
|
||||
|
||||
plugins {
|
||||
// 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)
|
||||
alias(libs.plugins.minotaur)
|
||||
// Utility
|
||||
publishing
|
||||
alias(libs.plugins.taskTree)
|
||||
|
||||
id("cc-tweaked.illuaminate")
|
||||
id("cc-tweaked.node")
|
||||
id("cc-tweaked.gametest")
|
||||
alias(libs.plugins.githubRelease)
|
||||
id("org.jetbrains.gradle.plugin.idea-ext")
|
||||
id("cc-tweaked")
|
||||
}
|
||||
|
||||
val isStable = true
|
||||
val isUnstable = project.properties["isUnstable"] == "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 {
|
||||
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() {
|
||||
mods.register("cctest") {
|
||||
source(sourceSets["testMod"])
|
||||
source(sourceSets["testFixtures"])
|
||||
}
|
||||
}
|
||||
|
||||
val testClient by registering {
|
||||
workingDirectory(file("run/testClient"))
|
||||
parent(client.get())
|
||||
configureForGameTest()
|
||||
}
|
||||
|
||||
val testServer by registering {
|
||||
workingDirectory(file("run/testServer"))
|
||||
parent(server.get())
|
||||
configureForGameTest()
|
||||
|
||||
property("cctest.run", "true")
|
||||
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"))
|
||||
accessTransformer(file("src/testMod/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.4:processor")
|
||||
|
||||
compileOnly(libs.jetbrainsAnnotations)
|
||||
annotationProcessorEverywhere(libs.autoService)
|
||||
|
||||
"extraModsCompileOnly"(fg.deobf("mezz.jei:jei-1.16.5:7.7.0.104:api"))
|
||||
"extraModsRuntimeOnly"(fg.deobf("mezz.jei:jei-1.16.5:7.7.0.104"))
|
||||
|
||||
"extraModsCompileOnly"(fg.deobf("com.blamejared.crafttweaker:CraftTweaker-1.16.5:7.1.0.313"))
|
||||
"extraModsCompileOnly"(fg.deobf("commoble.morered:morered-1.16.5:2.1.1.0"))
|
||||
|
||||
"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/javase/8/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(JavaLanguageVersion.of(11))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
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 setupRunGametest by tasks.registering(Copy::class) {
|
||||
group = LifecycleBasePlugin.VERIFICATION_GROUP
|
||||
description = "Sets up the environment for the test server."
|
||||
|
||||
from("src/testMod/server-files") {
|
||||
include("eula.txt")
|
||||
include("server.properties")
|
||||
}
|
||||
into("run/testServer")
|
||||
}
|
||||
|
||||
val runGametest by tasks.registering(JavaExec::class) {
|
||||
group = LifecycleBasePlugin.VERIFICATION_GROUP
|
||||
description = "Runs tests on a temporary Minecraft instance."
|
||||
dependsOn(setupRunGametest, "cleanRunGametest")
|
||||
|
||||
// Copy from runTestServer. We do it in this slightly odd way as runTestServer
|
||||
// isn't created until the task is configured (which is no good for us).
|
||||
val exec = tasks.getByName<JavaExec>("runTestServer")
|
||||
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")
|
||||
@@ -399,54 +23,30 @@ githubRelease {
|
||||
releaseName.set("[$mcVersion] $modVersion")
|
||||
body.set(
|
||||
provider {
|
||||
"## " + file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
|
||||
"## " + project(":core").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(!isStable)
|
||||
prerelease.set(isUnstable)
|
||||
}
|
||||
|
||||
tasks.publish { dependsOn(tasks.githubRelease) }
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://squiddev.cc/maven") {
|
||||
name = "SquidDev"
|
||||
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
@@ -3,14 +3,51 @@ 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 {
|
||||
|
66
buildSrc/src/main/kotlin/cc-tweaked.fabric.gradle.kts
Normal file
66
buildSrc/src/main/kotlin/cc-tweaked.fabric.gradle.kts
Normal file
@@ -0,0 +1,66 @@
|
||||
/** 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() }
|
||||
}
|
39
buildSrc/src/main/kotlin/cc-tweaked.forge.gradle.kts
Normal file
39
buildSrc/src/main/kotlin/cc-tweaked.forge.gradle.kts
Normal file
@@ -0,0 +1,39 @@
|
||||
/** 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() }
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import cc.tweaked.gradle.clientClasses
|
||||
import cc.tweaked.gradle.commonClasses
|
||||
|
||||
/**
|
||||
* Sets up the configurations for writing game tests.
|
||||
@@ -11,12 +12,13 @@ plugins {
|
||||
id("cc-tweaked.java-convention")
|
||||
}
|
||||
|
||||
val main = sourceSets.main.get()
|
||||
val main = sourceSets["main"]
|
||||
val client = sourceSets["client"]
|
||||
|
||||
// Both testMod and testFixtures inherit from the main classpath, just so we have access to Minecraft classes.
|
||||
// Both testMod and testFixtures inherit from the main and client classpath, just so we have access to Minecraft classes.
|
||||
val testMod by sourceSets.creating {
|
||||
compileClasspath += main.compileClasspath
|
||||
runtimeClasspath += main.runtimeClasspath
|
||||
compileClasspath += main.compileClasspath + client.compileClasspath
|
||||
runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
|
||||
}
|
||||
|
||||
configurations {
|
||||
@@ -32,12 +34,13 @@ 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
|
||||
compileClasspath += main.compileClasspath + client.compileClasspath
|
||||
}
|
||||
|
||||
java.registerFeature("testFixtures") {
|
||||
@@ -46,8 +49,12 @@ java.registerFeature("testFixtures") {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
add(testFixtures.implementationConfigurationName, main.output)
|
||||
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))
|
||||
|
||||
testImplementation(testFixtures(project))
|
||||
add(testMod.implementationConfigurationName, testFixtures(project))
|
||||
}
|
||||
|
@@ -1,23 +1,35 @@
|
||||
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 {
|
||||
@@ -28,10 +40,15 @@ repositories {
|
||||
includeGroup("org.squiddev")
|
||||
includeGroup("cc.tweaked")
|
||||
// Things we mirror
|
||||
includeGroup("com.blamejared.crafttweaker")
|
||||
includeGroup("commoble.morered")
|
||||
includeGroup("dev.architectury")
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,6 +56,9 @@ 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.
|
||||
@@ -46,13 +66,68 @@ 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")
|
||||
|
||||
@@ -67,6 +142,14 @@ 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
|
||||
@@ -78,8 +161,8 @@ spotless {
|
||||
}
|
||||
|
||||
val licenser = LicenseHeader.create(
|
||||
api = file("config/license/api.txt"),
|
||||
main = file("config/license/main.txt"),
|
||||
api = rootProject.file("config/license/api.txt"),
|
||||
main = rootProject.file("config/license/main.txt"),
|
||||
)
|
||||
|
||||
java {
|
||||
@@ -104,3 +187,12 @@ 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
|
||||
}
|
||||
|
45
buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts
Normal file
45
buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts
Normal file
@@ -0,0 +1,45 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
31
buildSrc/src/main/kotlin/cc-tweaked.vanilla.gradle.kts
Normal file
31
buildSrc/src/main/kotlin/cc-tweaked.vanilla.gradle.kts
Normal file
@@ -0,0 +1,31 @@
|
||||
/** 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)
|
||||
}
|
@@ -1,23 +1,35 @@
|
||||
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.JavaExec
|
||||
import org.gradle.api.tasks.SourceSetContainer
|
||||
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.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 java.io.BufferedWriter
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
import java.util.regex.Pattern
|
||||
|
||||
abstract class CCTweakedExtension(
|
||||
@@ -26,45 +38,137 @@ 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.projectDir.absolutePath, "rev-parse", "HEAD").trim()
|
||||
ProcessHelpers.captureOut("git", "-C", project.rootProject.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.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD").trim()
|
||||
ProcessHelpers.captureOut("git", "-C", project.rootProject.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()) {
|
||||
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)",
|
||||
),
|
||||
ProcessHelpers.captureLines(
|
||||
"git", "-C", project.rootProject.projectDir.absolutePath, "shortlog", "-ns",
|
||||
"--group=author", "--group=trailer:co-authored-by", "HEAD",
|
||||
)
|
||||
val process = ProcessHelpers.startProcess("git", "check-mailmap", "--stdin")
|
||||
BufferedWriter(OutputStreamWriter(process.outputStream)).use { writer ->
|
||||
for (authorName in authors) {
|
||||
var author = authorName
|
||||
|
||||
if (author.isEmpty()) continue
|
||||
if (!author.endsWith(">")) author += ">" // Some commits have broken Co-Authored-By lines!
|
||||
writer.write(author)
|
||||
writer.newLine()
|
||||
.asSequence()
|
||||
.map {
|
||||
val matcher = COMMIT_COUNTS.matcher(it)
|
||||
matcher.find()
|
||||
matcher.group(1)
|
||||
}
|
||||
}
|
||||
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)
|
||||
.filter { !IGNORED_USERS.contains(it) }
|
||||
.toList()
|
||||
.sortedWith(String.CASE_INSENSITIVE_ORDER)
|
||||
}
|
||||
|
||||
fun jacoco(task: NamedDomainObjectProvider<JavaExec>) {
|
||||
/**
|
||||
* 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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
|
||||
val classDump = project.buildDir.resolve("jacocoClassDump/${task.name}")
|
||||
val reportTaskName = "jacoco${task.name.capitalized()}Report"
|
||||
|
||||
@@ -93,8 +197,7 @@ abstract class CCTweakedExtension(
|
||||
classDirectories.from(classDump)
|
||||
|
||||
// Don't want to use sourceSets(...) here as we have a custom class directory.
|
||||
val sourceSets = project.extensions.getByType(SourceSetContainer::class.java)
|
||||
sourceDirectories.from(sourceSets["main"].allSource.sourceDirectories)
|
||||
for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
|
||||
}
|
||||
|
||||
project.extensions.configure(ReportingExtension::class.java) {
|
||||
@@ -104,8 +207,43 @@ 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 EMAIL = Pattern.compile("^([^<]+) <.+>$")
|
||||
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
|
||||
private val IGNORED_USERS = setOf(
|
||||
"GitHub", "Daniel Ratcliffe", "Weblate",
|
||||
)
|
||||
@@ -117,8 +255,14 @@ 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"))
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@ 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
|
||||
|
||||
/**
|
||||
@@ -9,10 +11,15 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||
*/
|
||||
class CCTweakedPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
project.extensions.create("cct", CCTweakedExtension::class.java)
|
||||
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")))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val JAVA_VERSION = JavaLanguageVersion.of(8)
|
||||
val JAVA_VERSION = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ 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.
|
||||
|
@@ -5,7 +5,6 @@ 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.
|
||||
|
@@ -2,19 +2,116 @@ 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)
|
||||
spec.classpath = classpath
|
||||
spec.mainClass.set(mainClass)
|
||||
spec.javaLauncher.set(javaLauncher)
|
||||
|
||||
// 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.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,170 @@
|
||||
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",
|
||||
)
|
||||
}
|
||||
}
|
@@ -66,9 +66,11 @@ 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(
|
||||
|
@@ -0,0 +1,62 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,189 @@
|
||||
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
|
||||
}
|
191
buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftExec.kt
Normal file
191
buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftExec.kt
Normal file
@@ -0,0 +1,191 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,35 +1,50 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.codehaus.groovy.runtime.ProcessGroovyMethods
|
||||
import org.gradle.api.GradleException
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
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 Runtime.getRuntime().exec(command, arrayOfNulls(0))
|
||||
return ProcessBuilder()
|
||||
.command(*command)
|
||||
.redirectError(ProcessBuilder.Redirect.INHERIT)
|
||||
.also { it.environment().clear() }
|
||||
.start()
|
||||
}
|
||||
|
||||
fun captureOut(vararg command: String): String {
|
||||
val process = startProcess(*command)
|
||||
process.outputStream.close()
|
||||
|
||||
val result = ProcessGroovyMethods.getText(process)
|
||||
if (process.waitFor() != 0) throw IOException("Command exited with a non-0 status")
|
||||
process.waitForOrThrow("Failed to run command")
|
||||
return result
|
||||
}
|
||||
|
||||
fun captureLines(vararg command: String): List<String> {
|
||||
return captureLines(startProcess(*command))
|
||||
}
|
||||
val process = startProcess(*command)
|
||||
process.outputStream.close()
|
||||
|
||||
fun captureLines(process: Process): List<String> {
|
||||
val out = BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
|
||||
reader.lines().filter { it.isNotEmpty() }.collect(Collectors.toList())
|
||||
reader.lines().filter { it.isNotEmpty() }.toList()
|
||||
}
|
||||
ProcessGroovyMethods.closeStreams(process)
|
||||
if (process.waitFor() != 0) throw IOException("Command exited with a non-0 status")
|
||||
process.waitForOrThrow("Failed to run command")
|
||||
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)")
|
||||
}
|
||||
|
@@ -0,0 +1,20 @@
|
||||
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)
|
||||
}
|
||||
}
|
12
buildSrc/src/main/kotlin/cc/tweaked/gradle/XmlUtil.kt
Normal file
12
buildSrc/src/main/kotlin/cc/tweaked/gradle/XmlUtil.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
||||
}
|
||||
}
|
@@ -17,7 +17,10 @@
|
||||
<module name="TreeWalker">
|
||||
<!-- Annotations -->
|
||||
<module name="AnnotationLocation" />
|
||||
<module name="AnnotationUseStyle" />
|
||||
<module name="AnnotationUseStyle">
|
||||
<!-- We want trailing commas on multiline arrays. -->
|
||||
<property name="trailingArrayComma" value="ignore" />
|
||||
</module>
|
||||
<module name="MissingDeprecated" />
|
||||
<module name="MissingOverride" />
|
||||
|
||||
@@ -26,17 +29,11 @@
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="ignored" />
|
||||
</module>
|
||||
<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="LeftCurly" />
|
||||
<module name="NeedBraces">
|
||||
<property name="allowSingleLineStatement" value="true"/>
|
||||
</module>
|
||||
<module name="RightCurly">
|
||||
<property name="option" value="alone" />
|
||||
</module>
|
||||
<module name="RightCurly" />
|
||||
|
||||
<!-- Class design. As if we've ever followed good practice here. -->
|
||||
<module name="FinalClass" />
|
||||
@@ -58,20 +55,29 @@
|
||||
<module name="SimplifyBooleanExpression" />
|
||||
<module name="SimplifyBooleanReturn" />
|
||||
<module name="StringLiteralEquality" />
|
||||
<module name="UnnecessaryParentheses" />
|
||||
<module name="UnnecessaryParentheses">
|
||||
<!-- Default minus LAND. -->
|
||||
<property name="tokens" value="EXPR,IDENT,NUM_DOUBLE,NUM_FLOAT,NUM_INT,NUM_LONG,STRING_LITERAL,LITERAL_NULL,LITERAL_FALSE,LITERAL_TRUE,ASSIGN,BAND_ASSIGN,BOR_ASSIGN,BSR_ASSIGN,BXOR_ASSIGN,DIV_ASSIGN,MINUS_ASSIGN,MOD_ASSIGN,PLUS_ASSIGN,SL_ASSIGN,SR_ASSIGN,STAR_ASSIGN,LAMBDA,TEXT_BLOCK_LITERAL_BEGIN,LITERAL_INSTANCEOF,GT,LT,GE,LE,EQUAL,NOT_EQUAL,UNARY_MINUS,UNARY_PLUS,INC,DEC,LNOT,BNOT,POST_INC,POST_DEC" />
|
||||
</module>
|
||||
<module name="UnnecessarySemicolonAfterTypeMemberDeclaration" />
|
||||
<module name="UnnecessarySemicolonInTryWithResources" />
|
||||
<module name="UnnecessarySemicolonInEnumeration" />
|
||||
|
||||
<!-- Imports -->
|
||||
<module name="CustomImportOrder" />
|
||||
<module name="CustomImportOrder">
|
||||
<property name="customImportOrderRules"
|
||||
value="THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE###STATIC"
|
||||
/>
|
||||
</module>
|
||||
<module name="IllegalImport" />
|
||||
<module name="RedundantImport" />
|
||||
<module name="UnusedImports" />
|
||||
|
||||
<!-- Javadoc -->
|
||||
<!-- TODO: Missing* checks for the dan200.computercraft.api package? -->
|
||||
<module name="AtclauseOrder" />
|
||||
<module name="AtclauseOrder">
|
||||
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
||||
</module>
|
||||
<module name="InvalidJavadocPosition" />
|
||||
<module name="JavadocBlockTagLocation" />
|
||||
<module name="JavadocMethod"/>
|
||||
@@ -100,19 +106,16 @@
|
||||
<module name="LocalFinalVariableName" />
|
||||
<module name="LocalVariableName" />
|
||||
<module name="MemberName" />
|
||||
<module name="MethodName" />
|
||||
<module name="MethodName">
|
||||
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
|
||||
</module>
|
||||
<module name="MethodTypeParameterName" />
|
||||
<module name="PackageName">
|
||||
<property name="format" value="^dan200\.computercraft(\.[a-z][a-z0-9]*)*" />
|
||||
<property name="format" value="^(dan200\.computercraft|cc\.tweaked)(\.[a-z][a-z0-9]*)*" />
|
||||
</module>
|
||||
<module name="ParameterName" />
|
||||
<module name="StaticVariableName">
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
|
||||
<property name="applyToPrivate" value="false" />
|
||||
</module>
|
||||
<module name="StaticVariableName">
|
||||
<property name="format" value="^(s_)?[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
|
||||
<property name="applyToPrivate" value="true" />
|
||||
</module>
|
||||
<module name="TypeName" />
|
||||
|
||||
@@ -125,18 +128,11 @@
|
||||
<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" />
|
||||
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP,METHOD_REF" />
|
||||
</module>
|
||||
<module name="NoWhitespaceBefore" />
|
||||
<!-- TODO: Decide on an OperatorWrap style. -->
|
||||
<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="ParenPad" />
|
||||
<module name="SeparatorWrap">
|
||||
<property name="option" value="eol" />
|
||||
<property name="tokens" value="COMMA,SEMI,ELLIPSIS,ARRAY_DECLARATOR,RBRACK,METHOD_REF" />
|
||||
@@ -156,6 +152,7 @@
|
||||
<property name="allowEmptyLambdas" value="true" />
|
||||
<property name="allowEmptyMethods" value="true" />
|
||||
<property name="allowEmptyConstructors" value="true" />
|
||||
<property name="allowEmptyTypes" value="true" />
|
||||
|
||||
<property name="tokens" value="ASSIGN,BAND,BAND_ASSIGN,BOR,BOR_ASSIGN,BSR,BSR_ASSIGN,BXOR,BXOR_ASSIGN,COLON,DIV,DIV_ASSIGN,EQUAL,GE,GT,LAMBDA,LAND,LCURLY,LE,LITERAL_RETURN,LOR,LT,MINUS,MINUS_ASSIGN,MOD,MOD_ASSIGN,NOT_EQUAL,PLUS,PLUS_ASSIGN,QUESTION,RCURLY,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,LITERAL_ASSERT,TYPE_EXTENSION_AND" />
|
||||
</module>
|
||||
|
@@ -3,6 +3,6 @@ FROM gitpod/workspace-base
|
||||
USER gitpod
|
||||
|
||||
RUN sudo apt-get -q update \
|
||||
&& sudo apt-get install -yq openjdk-8-jdk openjdk-16-jdk python3-pip npm \
|
||||
&& sudo apt-get install -yq openjdk-16-jdk python3-pip npm \
|
||||
&& sudo pip3 install pre-commit \
|
||||
&& sudo update-java-alternatives --set java-1.8.0-openjdk-amd64
|
||||
&& sudo update-java-alternatives --set java-1.16.0-openjdk-amd64
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,61 +0,0 @@
|
||||
<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>
|
@@ -5,7 +5,8 @@ kotlin.stdlib.default.dependency=false
|
||||
kotlin.jvm.target.validation.mode=error
|
||||
|
||||
# Mod properties
|
||||
modVersion=1.101.2
|
||||
isUnstable=true
|
||||
modVersion=1.102.2
|
||||
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.16.5
|
||||
mcVersion=1.19.3
|
||||
|
@@ -2,70 +2,152 @@
|
||||
|
||||
# Minecraft
|
||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
||||
forge = "36.2.34"
|
||||
parchment = "2021.08.08"
|
||||
parchmentMc = "1.16.5"
|
||||
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"
|
||||
|
||||
# Normal dependencies
|
||||
asm = "9.3"
|
||||
autoService = "1.0.1"
|
||||
cobalt = "0.6.0"
|
||||
checkerFramework = "3.12.0"
|
||||
cobalt = "0.5.9"
|
||||
fastutil = "8.5.9"
|
||||
guava = "31.1-jre"
|
||||
jetbrainsAnnotations = "23.0.0"
|
||||
jsr305 = "3.0.2"
|
||||
kotlin = "1.7.10"
|
||||
kotlin-coroutines = "1.6.0"
|
||||
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"
|
||||
|
||||
# Testing
|
||||
byteBuddy = "1.12.19"
|
||||
hamcrest = "2.2"
|
||||
jqwik = "1.7.0"
|
||||
junit = "5.9.1"
|
||||
|
||||
# Build tools
|
||||
cctJavadoc = "1.5.2"
|
||||
checkstyle = "8.25" # There's a reason we're pinned on an ancient version, but I can't remember what it is.
|
||||
cctJavadoc = "1.5.3"
|
||||
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"
|
||||
illuaminate = "0.1.0-20-g8c483a4"
|
||||
ideaExt = "1.1.6"
|
||||
illuaminate = "0.1.0-12-ga03e9cd"
|
||||
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-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
|
||||
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", 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" }
|
||||
|
||||
# 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]
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
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" }
|
||||
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" }
|
||||
|
||||
[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"]
|
||||
|
@@ -2,25 +2,26 @@
|
||||
|
||||
(sources
|
||||
/doc/
|
||||
/build/docs/luaJavadoc/
|
||||
/src/main/resources/*/computercraft/lua/bios.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/
|
||||
/src/test/resources/test-rom
|
||||
/src/web/mount)
|
||||
/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)
|
||||
|
||||
|
||||
(doc
|
||||
(destination build/illuaminate)
|
||||
; Also defined in projects/web/build.gradle.kts
|
||||
(destination /projects/web/build/illuaminate)
|
||||
(index doc/index.md)
|
||||
|
||||
(site
|
||||
(title "CC: Tweaked")
|
||||
(logo src/main/resources/pack.png)
|
||||
(logo projects/common/src/main/resources/pack.png)
|
||||
(url https://tweaked.cc/)
|
||||
(source-link https://github.com/cc-tweaked/CC-Tweaked/blob/${commit}/${path}#L${line})
|
||||
|
||||
(styles src/web/styles.css)
|
||||
(scripts build/rollup/index.js)
|
||||
(styles /projects/web/src/styles.css)
|
||||
(scripts /projects/web/build/rollup/index.js)
|
||||
(head doc/head.html))
|
||||
|
||||
(module-kinds
|
||||
@@ -32,15 +33,15 @@
|
||||
|
||||
(library-path
|
||||
/doc/stub/
|
||||
/build/docs/luaJavadoc/
|
||||
/projects/forge/build/docs/luaJavadoc/
|
||||
|
||||
/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/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/modules/main/
|
||||
/src/main/resources/*/computercraft/lua/rom/modules/command/
|
||||
/src/main/resources/*/computercraft/lua/rom/modules/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/))
|
||||
|
||||
(at /
|
||||
(linters
|
||||
@@ -80,31 +81,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
|
||||
(/src/main/resources/*/computercraft/lua/bios.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/)
|
||||
(/projects/core/src/main/resources/data/computercraft/lua/bios.lua
|
||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/)
|
||||
(linters -var:unused-global)
|
||||
(lint (allow-toplevel-global true)))
|
||||
|
||||
;; Silence some variable warnings in documentation stubs.
|
||||
(at (/doc/stub/ /build/docs/luaJavadoc/)
|
||||
(at (/doc/stub/ /projects/forge/build/docs/luaJavadoc/)
|
||||
(linters -var:unused-global)
|
||||
(lint (allow-toplevel-global true)))
|
||||
|
||||
;; Suppress warnings for currently undocumented modules.
|
||||
(at
|
||||
(; Lua APIs
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/window.lua)
|
||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/io.lua
|
||||
/projects/core/src/main/resources/data/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
|
||||
(/src/main/resources/*/computercraft/lua/bios.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/apis/turtle/turtle.lua)
|
||||
(/projects/core/src/main/resources/data/computercraft/lua/bios.lua
|
||||
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua)
|
||||
(linters -var:deprecated))
|
||||
|
||||
(at /src/test/resources/test-rom
|
||||
(at /projects/core/src/test/resources/test-rom
|
||||
; We should still be able to test deprecated members.
|
||||
(linters -var:deprecated)
|
||||
|
||||
@@ -113,4 +114,4 @@
|
||||
:max sleep write
|
||||
cct_test describe expect howlci fail it pending stub before_each)))
|
||||
|
||||
(at /src/web/mount/expr_template.lua (lint (globals :max __expr__)))
|
||||
(at /projects/web/src/mount/expr_template.lua (lint (globals :max __expr__)))
|
||||
|
159
projects/ARCHITECTURE.md
Normal file
159
projects/ARCHITECTURE.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# 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"
|
20
projects/common-api/build.gradle.kts
Normal file
20
projects/common-api/build.gradle.kts
Normal file
@@ -0,0 +1,20 @@
|
||||
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 })
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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() {
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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() {
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.item.Item;
|
||||
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");
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A reference to a block in the world, used by block detail providers.
|
||||
*
|
||||
* @param level The level the block exists in.
|
||||
* @param pos The position of the block.
|
||||
* @param state The block state at this position.
|
||||
* @param blockEntity The block entity at this position, if it exists.
|
||||
*/
|
||||
public record BlockReference(
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
BlockState state,
|
||||
@Nullable BlockEntity blockEntity
|
||||
) {
|
||||
public BlockReference(Level level, BlockPos pos) {
|
||||
this(level, pos, level.getBlockState(pos), level.getBlockEntity(pos));
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
@@ -14,21 +14,20 @@ import java.util.Map;
|
||||
* <p>
|
||||
* These are used by computer methods such as {@code turtle.getItemDetail()} or {@code turtle.inspect()}.
|
||||
* <p>
|
||||
* Specific instances of this registry are available from {@link DetailRegistries} and loader-specific versions
|
||||
* Specific instances of this registry are available from {@link VanillaDetailRegistries} and loader-specific versions
|
||||
* also in this package.
|
||||
*
|
||||
* @param <T> The type of object that this registry provides details for.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface DetailRegistry<T>
|
||||
{
|
||||
public interface DetailRegistry<T> {
|
||||
/**
|
||||
* Registers a detail provider.
|
||||
*
|
||||
* @param provider The detail provider to register.
|
||||
* @see IDetailProvider
|
||||
* @see DetailProvider
|
||||
*/
|
||||
void addProvider( IDetailProvider<T> provider );
|
||||
void addProvider(DetailProvider<T> provider);
|
||||
|
||||
/**
|
||||
* Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable
|
||||
@@ -37,7 +36,7 @@ public interface DetailRegistry<T>
|
||||
* @param object The object to get details for.
|
||||
* @return The basic details.
|
||||
*/
|
||||
Map<String, Object> getBasicDetails( T object );
|
||||
Map<String, Object> getBasicDetails(T object);
|
||||
|
||||
/**
|
||||
* Compute all details about an object, using {@link #getBasicDetails(Object)} and any registered providers.
|
||||
@@ -45,5 +44,5 @@ public interface DetailRegistry<T>
|
||||
* @param object The object to get details for.
|
||||
* @return The computed details.
|
||||
*/
|
||||
Map<String, Object> getDetails( T object );
|
||||
Map<String, Object> getDetails(T object);
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
public static final DetailRegistry<ItemStack> ITEM_STACK = ComputerCraftAPIService.get().getItemStackDetailRegistry();
|
||||
|
||||
/**
|
||||
* Provides details for {@link BlockReference}, a reference to a {@link Block} in the world.
|
||||
*/
|
||||
public static final DetailRegistry<BlockReference> BLOCK_IN_WORLD = ComputerCraftAPIService.get().getBlockInWorldDetailRegistry();
|
||||
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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
|
||||
) {
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -6,19 +6,17 @@
|
||||
package dan200.computercraft.api.pocket;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Wrapper class for pocket computers.
|
||||
*/
|
||||
public interface IPocketAccess
|
||||
{
|
||||
public interface IPocketAccess {
|
||||
/**
|
||||
* Gets the entity holding this item.
|
||||
* <p>
|
||||
@@ -45,7 +43,7 @@ public interface IPocketAccess
|
||||
* {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
|
||||
* @see #getColour()
|
||||
*/
|
||||
void setColour( int colour );
|
||||
void setColour(int colour);
|
||||
|
||||
/**
|
||||
* Get the colour of this pocket computer's light as a RGB number.
|
||||
@@ -63,7 +61,7 @@ public interface IPocketAccess
|
||||
* {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
|
||||
* @see #getLight()
|
||||
*/
|
||||
void setLight( int colour );
|
||||
void setLight(int colour);
|
||||
|
||||
/**
|
||||
* Get the upgrade-specific NBT.
|
||||
@@ -73,8 +71,7 @@ public interface IPocketAccess
|
||||
* @return The upgrade's NBT.
|
||||
* @see #updateUpgradeNBTData()
|
||||
*/
|
||||
@Nonnull
|
||||
CompoundNBT getUpgradeNBTData();
|
||||
CompoundTag getUpgradeNBTData();
|
||||
|
||||
/**
|
||||
* Mark the upgrade-specific NBT as dirty.
|
||||
@@ -93,6 +90,5 @@ public interface IPocketAccess
|
||||
*
|
||||
* @return A collection of all upgrade names.
|
||||
*/
|
||||
@Nonnull
|
||||
Map<ResourceLocation, IPeripheral> getUpgrades();
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A peripheral which can be equipped to the back side of a pocket computer.
|
||||
* <p>
|
||||
* Pocket upgrades are defined in two stages. First, on creates a {@link IPocketUpgrade} subclass and corresponding
|
||||
* {@link PocketUpgradeSerialiser} 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 PocketUpgradeSerialiser} for details on this process
|
||||
* and where files should be located.
|
||||
*
|
||||
* @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
|
||||
*/
|
||||
public interface IPocketUpgrade extends UpgradeBase {
|
||||
/**
|
||||
* Creates a peripheral for the pocket computer.
|
||||
* <p>
|
||||
* The peripheral created will be stored for the lifetime of the upgrade, will be passed an argument to
|
||||
* {@link #update(IPocketAccess, IPeripheral)} and will be attached, detached and have methods called in the same
|
||||
* manner as an ordinary peripheral.
|
||||
*
|
||||
* @param access The access object for the pocket item stack.
|
||||
* @return The newly created peripheral.
|
||||
* @see #update(IPocketAccess, IPeripheral)
|
||||
*/
|
||||
@Nullable
|
||||
IPeripheral createPeripheral(IPocketAccess access);
|
||||
|
||||
/**
|
||||
* Called when the pocket computer item stack updates.
|
||||
*
|
||||
* @param access The access object for the pocket item stack.
|
||||
* @param peripheral The peripheral for this upgrade.
|
||||
* @see #createPeripheral(IPocketAccess)
|
||||
*/
|
||||
default void update(IPocketAccess access, @Nullable IPeripheral peripheral) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the pocket computer is right clicked.
|
||||
*
|
||||
* @param world The world the computer is in.
|
||||
* @param access The access object for the pocket item stack.
|
||||
* @param peripheral The peripheral for this upgrade.
|
||||
* @return {@code true} to stop the GUI from opening, otherwise false. You should always provide some code path
|
||||
* which returns {@code false}, such as requiring the player to be sneaking - otherwise they will be unable to
|
||||
* access the GUI.
|
||||
* @see #createPeripheral(IPocketAccess)
|
||||
*/
|
||||
default boolean onRightClick(Level world, IPocketAccess access, @Nullable IPeripheral peripheral) {
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -9,15 +9,13 @@ import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.lua.ILuaCallback;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import net.minecraft.inventory.IInventory;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.items.IItemHandlerModifiable;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
@@ -26,24 +24,35 @@ import javax.annotation.Nullable;
|
||||
* This should not be implemented by your classes. Do not interact with turtles except via this interface and
|
||||
* {@link ITurtleUpgrade}.
|
||||
*/
|
||||
public interface ITurtleAccess
|
||||
{
|
||||
public interface ITurtleAccess {
|
||||
/**
|
||||
* Returns the world in which the turtle resides.
|
||||
*
|
||||
* @return the world in which the turtle resides.
|
||||
*/
|
||||
@Nonnull
|
||||
World getWorld();
|
||||
Level getLevel();
|
||||
|
||||
/**
|
||||
* Returns a vector containing the integer co-ordinates at which the turtle resides.
|
||||
*
|
||||
* @return a vector containing the integer co-ordinates at which the turtle resides.
|
||||
*/
|
||||
@Nonnull
|
||||
BlockPos getPosition();
|
||||
|
||||
/**
|
||||
* Determine if this turtle has been removed.
|
||||
* <p>
|
||||
* It's possible for a turtle to be removed while a {@link TurtleCommand} is executed, for instance if interacting
|
||||
* with a block causes the turtle to be blown up. It's recommended you check the turtle is still present before
|
||||
* trying to interact with it again.
|
||||
* <p>
|
||||
* If a turtle has been removed {@link #getLevel()} and {@link #getPosition()} will continue to function as before.
|
||||
* All other methods will fail.
|
||||
*
|
||||
* @return Whether this turtle has been removed.
|
||||
*/
|
||||
boolean isRemoved();
|
||||
|
||||
/**
|
||||
* Attempt to move this turtle to a new position.
|
||||
* <p>
|
||||
@@ -56,7 +65,7 @@ public interface ITurtleAccess
|
||||
* was cancelled.
|
||||
* @throws UnsupportedOperationException When attempting to teleport on the client side.
|
||||
*/
|
||||
boolean teleportTo( @Nonnull World world, @Nonnull BlockPos pos );
|
||||
boolean teleportTo(Level world, BlockPos pos);
|
||||
|
||||
/**
|
||||
* Returns a vector containing the floating point co-ordinates at which the turtle is rendered.
|
||||
@@ -66,8 +75,7 @@ public interface ITurtleAccess
|
||||
* @return A vector containing the floating point co-ordinates at which the turtle resides.
|
||||
* @see #getVisualYaw(float)
|
||||
*/
|
||||
@Nonnull
|
||||
Vector3d getVisualPosition( float f );
|
||||
Vec3 getVisualPosition(float f);
|
||||
|
||||
/**
|
||||
* Returns the yaw the turtle is facing when it is rendered.
|
||||
@@ -76,7 +84,7 @@ public interface ITurtleAccess
|
||||
* @return The yaw the turtle is facing.
|
||||
* @see #getVisualPosition(float)
|
||||
*/
|
||||
float getVisualYaw( float f );
|
||||
float getVisualYaw(float f);
|
||||
|
||||
/**
|
||||
* Returns the world direction the turtle is currently facing.
|
||||
@@ -84,7 +92,6 @@ public interface ITurtleAccess
|
||||
* @return The world direction the turtle is currently facing.
|
||||
* @see #setDirection(Direction)
|
||||
*/
|
||||
@Nonnull
|
||||
Direction getDirection();
|
||||
|
||||
/**
|
||||
@@ -94,7 +101,7 @@ public interface ITurtleAccess
|
||||
* @param dir The new direction to set. This should be on either the x or z axis (so north, south, east or west).
|
||||
* @see #getDirection()
|
||||
*/
|
||||
void setDirection( @Nonnull Direction dir );
|
||||
void setDirection(Direction dir);
|
||||
|
||||
/**
|
||||
* Get the currently selected slot in the turtle's inventory.
|
||||
@@ -114,7 +121,7 @@ public interface ITurtleAccess
|
||||
* @see #getInventory()
|
||||
* @see #getSelectedSlot()
|
||||
*/
|
||||
void setSelectedSlot( int slot );
|
||||
void setSelectedSlot(int slot);
|
||||
|
||||
/**
|
||||
* Set the colour of the turtle to a RGB number.
|
||||
@@ -123,7 +130,7 @@ public interface ITurtleAccess
|
||||
* and {@code 0xFFFFFF} or -1 to reset to the default colour.
|
||||
* @see #getColour()
|
||||
*/
|
||||
void setColour( int colour );
|
||||
void setColour(int colour);
|
||||
|
||||
/**
|
||||
* Get the colour of this turtle as a RGB number.
|
||||
@@ -148,23 +155,8 @@ public interface ITurtleAccess
|
||||
* Note: this inventory should only be accessed and modified on the server thread.
|
||||
*
|
||||
* @return This turtle's inventory
|
||||
* @see #getItemHandler()
|
||||
*/
|
||||
@Nonnull
|
||||
IInventory getInventory();
|
||||
|
||||
/**
|
||||
* Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
|
||||
* <p>
|
||||
* Note: this inventory should only be accessed and modified on the server thread.
|
||||
*
|
||||
* @return This turtle's inventory
|
||||
* @see #getInventory()
|
||||
* @see IItemHandlerModifiable
|
||||
* @see net.minecraftforge.items.CapabilityItemHandler#ITEM_HANDLER_CAPABILITY
|
||||
*/
|
||||
@Nonnull
|
||||
IItemHandlerModifiable getItemHandler();
|
||||
Container getInventory();
|
||||
|
||||
/**
|
||||
* Determine whether this turtle will require fuel when performing actions.
|
||||
@@ -194,7 +186,7 @@ public interface ITurtleAccess
|
||||
* @see #addFuel(int)
|
||||
* @see #consumeFuel(int)
|
||||
*/
|
||||
void setFuelLevel( int fuel );
|
||||
void setFuelLevel(int fuel);
|
||||
|
||||
/**
|
||||
* Get the maximum amount of fuel a turtle can hold.
|
||||
@@ -211,7 +203,7 @@ public interface ITurtleAccess
|
||||
* greater than the current fuel level of the turtle. No fuel will be consumed if {@code false} is returned.
|
||||
* @throws UnsupportedOperationException When attempting to consume fuel on the client side.
|
||||
*/
|
||||
boolean consumeFuel( int fuel );
|
||||
boolean consumeFuel(int fuel);
|
||||
|
||||
/**
|
||||
* Increase the turtle's fuel level by the given amount.
|
||||
@@ -219,7 +211,7 @@ public interface ITurtleAccess
|
||||
* @param fuel The amount to refuel with.
|
||||
* @throws UnsupportedOperationException When attempting to refuel on the client side.
|
||||
*/
|
||||
void addFuel( int fuel );
|
||||
void addFuel(int fuel);
|
||||
|
||||
/**
|
||||
* Adds a custom command to the turtles command queue. Unlike peripheral methods, these custom commands will be executed
|
||||
@@ -232,11 +224,10 @@ public interface ITurtleAccess
|
||||
* @return The objects the command returned when executed. you should probably return these to the player
|
||||
* unchanged if called from a peripheral method.
|
||||
* @throws UnsupportedOperationException When attempting to execute a command on the client side.
|
||||
* @see ITurtleCommand
|
||||
* @see TurtleCommand
|
||||
* @see MethodResult#pullEvent(String, ILuaCallback)
|
||||
*/
|
||||
@Nonnull
|
||||
MethodResult executeCommand( @Nonnull ITurtleCommand command );
|
||||
MethodResult executeCommand(TurtleCommand command);
|
||||
|
||||
/**
|
||||
* Start playing a specific animation. This will prevent other turtle commands from executing until
|
||||
@@ -246,7 +237,7 @@ public interface ITurtleAccess
|
||||
* @throws UnsupportedOperationException When attempting to execute play an animation on the client side.
|
||||
* @see TurtleAnimation
|
||||
*/
|
||||
void playAnimation( @Nonnull TurtleAnimation animation );
|
||||
void playAnimation(TurtleAnimation animation);
|
||||
|
||||
/**
|
||||
* Returns the turtle on the specified side of the turtle, if there is one.
|
||||
@@ -256,7 +247,7 @@ public interface ITurtleAccess
|
||||
* @see #setUpgrade(TurtleSide, ITurtleUpgrade)
|
||||
*/
|
||||
@Nullable
|
||||
ITurtleUpgrade getUpgrade( @Nonnull TurtleSide side );
|
||||
ITurtleUpgrade getUpgrade(TurtleSide side);
|
||||
|
||||
/**
|
||||
* Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
|
||||
@@ -265,7 +256,7 @@ public interface ITurtleAccess
|
||||
* @param upgrade The upgrade to set, may be {@code null} to clear.
|
||||
* @see #getUpgrade(TurtleSide)
|
||||
*/
|
||||
void setUpgrade( @Nonnull TurtleSide side, @Nullable ITurtleUpgrade upgrade );
|
||||
void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade);
|
||||
|
||||
/**
|
||||
* Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
|
||||
@@ -274,7 +265,7 @@ public interface ITurtleAccess
|
||||
* @return The peripheral created by the upgrade on the specified side of the turtle, {@code null} if none exists.
|
||||
*/
|
||||
@Nullable
|
||||
IPeripheral getPeripheral( @Nonnull TurtleSide side );
|
||||
IPeripheral getPeripheral(TurtleSide side);
|
||||
|
||||
/**
|
||||
* Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
|
||||
@@ -286,8 +277,7 @@ public interface ITurtleAccess
|
||||
* @return The upgrade-specific data.
|
||||
* @see #updateUpgradeNBTData(TurtleSide)
|
||||
*/
|
||||
@Nonnull
|
||||
CompoundNBT getUpgradeNBTData( @Nullable TurtleSide side );
|
||||
CompoundTag getUpgradeNBTData(TurtleSide side);
|
||||
|
||||
/**
|
||||
* Mark the upgrade-specific data as dirty on a specific side. This is required for the data to be synced to the
|
||||
@@ -296,5 +286,5 @@ public interface ITurtleAccess
|
||||
* @param side The side to mark dirty.
|
||||
* @see #updateUpgradeNBTData(TurtleSide)
|
||||
*/
|
||||
void updateUpgradeNBTData( @Nonnull TurtleSide side );
|
||||
void updateUpgradeNBTData(TurtleSide side);
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
@@ -12,8 +12,7 @@ package dan200.computercraft.api.turtle;
|
||||
*
|
||||
* @see ITurtleAccess#playAnimation(TurtleAnimation)
|
||||
*/
|
||||
public enum TurtleAnimation
|
||||
{
|
||||
public enum TurtleAnimation {
|
||||
/**
|
||||
* An animation which does nothing. This takes no time to complete.
|
||||
*
|
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
@@ -8,8 +8,7 @@ package dan200.computercraft.api.turtle;
|
||||
/**
|
||||
* An enum representing the two sides of the turtle that a turtle turtle might reside.
|
||||
*/
|
||||
public enum TurtleSide
|
||||
{
|
||||
public enum TurtleSide {
|
||||
/**
|
||||
* The turtle's left side (where the pickaxe usually is on a Wireless Mining Turtle).
|
||||
*/
|
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 futher 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());
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -10,8 +10,7 @@ package dan200.computercraft.api.turtle;
|
||||
*
|
||||
* @see ITurtleUpgrade#getType()
|
||||
*/
|
||||
public enum TurtleUpgradeType
|
||||
{
|
||||
public enum TurtleUpgradeType {
|
||||
/**
|
||||
* A tool is rendered as an item on the side of the turtle, and responds to the {@code turtle.dig()}
|
||||
* and {@code turtle.attack()} methods (Such as pickaxe or sword on Mining and Melee turtles).
|
||||
@@ -31,13 +30,11 @@ public enum TurtleUpgradeType
|
||||
*/
|
||||
BOTH;
|
||||
|
||||
public boolean isTool()
|
||||
{
|
||||
public boolean isTool() {
|
||||
return this == TOOL || this == BOTH;
|
||||
}
|
||||
|
||||
public boolean isPeripheral()
|
||||
{
|
||||
public boolean isPeripheral() {
|
||||
return this == PERIPHERAL || this == BOTH;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.core.Direction;
|
||||
|
||||
/**
|
||||
* An enum representing the different actions that an {@link ITurtleUpgrade} of type Tool may be called on to perform by
|
||||
@@ -14,8 +14,7 @@ import net.minecraft.util.Direction;
|
||||
* @see ITurtleUpgrade#getType()
|
||||
* @see ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)
|
||||
*/
|
||||
public enum TurtleVerb
|
||||
{
|
||||
public enum TurtleVerb {
|
||||
/**
|
||||
* The turtle called {@code turtle.dig()}, {@code turtle.digUp()} or {@code turtle.digDown()}.
|
||||
*/
|
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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";
|
||||
}
|
||||
}
|
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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 beeen 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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() {
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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() {
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@ package dan200.computercraft.impl;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* A ComputerCraft-related service failed to load.
|
||||
@@ -15,12 +16,11 @@ import javax.annotation.Nullable;
|
||||
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
class ServiceException extends RuntimeException
|
||||
{
|
||||
class ServiceException extends RuntimeException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -8392300691666423882L;
|
||||
|
||||
ServiceException( String message, @Nullable Throwable cause )
|
||||
{
|
||||
super( message, cause );
|
||||
ServiceException(String message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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;
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
36
projects/common/build.gradle.kts
Normal file
36
projects/common/build.gradle.kts
Normal file
@@ -0,0 +1,36 @@
|
||||
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)
|
||||
}
|
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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 dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.ComputerCraftAPIClient;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.client.gui.*;
|
||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||
import dan200.computercraft.client.turtle.TurtleModemModeller;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
|
||||
import dan200.computercraft.shared.media.items.DiskItem;
|
||||
import dan200.computercraft.shared.media.items.TreasureDiskItem;
|
||||
import net.minecraft.client.color.item.ItemColor;
|
||||
import net.minecraft.client.gui.screens.MenuScreens;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.ShaderInstance;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
|
||||
import net.minecraft.client.renderer.item.ItemProperties;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceProvider;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Registers client-side objects, such as {@link BlockEntityRendererProvider}s and
|
||||
* {@link MenuScreens.ScreenConstructor}.
|
||||
* <p>
|
||||
* The functions in this class should be called from a loader-specific class.
|
||||
*
|
||||
* @see ModRegistry The common registry for actual game objects.
|
||||
*/
|
||||
public final class ClientRegistry {
|
||||
private ClientRegistry() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any client-side objects which don't have to be done on the main thread.
|
||||
*/
|
||||
public static void register() {
|
||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
|
||||
));
|
||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
|
||||
new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
|
||||
));
|
||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
|
||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
|
||||
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any client-side objects which must be done on the main thread.
|
||||
*/
|
||||
public static void registerMainThread() {
|
||||
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
|
||||
MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
|
||||
MenuScreens.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
|
||||
MenuScreens.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
|
||||
|
||||
MenuScreens.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
|
||||
MenuScreens.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
|
||||
MenuScreens.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
|
||||
|
||||
MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
|
||||
|
||||
registerItemProperty("state",
|
||||
new UnclampedPropertyFunction((stack, world, player, random) -> ClientPocketComputers.get(stack).getState().ordinal()),
|
||||
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
|
||||
);
|
||||
registerItemProperty("coloured",
|
||||
(stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
|
||||
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
|
||||
);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static void registerItemProperty(String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
|
||||
var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
|
||||
for (var item : items) ItemProperties.register(item.get(), id, getter);
|
||||
}
|
||||
|
||||
private static final String[] EXTRA_MODELS = new String[]{
|
||||
// Turtle upgrades
|
||||
"block/turtle_modem_normal_off_left",
|
||||
"block/turtle_modem_normal_on_left",
|
||||
"block/turtle_modem_normal_off_right",
|
||||
"block/turtle_modem_normal_on_right",
|
||||
|
||||
"block/turtle_modem_advanced_off_left",
|
||||
"block/turtle_modem_advanced_on_left",
|
||||
"block/turtle_modem_advanced_off_right",
|
||||
"block/turtle_modem_advanced_on_right",
|
||||
|
||||
"block/turtle_crafting_table_left",
|
||||
"block/turtle_crafting_table_right",
|
||||
|
||||
"block/turtle_speaker_left",
|
||||
"block/turtle_speaker_right",
|
||||
|
||||
// Turtle block renderer
|
||||
"block/turtle_colour",
|
||||
"block/turtle_elf_overlay",
|
||||
};
|
||||
|
||||
public static void registerExtraModels(Consumer<ResourceLocation> register) {
|
||||
for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
|
||||
}
|
||||
|
||||
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
|
||||
register.accept(
|
||||
(stack, layer) -> layer == 1 ? ((DiskItem) stack.getItem()).getColour(stack) : 0xFFFFFF,
|
||||
ModRegistry.Items.DISK.get()
|
||||
);
|
||||
|
||||
register.accept(
|
||||
(stack, layer) -> layer == 1 ? TreasureDiskItem.getColour(stack) : 0xFFFFFF,
|
||||
ModRegistry.Items.TREASURE_DISK.get()
|
||||
);
|
||||
|
||||
register.accept(ClientRegistry::getPocketColour, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get());
|
||||
register.accept(ClientRegistry::getPocketColour, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
|
||||
|
||||
register.accept(ClientRegistry::getTurtleColour, ModRegistry.Blocks.TURTLE_NORMAL.get());
|
||||
register.accept(ClientRegistry::getTurtleColour, ModRegistry.Blocks.TURTLE_ADVANCED.get());
|
||||
}
|
||||
|
||||
private static int getPocketColour(ItemStack stack, int layer) {
|
||||
switch (layer) {
|
||||
case 0:
|
||||
default:
|
||||
return 0xFFFFFF;
|
||||
case 1: // Frame colour
|
||||
return IColouredItem.getColourBasic(stack);
|
||||
case 2: { // Light colour
|
||||
var light = ClientPocketComputers.get(stack).getLightState();
|
||||
return light == -1 ? Colour.BLACK.getHex() : light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getTurtleColour(ItemStack stack, int layer) {
|
||||
return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF;
|
||||
}
|
||||
|
||||
public static void registerBlockEntityRenderers(BlockEntityRenderRegistry register) {
|
||||
register.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), MonitorBlockEntityRenderer::new);
|
||||
register.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
|
||||
register.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
|
||||
register.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), TurtleBlockEntityRenderer::new);
|
||||
}
|
||||
|
||||
public interface BlockEntityRenderRegistry {
|
||||
<T extends BlockEntity> void register(BlockEntityType<? extends T> type, BlockEntityRendererProvider<T> provider);
|
||||
}
|
||||
|
||||
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
|
||||
RenderTypes.registerShaders(resources, load);
|
||||
}
|
||||
|
||||
private record UnclampedPropertyFunction(
|
||||
ClampedItemPropertyFunction function
|
||||
) implements ClampedItemPropertyFunction {
|
||||
@Override
|
||||
public float unclampedCall(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int layer) {
|
||||
return function.unclampedCall(stack, level, entity, layer);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public float call(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int layer) {
|
||||
return function.unclampedCall(stack, level, entity, layer);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 dan200.computercraft.shared.command.text.ChatHelpers;
|
||||
import dan200.computercraft.shared.command.text.TableBuilder;
|
||||
import dan200.computercraft.shared.command.text.TableFormatter;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.GuiMessageTag;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Mth;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ClientTableFormatter implements TableFormatter {
|
||||
public static final ClientTableFormatter INSTANCE = new ClientTableFormatter();
|
||||
|
||||
private static Font renderer() {
|
||||
return Minecraft.getInstance().font;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Component getPadding(Component component, int width) {
|
||||
var extraWidth = width - getWidth(component);
|
||||
if (extraWidth <= 0) return null;
|
||||
|
||||
var renderer = renderer();
|
||||
|
||||
float spaceWidth = renderer.width(" ");
|
||||
var spaces = Mth.floor(extraWidth / spaceWidth);
|
||||
var extra = extraWidth - (int) (spaces * spaceWidth);
|
||||
|
||||
return ChatHelpers.coloured(StringUtils.repeat(' ', spaces) + StringUtils.repeat((char) 712, extra), ChatFormatting.GRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPadding() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth(Component component) {
|
||||
return renderer().width(component);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLine(String label, Component component) {
|
||||
var mc = Minecraft.getInstance();
|
||||
var chat = mc.gui.getChat();
|
||||
|
||||
// TODO: Trim the text if it goes over the allowed length
|
||||
// int maxWidth = MathHelper.floor( chat.getChatWidth() / chat.getScale() );
|
||||
// List<ITextProperties> list = RenderComponentsUtil.wrapComponents( component, maxWidth, mc.fontRenderer );
|
||||
// if( !list.isEmpty() ) chat.printChatMessageWithOptionalDeletion( list.get( 0 ), id );
|
||||
chat.addMessage(component, null, createTag(label));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(TableBuilder table) {
|
||||
var chat = Minecraft.getInstance().gui.getChat();
|
||||
|
||||
var tag = createTag(table.getId());
|
||||
if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) {
|
||||
chat.refreshTrimmedMessage();
|
||||
}
|
||||
|
||||
TableFormatter.super.display(table);
|
||||
}
|
||||
|
||||
private static GuiMessageTag createTag(String id) {
|
||||
return new GuiMessageTag(0xa0a0a0, null, null, "ComputerCraft/" + id);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.google.auto.service.AutoService;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
|
||||
|
||||
@AutoService(ComputerCraftAPIClientService.class)
|
||||
public final class ComputerCraftAPIClientImpl implements ComputerCraftAPIClientService {
|
||||
@Override
|
||||
public <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
|
||||
TurtleUpgradeModellers.register(serialiser, modeller);
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public final class FrameInfo {
|
||||
private static int tick;
|
||||
private static long renderFrame;
|
||||
|
||||
private FrameInfo() {
|
||||
}
|
||||
|
||||
public static boolean getGlobalCursorBlink() {
|
||||
return (tick / 8) % 2 == 0;
|
||||
}
|
||||
|
||||
public static long getRenderFrame() {
|
||||
return renderFrame;
|
||||
}
|
||||
|
||||
public static void onTick() {
|
||||
tick++;
|
||||
}
|
||||
|
||||
public static void onRenderTick() {
|
||||
renderFrame++;
|
||||
}
|
||||
}
|
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
|
||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.network.server.UploadFileMessage;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
|
||||
public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> extends AbstractContainerScreen<T> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractComputerScreen.class);
|
||||
|
||||
private static final Component OK = Component.translatable("gui.ok");
|
||||
private static final Component NO_RESPONSE_TITLE = Component.translatable("gui.computercraft.upload.no_response");
|
||||
private static final Component NO_RESPONSE_MSG = Component.translatable("gui.computercraft.upload.no_response.msg",
|
||||
Component.literal("import").withStyle(ChatFormatting.DARK_GRAY));
|
||||
|
||||
protected @Nullable TerminalWidget terminal;
|
||||
protected Terminal terminalData;
|
||||
protected final ComputerFamily family;
|
||||
protected final InputHandler input;
|
||||
|
||||
protected final int sidebarYOffset;
|
||||
|
||||
private long uploadNagDeadline = Long.MAX_VALUE;
|
||||
private final ItemStack displayStack;
|
||||
|
||||
public AbstractComputerScreen(T container, Inventory player, Component title, int sidebarYOffset) {
|
||||
super(container, player, title);
|
||||
terminalData = container.getTerminal();
|
||||
family = container.getFamily();
|
||||
displayStack = container.getDisplayStack();
|
||||
input = new ClientInputHandler(menu);
|
||||
this.sidebarYOffset = sidebarYOffset;
|
||||
}
|
||||
|
||||
protected abstract TerminalWidget createTerminal();
|
||||
|
||||
protected final TerminalWidget getTerminal() {
|
||||
if (terminal == null) throw new IllegalStateException("Screen has not been initialised yet");
|
||||
return terminal;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
|
||||
terminal = addRenderableWidget(createTerminal());
|
||||
ComputerSidebar.addButtons(menu::isOn, input, this::addRenderableWidget, leftPos, topPos + sidebarYOffset);
|
||||
setFocused(terminal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void containerTick() {
|
||||
super.containerTick();
|
||||
getTerminal().update();
|
||||
|
||||
if (uploadNagDeadline != Long.MAX_VALUE && Util.getNanos() >= uploadNagDeadline) {
|
||||
new ItemToast(minecraft, displayStack, NO_RESPONSE_TITLE, NO_RESPONSE_MSG, ItemToast.TRANSFER_NO_RESPONSE_TOKEN)
|
||||
.showOrReplace(minecraft.getToasts());
|
||||
uploadNagDeadline = Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed(int key, int scancode, int modifiers) {
|
||||
// Forward the tab key to the terminal, rather than moving between controls.
|
||||
if (key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal) {
|
||||
return getFocused().keyPressed(key, scancode, modifiers);
|
||||
}
|
||||
|
||||
return super.keyPressed(key, scancode, modifiers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased(double x, double y, int button) {
|
||||
// Reimplement ContainerEventHandler.mouseReleased, as it's not called in vanilla (it is in Forge, but that
|
||||
// shouldn't matter).
|
||||
setDragging(false);
|
||||
var child = getChildAt(x, y);
|
||||
if (child.isPresent() && child.get().mouseReleased(x, y, button)) return true;
|
||||
|
||||
return super.mouseReleased(x, y, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
|
||||
renderBackground(stack);
|
||||
super.render(stack, mouseX, mouseY, partialTicks);
|
||||
renderTooltip(stack, mouseX, mouseY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(double x, double y, int button) {
|
||||
var changed = super.mouseClicked(x, y, button);
|
||||
// Clicking the terminate/shutdown button steals focus, which means then pressing "enter" will click the button
|
||||
// again. Restore the focus to the terminal in these cases.
|
||||
if (getFocused() instanceof DynamicImageButton) setFocused(terminal);
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseDragged(double x, double y, int button, double deltaX, double deltaY) {
|
||||
return (getFocused() != null && getFocused().mouseDragged(x, y, button, deltaX, deltaY))
|
||||
|| super.mouseDragged(x, y, button, deltaX, deltaY);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void renderLabels(PoseStack transform, int mouseX, int mouseY) {
|
||||
// Skip rendering labels.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesDrop(List<Path> files) {
|
||||
if (files.isEmpty()) return;
|
||||
|
||||
if (!menu.isOn()) {
|
||||
alert(UploadResult.FAILED_TITLE, UploadResult.COMPUTER_OFF_MSG);
|
||||
return;
|
||||
}
|
||||
|
||||
long size = 0;
|
||||
|
||||
List<FileUpload> toUpload = new ArrayList<>();
|
||||
for (var file : files) {
|
||||
// TODO: Recurse directories? If so, we probably want to shunt this off-thread.
|
||||
if (!Files.isRegularFile(file)) continue;
|
||||
|
||||
try (var sbc = Files.newByteChannel(file)) {
|
||||
var fileSize = sbc.size();
|
||||
if (fileSize > UploadFileMessage.MAX_SIZE || (size += fileSize) >= UploadFileMessage.MAX_SIZE) {
|
||||
alert(UploadResult.FAILED_TITLE, UploadResult.TOO_MUCH_MSG);
|
||||
return;
|
||||
}
|
||||
|
||||
var name = file.getFileName().toString();
|
||||
if (name.length() > UploadFileMessage.MAX_FILE_NAME) {
|
||||
alert(UploadResult.FAILED_TITLE, Component.translatable("gui.computercraft.upload.failed.name_too_long"));
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = ByteBuffer.allocateDirect((int) fileSize);
|
||||
sbc.read(buffer);
|
||||
buffer.flip();
|
||||
|
||||
var digest = FileUpload.getDigest(buffer);
|
||||
if (digest == null) {
|
||||
alert(UploadResult.FAILED_TITLE, Component.translatable("gui.computercraft.upload.failed.corrupted"));
|
||||
return;
|
||||
}
|
||||
|
||||
toUpload.add(new FileUpload(name, buffer, digest));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed uploading files", e);
|
||||
alert(UploadResult.FAILED_TITLE, Component.translatable("gui.computercraft.upload.failed.generic", "Cannot compute checksum"));
|
||||
}
|
||||
}
|
||||
|
||||
if (toUpload.size() > UploadFileMessage.MAX_FILES) {
|
||||
alert(UploadResult.FAILED_TITLE, Component.translatable("gui.computercraft.upload.failed.too_many_files"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientPlatformHelper.get()::sendToServer);
|
||||
}
|
||||
|
||||
public void uploadResult(UploadResult result, @Nullable Component message) {
|
||||
switch (result) {
|
||||
case QUEUED -> {
|
||||
if (Config.uploadNagDelay > 0) {
|
||||
uploadNagDeadline = Util.getNanos() + TimeUnit.SECONDS.toNanos(Config.uploadNagDelay);
|
||||
}
|
||||
}
|
||||
case CONSUMED -> uploadNagDeadline = Long.MAX_VALUE;
|
||||
case ERROR -> alert(UploadResult.FAILED_TITLE, assertNonNull(message));
|
||||
}
|
||||
}
|
||||
|
||||
private void alert(Component title, Component message) {
|
||||
OptionScreen.show(minecraft, title, message,
|
||||
Collections.singletonList(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
|
||||
() -> minecraft.setScreen(this)
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
|
||||
import dan200.computercraft.shared.network.server.KeyEventServerMessage;
|
||||
import dan200.computercraft.shared.network.server.MouseEventServerMessage;
|
||||
import dan200.computercraft.shared.network.server.QueueEventServerMessage;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An {@link InputHandler} which for use on the client.
|
||||
* <p>
|
||||
* This queues events on the remote player's open {@link ComputerMenu}
|
||||
*/
|
||||
public final class ClientInputHandler implements InputHandler {
|
||||
private final AbstractContainerMenu menu;
|
||||
|
||||
public ClientInputHandler(AbstractContainerMenu menu) {
|
||||
this.menu = menu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void turnOn() {
|
||||
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reboot() {
|
||||
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueEvent(String event, @Nullable Object[] arguments) {
|
||||
ClientPlatformHelper.get().sendToServer(new QueueEventServerMessage(menu, event, arguments));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyDown(int key, boolean repeat) {
|
||||
ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyUp(int key) {
|
||||
ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClick(int button, int x, int y) {
|
||||
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseUp(int button, int x, int y) {
|
||||
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDrag(int button, int x, int y) {
|
||||
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseScroll(int direction, int x, int y) {
|
||||
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user