mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-15 14:07:38 +00:00
Compare commits
280 Commits
v1.19-1.10
...
v1.19.3-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9e6e0c8b88 | ||
![]() |
5502412181 | ||
![]() |
62e3c5f9aa | ||
![]() |
7e54a40fa9 | ||
![]() |
8ac42566ec | ||
![]() |
66b20d2bdb | ||
![]() |
81dad421d5 | ||
![]() |
3075d3cea8 | ||
![]() |
cdab8f429e | ||
![]() |
2e5cd29e12 | ||
![]() |
22cadd6730 | ||
![]() |
366052ec48 | ||
![]() |
3224e0bf8b | ||
![]() |
fb4b097a66 | ||
![]() |
67f3d91850 | ||
![]() |
1e3a930543 | ||
![]() |
b21e2f4e63 | ||
![]() |
a12b405acf | ||
![]() |
e076818b29 | ||
![]() |
1554c7b397 | ||
![]() |
6cd32a6368 | ||
![]() |
7335a892b5 | ||
![]() |
83eddc6636 | ||
![]() |
d066d175bf | ||
![]() |
9873ccfa0d | ||
![]() |
da7a50368d | ||
![]() |
8cfbfe7ceb | ||
![]() |
67244b17af | ||
![]() |
9e1de23f4a | ||
![]() |
86b60855d6 | ||
![]() |
db6b6fd173 | ||
![]() |
2014e9527e | ||
![]() |
f43b839056 | ||
![]() |
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 | ||
![]() |
1d3ecb551d | ||
![]() |
aefda6a381 | ||
![]() |
c1bf9f0b24 | ||
![]() |
629abb65e3 | ||
![]() |
11ac865877 | ||
![]() |
6b93fafc46 | ||
![]() |
1acb8441ec | ||
![]() |
4b0988768d | ||
![]() |
f528046535 | ||
![]() |
93f747fb54 | ||
![]() |
5d4c34fbac | ||
![]() |
4c5b3a6ee5 | ||
![]() |
7701b343fb | ||
![]() |
14cb97cba1 | ||
![]() |
1490ca8624 | ||
![]() |
f5b89982de | ||
![]() |
1a87175ae7 | ||
![]() |
c4184a33bc | ||
![]() |
b3702fed78 | ||
![]() |
b5056fc3b8 | ||
![]() |
38b2c944f3 | ||
![]() |
5ee5b11995 | ||
![]() |
b2d2153258 | ||
![]() |
3d6ef0cf96 | ||
![]() |
71f81e1201 | ||
![]() |
1e88d37004 | ||
![]() |
97387556fe | ||
![]() |
1f910ee2ba | ||
![]() |
a9ef874174 | ||
![]() |
a2911038c5 | ||
![]() |
158850be09 | ||
![]() |
be827a21db | ||
![]() |
562f224c01 | ||
![]() |
f45614175a | ||
![]() |
af7af615c7 | ||
![]() |
8171578e80 | ||
![]() |
f4e542b4db | ||
![]() |
3e3bc8d4b1 | ||
![]() |
b48f590b92 | ||
![]() |
6ab90dc30d | ||
![]() |
0cfdd7b5e9 | ||
![]() |
af5d816798 | ||
![]() |
57cf6084e2 | ||
![]() |
e9cde9e1bf | ||
![]() |
68da044ff2 | ||
![]() |
18d9993fa7 | ||
![]() |
0c3de1087e | ||
![]() |
ff89e5feeb | ||
![]() |
0b26ab366d | ||
![]() |
cb9731306c | ||
![]() |
5d833ac634 | ||
![]() |
9db3e6d2a0 | ||
![]() |
1e703f1b07 | ||
![]() |
b663028f42 | ||
![]() |
cee60cdb5b | ||
![]() |
695ef0542a | ||
![]() |
c0d20b72c9 | ||
![]() |
cf05ab1db1 | ||
![]() |
c49547b962 | ||
![]() |
c8e15f201c | ||
![]() |
bc79100a2f | ||
![]() |
9ed5ebb868 | ||
![]() |
a9b74dc979 | ||
![]() |
12b8a0393f | ||
![]() |
cbfd83c2ba | ||
![]() |
8564c1e54b | ||
![]() |
66dff1523b | ||
![]() |
08895cdecc | ||
![]() |
5be290a1e2 | ||
![]() |
371f931140 | ||
![]() |
da5956e943 | ||
![]() |
e7533f2353 | ||
![]() |
0b7fbcde53 | ||
![]() |
76f8dd2d14 | ||
![]() |
c3b7302108 | ||
![]() |
61ac48c99f | ||
![]() |
d22e138413 | ||
![]() |
ba64e06ca7 | ||
![]() |
db8c979a06 | ||
![]() |
9d18487dc5 | ||
![]() |
34a2e87735 | ||
![]() |
feb7681c9c | ||
![]() |
ad4a2aa68d | ||
![]() |
4228011b84 | ||
![]() |
c43d851e63 | ||
![]() |
50fe7935a3 | ||
![]() |
bd19fdf350 | ||
![]() |
c3615d9c5b | ||
![]() |
e2041f7438 | ||
![]() |
d61202e2b8 | ||
![]() |
6ce88a7dcf | ||
![]() |
5d65b3e654 | ||
![]() |
abf857f864 | ||
![]() |
ebef3117f2 | ||
![]() |
25a44bea6e | ||
![]() |
b28c1ac8e0 | ||
![]() |
69b211b4fb | ||
![]() |
48147fa61c | ||
![]() |
ba976f9a16 | ||
![]() |
969feb4a1c | ||
![]() |
4a273ae8e5 | ||
![]() |
bd5de11ad5 | ||
![]() |
5366fcb9c8 | ||
![]() |
6335e77da6 | ||
![]() |
4cfd0a2d1c | ||
![]() |
be3a960273 | ||
![]() |
f25a73b8f2 | ||
![]() |
954254e7e4 | ||
![]() |
e906f3ebc3 | ||
![]() |
56f0e0674f | ||
![]() |
4e438df9ad | ||
![]() |
51c3a9d8af | ||
![]() |
d967730085 | ||
![]() |
d6afee8deb | ||
![]() |
41bddcab9f | ||
![]() |
b8d7695392 | ||
![]() |
b7fa4102df | ||
![]() |
92c613a7a2 | ||
![]() |
d2f94f2653 | ||
![]() |
718111787c | ||
![]() |
bb0e449560 | ||
![]() |
ee495b3359 | ||
![]() |
d1e952770d | ||
![]() |
2d30208631 | ||
![]() |
03f50f9298 | ||
![]() |
8fc7820a12 | ||
![]() |
1a0e3fc2fa | ||
![]() |
6d5b13dbbc | ||
![]() |
f9f8233ef4 | ||
![]() |
a2e3d9d9bd | ||
![]() |
b7f698d6f7 | ||
![]() |
93f3cd4a53 | ||
![]() |
755f8eff93 |
@@ -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
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -9,8 +9,8 @@ body:
|
||||
description: What version of Minecraft are you using?
|
||||
options:
|
||||
- 1.16.x
|
||||
- 1.17.x
|
||||
- 1.18.x
|
||||
- 1.19.x
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ComputerCraft Discord
|
||||
url: https://discord.computercraft.cc
|
||||
about: Get help on the ComputerCraft Discord.
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
||||
about: Or ask questions on GitHub Discussions.
|
||||
about: Ask questions on GitHub Discussions.
|
||||
|
96
.github/workflows/main-ci.yml
vendored
96
.github/workflows/main-ci.yml
vendored
@@ -8,20 +8,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Java 8
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 8
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Cache gradle dependencies
|
||||
uses: actions/cache@v2
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('gradle.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
||||
|
||||
- name: Disable Gradle daemon
|
||||
run: |
|
||||
@@ -32,39 +31,68 @@ jobs:
|
||||
run: |
|
||||
./gradlew assemble || ./gradlew assemble
|
||||
./gradlew downloadAssets || ./gradlew downloadAssets
|
||||
xvfb-run ./gradlew build
|
||||
./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 Screnshots
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Screenshots
|
||||
path: test-files/client/screenshots
|
||||
if-no-files-found: ignore
|
||||
retention-days: 5
|
||||
if: failure()
|
||||
|
||||
- name: Upload Coverage
|
||||
uses: codecov/codecov-action@v2
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
- name: Parse test reports
|
||||
run: ./tools/parse-reports.py
|
||||
if: ${{ failure() }}
|
||||
|
||||
- name: Cache pre-commit
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: ${{ runner.os }}-pre-commit-${{ hashFiles('config/pre-commit/config.yml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pre-commit-
|
||||
|
||||
- name: Run linters
|
||||
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: |
|
||||
pip install pre-commit
|
||||
pre-commit run --config config/pre-commit/config.yml --show-diff-on-failure --all --color=always
|
||||
./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"
|
||||
|
30
.github/workflows/make-doc.yml
vendored
30
.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:
|
||||
@@ -11,35 +11,25 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Java 8
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Cache gradle dependencies
|
||||
uses: actions/cache@v2
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('gradle.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Setup illuaminate
|
||||
run: |
|
||||
test -d bin || mkdir bin
|
||||
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
|
||||
chmod +x bin/illuaminate
|
||||
|
||||
- name: Setup node
|
||||
run: npm ci
|
||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
||||
|
||||
- name: Build with Gradle
|
||||
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
|
||||
|
11
.gitignore
vendored
11
.gitignore
vendored
@@ -2,14 +2,17 @@
|
||||
/classes
|
||||
/logs
|
||||
/build
|
||||
/projects/*/logs
|
||||
/projects/*/build
|
||||
/buildSrc/build
|
||||
/out
|
||||
/doc/out/
|
||||
/node_modules
|
||||
.jqwik-database
|
||||
|
||||
# Runtime directories
|
||||
/run
|
||||
/run-*
|
||||
/test-files
|
||||
/projects/*/run
|
||||
|
||||
*.ipr
|
||||
*.iws
|
||||
@@ -22,8 +25,6 @@
|
||||
/.project
|
||||
/.settings
|
||||
/.vscode
|
||||
bin/
|
||||
*.launch
|
||||
|
||||
/src/generated/resources/.cache
|
||||
/src/web/mount/*.d.ts
|
||||
/projects/*/src/generated/resources/.cache
|
||||
|
@@ -17,6 +17,6 @@ vscode:
|
||||
|
||||
tasks:
|
||||
- name: Setup pre-commit hool
|
||||
init: pre-commit install --config config/pre-commit/config.yml --allow-missing-config
|
||||
init: pre-commit install --allow-missing-config
|
||||
- name: Install npm packages
|
||||
init: npm ci
|
||||
|
54
.pre-commit-config.yaml
Normal file
54
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.0.1
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-merge-conflict
|
||||
|
||||
# Quick syntax checkers
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
- id: check-toml
|
||||
- id: check-json
|
||||
exclude: "tsconfig\\.json$"
|
||||
|
||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
||||
rev: 2.3.54
|
||||
hooks:
|
||||
- id: editorconfig-checker
|
||||
args: ['-disable-indentation']
|
||||
exclude: "^(.*\\.(bat)|LICENSE)$"
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: license
|
||||
name: Spotless
|
||||
files: ".*\\.(java|kt|kts)$"
|
||||
language: system
|
||||
entry: ./gradlew spotlessApply
|
||||
pass_filenames: false
|
||||
require_serial: true
|
||||
- id: checkstyle
|
||||
name: Check Java codestyle
|
||||
files: ".*\\.java$"
|
||||
language: system
|
||||
entry: ./gradlew checkstyleMain checkstyleTest
|
||||
pass_filenames: false
|
||||
require_serial: true
|
||||
- id: illuaminate
|
||||
name: Check Lua code
|
||||
files: ".*\\.(lua|java|md)"
|
||||
language: system
|
||||
entry: ./gradlew lintLua
|
||||
pass_filenames: false
|
||||
require_serial: true
|
||||
|
||||
exclude: |
|
||||
(?x)^(
|
||||
projects/[a-z]+/src/generated|
|
||||
projects/core/src/test/resources/test-rom/data/json-parsing/|
|
||||
.*\.dfpwm
|
||||
)
|
133
CODE_OF_CONDUCT.md
Normal file
133
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,133 @@
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
"conduct AT squiddev DOT cc". All complaints will be reviewed and investigated
|
||||
promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
152
CONTRIBUTING.md
152
CONTRIBUTING.md
@@ -4,112 +4,102 @@ 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. See [the usage section][illuaminate-usage] for
|
||||
how to download and run it. You may need to generate the Java documentation stubs (see "Documentation" below) for all
|
||||
lints to pass.
|
||||
## 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.
|
||||
|
||||
Building all documentation is, sadly, a multi-stage process (though this is largely hidden by Gradle). First we need to
|
||||
convert Java doc-comments into Lua ones, we also generate some Javascript to embed. All of this is then finally fed into
|
||||
illuaminate, which spits out our HTML.
|
||||
You'll first need to [set up a development environment as above](#setting-up-a-development-environment).
|
||||
|
||||
#### Setting up the tooling
|
||||
For various reasons, getting the environment set up to build documentation can be pretty complex. I'd quite like to
|
||||
automate this via Docker and/or nix in the future, but this needs to be done manually for now.
|
||||
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.
|
||||
|
||||
This tooling is only needed if you need to build the whole website. If you just want to generate the Lua stubs, you can
|
||||
skp this section.
|
||||
- Install Node/npm and install our Node packages with `npm ci`.
|
||||
- Install [illuaminate][illuaminate-usage] as described above.
|
||||
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!
|
||||
|
||||
#### Building documentation
|
||||
Gradle should be your entrypoint to building most documentation. There's two tasks which are of interest:
|
||||
|
||||
- `./gradlew luaJavadoc` - Generate documentation stubs for Java methods.
|
||||
- `./gradlew docWebsite` - Generate the whole website (including Javascript pages). The resulting HTML is stored at
|
||||
`./build/docs/site/`.
|
||||
|
||||
#### Writing documentation
|
||||
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 (such as tables). It is very much recommended that you build and preview the docs locally first.
|
||||
|
||||
### 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 and client,
|
||||
using [the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals.
|
||||
|
||||
These are run by `./gradlew testClient` and `./gradlew testServer`. You may want to run the client under `xvfb-run`
|
||||
or similar when running in a headless environment.
|
||||
|
||||
## 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"
|
||||
[illuaminate-usage]: https://github.com/SquidDev/illuaminate/blob/master/README.md#usage "Installing Illuaminate"
|
||||
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
|
||||
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
|
||||
[ldoc]: http://stevedonovan.github.io/ldoc/ "ldoc, a Lua documentation generator."
|
||||
[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
|
||||
|
25
README.md
25
README.md
@@ -13,9 +13,8 @@ developing the mod, [check out the instructions here](CONTRIBUTING.md#developing
|
||||
|
||||
## Community
|
||||
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
|
||||
ComputerCraft we have a [forum](https://forums.computercraft.cc/) and [Discord guild](https://discord.computercraft.cc)!
|
||||
There's also a fairly populated, albeit quiet [IRC channel](http://webchat.esper.net/?channels=computercraft), if that's
|
||||
more your cup of tea.
|
||||
ComputerCraft, do check out our [forum] and [GitHub discussions page][GitHub discussions]! There's also a fairly
|
||||
populated, albeit quiet [IRC channel][irc], if that's more your cup of tea.
|
||||
|
||||
We also host fairly comprehensive documentation at [tweaked.cc](https://tweaked.cc/ "The CC: Tweaked website").
|
||||
|
||||
@@ -27,15 +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 {
|
||||
implementation 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")
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,3 +61,6 @@ the generated documentation [can be browsed online](https://tweaked.cc/javadoc/)
|
||||
[modrinth]: https://modrinth.com/mod/gu7yAYhd "Download CC: Tweaked from Modrinth"
|
||||
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
|
||||
[ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge"
|
||||
[forum]: https://forums.computercraft.cc/
|
||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
||||
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
||||
|
625
build.gradle
625
build.gradle
@@ -1,625 +0,0 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = "https://maven.minecraftforge.net" }
|
||||
maven { url = 'https://maven.parchmentmc.org' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:5.1.+'
|
||||
classpath "org.spongepowered:mixingradle:0.7.+"
|
||||
classpath 'org.parchmentmc:librarian:1.+'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "checkstyle"
|
||||
id "jacoco"
|
||||
id "maven-publish"
|
||||
id "com.github.hierynomus.license" version "0.16.1"
|
||||
id "com.matthewprenger.cursegradle" version "1.4.0"
|
||||
id "com.github.breadmoirai.github-release" version "2.2.12"
|
||||
id "org.jetbrains.kotlin.jvm" version "1.6.0"
|
||||
id "com.modrinth.minotaur" version "1.2.1"
|
||||
}
|
||||
|
||||
apply plugin: 'net.minecraftforge.gradle'
|
||||
apply plugin: "org.spongepowered.mixin"
|
||||
apply plugin: 'org.parchmentmc.librarian.forgegradle'
|
||||
|
||||
version = mod_version
|
||||
|
||||
group = "org.squiddev"
|
||||
archivesBaseName = "cc-tweaked-${mc_version}"
|
||||
|
||||
def javaVersion = JavaLanguageVersion.of(17)
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = javaVersion
|
||||
}
|
||||
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
tasks.withType(JavaExec).configureEach {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.resources {
|
||||
srcDir 'src/generated/resources'
|
||||
}
|
||||
|
||||
testMod {}
|
||||
}
|
||||
|
||||
minecraft {
|
||||
runs {
|
||||
all {
|
||||
lazyToken('minecraft_classpath') {
|
||||
configurations.shade.copyRecursive().resolve().collect { it.absolutePath }.join(File.pathSeparator)
|
||||
}
|
||||
|
||||
property 'forge.logging.markers', 'REGISTRIES'
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
|
||||
mods {
|
||||
computercraft {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
|
||||
arg "-mixin.config=computercraft.mixins.json"
|
||||
}
|
||||
|
||||
client {
|
||||
workingDirectory project.file('run')
|
||||
}
|
||||
|
||||
server {
|
||||
workingDirectory project.file("run/server")
|
||||
arg "--nogui"
|
||||
}
|
||||
|
||||
data {
|
||||
workingDirectory project.file('run')
|
||||
args '--mod', 'computercraft', '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')
|
||||
}
|
||||
|
||||
testClient {
|
||||
workingDirectory project.file('test-files/client')
|
||||
parent runs.client
|
||||
|
||||
mods {
|
||||
cctest {
|
||||
source sourceSets.testMod
|
||||
}
|
||||
}
|
||||
|
||||
lazyToken('minecraft_classpath') {
|
||||
(configurations.shade.copyRecursive().resolve() + configurations.testModExtra.copyRecursive().resolve())
|
||||
.collect { it.absolutePath }
|
||||
.join(File.pathSeparator)
|
||||
}
|
||||
}
|
||||
|
||||
gameTestServer {
|
||||
workingDirectory project.file('test-files/server')
|
||||
|
||||
mods {
|
||||
cctest {
|
||||
source sourceSets.testMod
|
||||
}
|
||||
}
|
||||
|
||||
lazyToken('minecraft_classpath') {
|
||||
(configurations.shade.copyRecursive().resolve() + configurations.testModExtra.copyRecursive().resolve())
|
||||
.collect { it.absolutePath }
|
||||
.join(File.pathSeparator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mappings channel: 'parchment', version: "${mapping_version}-${mc_version}"
|
||||
mappings channel: 'official', version: mc_version
|
||||
|
||||
accessTransformer file('src/main/resources/META-INF/accesstransformer.cfg')
|
||||
accessTransformer file('src/testMod/resources/META-INF/accesstransformer.cfg')
|
||||
}
|
||||
|
||||
mixin {
|
||||
add sourceSets.main, 'computercraft.mixins.refmap.json'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
name "SquidDev"
|
||||
url "https://squiddev.cc/maven"
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
shade
|
||||
implementation.extendsFrom shade
|
||||
|
||||
cctJavadoc
|
||||
|
||||
testModExtra
|
||||
testModImplementation.extendsFrom(testModExtra)
|
||||
testModImplementation.extendsFrom(implementation)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:8.45"
|
||||
|
||||
minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}"
|
||||
annotationProcessor 'org.spongepowered:mixin:0.8.4:processor'
|
||||
|
||||
compileOnly fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116:api")
|
||||
// runtimeOnly fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116")
|
||||
|
||||
shade 'org.squiddev:Cobalt:0.5.5'
|
||||
shade('io.netty:netty-codec-http:4.1.76.Final') {
|
||||
exclude group: "*"
|
||||
}
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
|
||||
|
||||
testModImplementation sourceSets.main.output
|
||||
testModExtra('org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0') {
|
||||
exclude group: "org.jetbrains", module: "annotations"
|
||||
}
|
||||
|
||||
cctJavadoc 'cc.tweaked:cct-javadoc:1.4.6'
|
||||
}
|
||||
|
||||
// Compile tasks
|
||||
|
||||
compileTestModJava {
|
||||
dependsOn(compileJava)
|
||||
}
|
||||
|
||||
javadoc {
|
||||
include "dan200/computercraft/api/**/*.java"
|
||||
}
|
||||
|
||||
task luaJavadoc(type: Javadoc) {
|
||||
description "Generates documentation for Java-side Lua functions."
|
||||
group "documentation"
|
||||
|
||||
source = sourceSets.main.allJava
|
||||
destinationDir = file("${project.docsDir}/luaJavadoc")
|
||||
classpath = sourceSets.main.compileClasspath
|
||||
|
||||
options.docletpath = configurations.cctJavadoc.files as List
|
||||
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
|
||||
options.noTimestamp = false
|
||||
|
||||
javadocTool = javaToolchains.javadocToolFor {
|
||||
languageVersion = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes([
|
||||
"Specification-Title" : "computercraft",
|
||||
"Specification-Vendor" : "SquidDev",
|
||||
"Specification-Version" : "1",
|
||||
"Implementation-Title" : "CC: Tweaked",
|
||||
"Implementation-Version" : "${mod_version}",
|
||||
"Implementation-Vendor" : "SquidDev",
|
||||
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
,
|
||||
"MixinConfigs" : "computercraft.mixins.json",
|
||||
])
|
||||
}
|
||||
|
||||
duplicatesStrategy(DuplicatesStrategy.WARN)
|
||||
|
||||
from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
|
||||
jar.finalizedBy('reobfJar')
|
||||
|
||||
[compileJava, compileTestJava, compileTestModJava].forEach {
|
||||
it.configure {
|
||||
options.compilerArgs << "-Xlint" << "-Xlint:-processing"
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
def hash = 'none'
|
||||
Set<String> contributors = []
|
||||
try {
|
||||
hash = ["git", "-C", projectDir, "rev-parse", "HEAD"].execute().text.trim()
|
||||
|
||||
def blacklist = ['GitHub', 'Daniel Ratcliffe', 'Weblate']
|
||||
|
||||
// Extract all authors, commiters and co-authors from the git log.
|
||||
def authors = ["git", "-C", projectDir, "log", "--format=tformat:%an <%ae>%n%cn <%ce>%n%(trailers:key=Co-authored-by,valueonly)"]
|
||||
.execute().text.readLines().unique()
|
||||
|
||||
// We now pass this through git's mailmap to de-duplicate some authors.
|
||||
def remapAuthors = ["git", "check-mailmap", "--stdin"].execute()
|
||||
remapAuthors.withWriter { stdin ->
|
||||
if (stdin !instanceof BufferedWriter) stdin = new BufferedWriter(stdin)
|
||||
|
||||
authors.forEach {
|
||||
if (it == "") return
|
||||
if (!it.endsWith(">")) it += ">" // Some commits have broken Co-Authored-By lines!
|
||||
stdin.writeLine(it)
|
||||
}
|
||||
stdin.close()
|
||||
}
|
||||
|
||||
// And finally extract out the actual name.
|
||||
def emailRegex = ~/^([^<]+) <.+>$/
|
||||
remapAuthors.text.readLines().forEach {
|
||||
def matcher = it =~ emailRegex
|
||||
matcher.find()
|
||||
def name = matcher.group(1)
|
||||
if (!blacklist.contains(name)) contributors.add(name)
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
inputs.property "commithash", hash
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
include 'data/computercraft/lua/rom/help/credits.txt'
|
||||
|
||||
expand(
|
||||
'gitcontributors': contributors.sort(false, String.CASE_INSENSITIVE_ORDER).join('\n')
|
||||
)
|
||||
}
|
||||
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
exclude 'data/computercraft/lua/rom/help/credits.txt'
|
||||
}
|
||||
}
|
||||
|
||||
sourcesJar {
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
}
|
||||
|
||||
// Web tasks
|
||||
|
||||
|
||||
import com.hierynomus.gradle.license.tasks.LicenseCheck
|
||||
import com.hierynomus.gradle.license.tasks.LicenseFormat
|
||||
import com.modrinth.minotaur.TaskModrinthUpload
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
List<String> mkCommand(String command) {
|
||||
return Os.isFamily(Os.FAMILY_WINDOWS) ? ["cmd", "/c", command] : ["sh", "-c", command]
|
||||
}
|
||||
|
||||
task rollup(type: Exec) {
|
||||
group = "build"
|
||||
description = "Bundles JS into rollup"
|
||||
|
||||
inputs.files(fileTree("src/web")).withPropertyName("sources")
|
||||
inputs.file("package-lock.json").withPropertyName("package-lock.json")
|
||||
inputs.file("tsconfig.json").withPropertyName("Typescript config")
|
||||
inputs.file("rollup.config.js").withPropertyName("Rollup config")
|
||||
outputs.file("$buildDir/rollup/index.js").withPropertyName("output")
|
||||
|
||||
commandLine mkCommand('"node_modules/.bin/rollup" --config rollup.config.js')
|
||||
}
|
||||
|
||||
task illuaminateDocs(type: Exec, dependsOn: [rollup, luaJavadoc]) {
|
||||
group = "build"
|
||||
description = "Generates docs using Illuaminate"
|
||||
|
||||
inputs.files(fileTree("doc")).withPropertyName("docs")
|
||||
inputs.files(fileTree("src/main/resources/data/computercraft/lua/rom")).withPropertyName("lua rom")
|
||||
inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
|
||||
inputs.dir("$buildDir/docs/luaJavadoc")
|
||||
inputs.file("$buildDir/rollup/index.js").withPropertyName("scripts")
|
||||
inputs.file("src/web/styles.css").withPropertyName("styles")
|
||||
outputs.dir("$buildDir/docs/lua")
|
||||
|
||||
commandLine mkCommand('"bin/illuaminate" doc-gen')
|
||||
}
|
||||
|
||||
task jsxDocs(type: Exec, dependsOn: [illuaminateDocs]) {
|
||||
group = "build"
|
||||
description = "Post-processes documentation to statically render some dynamic content."
|
||||
|
||||
inputs.files(fileTree("src/web")).withPropertyName("sources")
|
||||
inputs.file("src/generated/export/index.json").withPropertyName("export")
|
||||
inputs.file("package-lock.json").withPropertyName("package-lock.json")
|
||||
inputs.file("tsconfig.json").withPropertyName("Typescript config")
|
||||
inputs.files(fileTree("$buildDir/docs/lua"))
|
||||
outputs.dir("$buildDir/docs/site")
|
||||
|
||||
commandLine mkCommand('"node_modules/.bin/ts-node" --esm src/web/transform.tsx')
|
||||
}
|
||||
|
||||
task docWebsite(type: Copy, dependsOn: [jsxDocs]) {
|
||||
from('doc') {
|
||||
include 'logo.png'
|
||||
include 'images/**'
|
||||
}
|
||||
from("$buildDir/rollup") {
|
||||
exclude 'index.js'
|
||||
}
|
||||
from("$buildDir/docs/lua") {
|
||||
exclude '**/*.html'
|
||||
}
|
||||
from("src/generated/export/items") {
|
||||
into("images/items")
|
||||
}
|
||||
|
||||
into "${project.docsDir}/site"
|
||||
}
|
||||
|
||||
// Check tasks
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
dependsOn('test')
|
||||
reports {
|
||||
xml.required = true
|
||||
html.required = true
|
||||
}
|
||||
}
|
||||
|
||||
check.dependsOn jacocoTestReport
|
||||
|
||||
license {
|
||||
mapping("java", "SLASHSTAR_STYLE")
|
||||
strictCheck true
|
||||
|
||||
ext.year = Calendar.getInstance().get(Calendar.YEAR)
|
||||
}
|
||||
|
||||
[licenseMain, licenseFormatMain].forEach {
|
||||
it.configure {
|
||||
include("**/*.java")
|
||||
exclude("dan200/computercraft/api/**")
|
||||
header file('config/license/main.txt')
|
||||
}
|
||||
}
|
||||
|
||||
[licenseTest, licenseFormatTest, licenseTestMod, licenseFormatTestMod].forEach {
|
||||
it.configure {
|
||||
include("**/*.java")
|
||||
header file('config/license/main.txt')
|
||||
}
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(LicenseFormat) {
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
task licenseAPI(type: LicenseCheck)
|
||||
task licenseFormatAPI(type: LicenseFormat)
|
||||
[licenseAPI, licenseFormatAPI].forEach {
|
||||
it.configure {
|
||||
source = sourceSets.main.java
|
||||
include("dan200/computercraft/api/**")
|
||||
header file('config/license/api.txt')
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("testServer", JavaExec.class).configure {
|
||||
it.group('In-game tests')
|
||||
it.description("Runs tests on a temporary Minecraft instance.")
|
||||
it.dependsOn("prepareRunGameTestServer", "cleanTestServer", 'compileTestModJava')
|
||||
|
||||
// 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).
|
||||
JavaExec exec = tasks.getByName("runGameTestServer")
|
||||
exec.copyTo(it)
|
||||
it.setClasspath(exec.getClasspath())
|
||||
it.mainClass = exec.mainClass
|
||||
it.setArgs(exec.getArgs())
|
||||
|
||||
// Jacoco and modlauncher don't play well together as the classes loaded in-game don't
|
||||
// match up with those written to disk. We get Jacoco to dump all classes to disk, and
|
||||
// use that when generating the report.
|
||||
def coverageOut = new File(buildDir, "jacocoClassDump/testServer")
|
||||
jacoco.applyTo(it)
|
||||
it.jacoco.setIncludes(["dan200.computercraft.*"])
|
||||
it.jacoco.setClassDumpDir(coverageOut)
|
||||
it.outputs.dir(coverageOut)
|
||||
// Older versions of modlauncher don't include a protection domain (and thus no code
|
||||
// source). Jacoco skips such classes by default, so we need to explicitly include them.
|
||||
it.jacoco.setIncludeNoLocationClasses(true)
|
||||
}
|
||||
|
||||
tasks.register("jacocoTestServerReport", JacocoReport.class).configure {
|
||||
it.group('In-game')
|
||||
it.description("Generate coverage reports for testServer")
|
||||
it.dependsOn("testServer")
|
||||
|
||||
it.executionData(new File(buildDir, "jacoco/testServer.exec"))
|
||||
it.sourceDirectories.from(sourceSets.main.allJava.srcDirs)
|
||||
it.classDirectories.from(new File(buildDir, "jacocoClassDump/testServer"))
|
||||
|
||||
it.reports {
|
||||
xml.enabled true
|
||||
html.enabled true
|
||||
}
|
||||
}
|
||||
|
||||
check.dependsOn("jacocoTestServerReport")
|
||||
|
||||
|
||||
// Upload tasks
|
||||
|
||||
task checkRelease {
|
||||
group "upload"
|
||||
description "Verifies that everything is ready for a release"
|
||||
|
||||
inputs.property "version", mod_version
|
||||
inputs.file("src/main/resources/data/computercraft/lua/rom/help/changelog.md")
|
||||
inputs.file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
|
||||
|
||||
doLast {
|
||||
def ok = true
|
||||
|
||||
// Check we're targetting the current version
|
||||
def whatsnew = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.md").readLines()
|
||||
if (whatsnew[0] != "New features in CC: Tweaked $mod_version") {
|
||||
ok = false
|
||||
project.logger.error("Expected `whatsnew.md' to target $mod_version.")
|
||||
}
|
||||
|
||||
// Check "read more" exists and trim it
|
||||
def idx = whatsnew.findIndexOf { it == 'Type "help changelog" to see the full version history.' }
|
||||
if (idx == -1) {
|
||||
ok = false
|
||||
project.logger.error("Must mention the changelog in whatsnew.md")
|
||||
} else {
|
||||
whatsnew = whatsnew.getAt(0..<idx)
|
||||
}
|
||||
|
||||
// Check whatsnew and changelog match.
|
||||
def versionChangelog = "# " + whatsnew.join("\n")
|
||||
def changelog = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/changelog.md").getText()
|
||||
if (!changelog.startsWith(versionChangelog)) {
|
||||
ok = false
|
||||
project.logger.error("whatsnew and changelog are not in sync")
|
||||
}
|
||||
|
||||
if (!ok) throw new IllegalStateException("Could not check release")
|
||||
}
|
||||
}
|
||||
check.dependsOn checkRelease
|
||||
|
||||
def isStable = true
|
||||
|
||||
curseforge {
|
||||
apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : ''
|
||||
project {
|
||||
id = '282001'
|
||||
releaseType = isStable ? 'release' : 'alpha'
|
||||
changelog = "Release notes can be found on the GitHub repository (https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v${mc_version}-${mod_version})."
|
||||
|
||||
addGameVersion "${mc_version}"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('publishModrinth', TaskModrinthUpload.class).configure {
|
||||
dependsOn('assemble', 'reobfJar')
|
||||
onlyIf {
|
||||
project.hasProperty('modrinthApiKey')
|
||||
}
|
||||
|
||||
token = project.hasProperty('modrinthApiKey') ? project.getProperty('modrinthApiKey') : ''
|
||||
projectId = 'gu7yAYhd'
|
||||
versionNumber = "${project.mc_version}-${project.mod_version}"
|
||||
uploadFile = jar
|
||||
versionType = isStable ? 'RELEASE' : 'ALPHA'
|
||||
addGameVersion(project.mc_version)
|
||||
changelog = "Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v${mc_version}-${mod_version})."
|
||||
addLoader('forge')
|
||||
}
|
||||
|
||||
tasks.withType(GenerateModuleMetadata) {
|
||||
// We can't generate metadata as that includes Forge as a dependency.
|
||||
enabled = false
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
from components.java
|
||||
|
||||
pom {
|
||||
name = 'CC: Tweaked'
|
||||
description = 'CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.'
|
||||
url = 'https://github.com/cc-tweaked/CC-Tweaked'
|
||||
|
||||
scm {
|
||||
url = 'https://github.com/cc-tweaked/CC-Tweaked.git'
|
||||
}
|
||||
|
||||
issueManagement {
|
||||
system = 'github'
|
||||
url = 'https://github.com/cc-tweaked/CC-Tweaked/issues'
|
||||
}
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name = 'ComputerCraft Public License, Version 1.0'
|
||||
url = 'https://github.com/cc-tweaked/CC-Tweaked/blob/mc-1.15.x/LICENSE'
|
||||
}
|
||||
}
|
||||
|
||||
withXml { asNode().remove(asNode().get("dependencies")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
if (project.hasProperty("mavenUser")) {
|
||||
maven {
|
||||
name = "SquidDev"
|
||||
url = "https://squiddev.cc/maven"
|
||||
credentials {
|
||||
username = project.property("mavenUser") as String
|
||||
password = project.property("mavenPass") as String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
githubRelease {
|
||||
token project.hasProperty('githubApiKey') ? project.githubApiKey : ''
|
||||
owner 'cc-tweaked'
|
||||
repo 'CC-Tweaked'
|
||||
targetCommitish.set(project.provider({
|
||||
def cmd = ["git", "rev-parse", "--abbrev-ref", "HEAD"]
|
||||
println(cmd)
|
||||
def proc = cmd.execute([], projectDir)
|
||||
if (proc.waitFor() != 0) {
|
||||
println(proc.err.text.trim())
|
||||
throw new IllegalStateException("Executed with a non-0 exit code (${proc.exitValue()}).")
|
||||
}
|
||||
|
||||
def branch = proc.text.trim()
|
||||
if (branch == "") throw new IllegalStateException("Cannot determine branch")
|
||||
return branch
|
||||
}))
|
||||
|
||||
tagName "v${mc_version}-${mod_version}"
|
||||
releaseName "[${mc_version}] ${mod_version}"
|
||||
body.set(project.provider({
|
||||
"## " + new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
|
||||
.readLines()
|
||||
.takeWhile { it != 'Type "help changelog" to see the full version history.' }
|
||||
.join("\n").trim()
|
||||
}))
|
||||
prerelease !isStable
|
||||
}
|
||||
|
||||
def uploadTasks = ["publish", "curseforge", "publishModrinth", "githubRelease"]
|
||||
uploadTasks.forEach { tasks.getByName(it).dependsOn checkRelease }
|
||||
|
||||
task uploadAll(dependsOn: uploadTasks) {
|
||||
group "upload"
|
||||
description "Uploads to all repositories (Maven, Curse, Modrinth, GitHub release)"
|
||||
}
|
52
build.gradle.kts
Normal file
52
build.gradle.kts
Normal file
@@ -0,0 +1,52 @@
|
||||
import org.jetbrains.gradle.ext.compiler
|
||||
import org.jetbrains.gradle.ext.settings
|
||||
|
||||
plugins {
|
||||
publishing
|
||||
alias(libs.plugins.taskTree)
|
||||
alias(libs.plugins.githubRelease)
|
||||
id("org.jetbrains.gradle.plugin.idea-ext")
|
||||
id("cc-tweaked")
|
||||
}
|
||||
|
||||
val isUnstable = project.properties["isUnstable"] == "true"
|
||||
val modVersion: String by extra
|
||||
val mcVersion: String by extra
|
||||
|
||||
githubRelease {
|
||||
token(findProperty("githubApiKey") as String? ?: "")
|
||||
owner.set("cc-tweaked")
|
||||
repo.set("CC-Tweaked")
|
||||
targetCommitish.set(cct.gitBranch)
|
||||
|
||||
tagName.set("v$mcVersion-$modVersion")
|
||||
releaseName.set("[$mcVersion] $modVersion")
|
||||
body.set(
|
||||
provider {
|
||||
"## " + 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(isUnstable)
|
||||
}
|
||||
|
||||
tasks.publish { dependsOn(tasks.githubRelease) }
|
||||
|
||||
idea.project.settings.compiler.javac {
|
||||
// We want ErrorProne to be present when compiling via IntelliJ, as it offers some helpful warnings
|
||||
// and errors. Loop through our source sets and find the appropriate flags.
|
||||
moduleJavacAdditionalOptions = subprojects
|
||||
.asSequence()
|
||||
.map { evaluationDependsOn(it.path) }
|
||||
.flatMap { project ->
|
||||
val sourceSets = project.extensions.findByType(SourceSetContainer::class) ?: return@flatMap sequenceOf()
|
||||
sourceSets.asSequence().map { sourceSet ->
|
||||
val name = "${idea.project.name}.${project.name}.${sourceSet.name}"
|
||||
val compile = project.tasks.named(sourceSet.compileJavaTaskName, JavaCompile::class).get()
|
||||
name to compile.options.allCompilerArgs.joinToString(" ") { if (it.contains(" ")) "\"$it\"" else it }
|
||||
}
|
||||
}
|
||||
.toMap()
|
||||
}
|
70
buildSrc/build.gradle.kts
Normal file
70
buildSrc/build.gradle.kts
Normal file
@@ -0,0 +1,70 @@
|
||||
plugins {
|
||||
`java-gradle-plugin`
|
||||
`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 {
|
||||
plugins {
|
||||
register("cc-tweaked") {
|
||||
id = "cc-tweaked"
|
||||
implementationClass = "cc.tweaked.gradle.CCTweakedPlugin"
|
||||
}
|
||||
|
||||
register("cc-tweaked.illuaminate") {
|
||||
id = "cc-tweaked.illuaminate"
|
||||
implementationClass = "cc.tweaked.gradle.IlluaminatePlugin"
|
||||
}
|
||||
|
||||
register("cc-tweaked.node") {
|
||||
id = "cc-tweaked.node"
|
||||
implementationClass = "cc.tweaked.gradle.NodePlugin"
|
||||
}
|
||||
}
|
||||
}
|
7
buildSrc/settings.gradle.kts
Normal file
7
buildSrc/settings.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
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() }
|
||||
}
|
60
buildSrc/src/main/kotlin/cc-tweaked.gametest.gradle.kts
Normal file
60
buildSrc/src/main/kotlin/cc-tweaked.gametest.gradle.kts
Normal file
@@ -0,0 +1,60 @@
|
||||
import cc.tweaked.gradle.clientClasses
|
||||
import cc.tweaked.gradle.commonClasses
|
||||
|
||||
/**
|
||||
* Sets up the configurations for writing game tests.
|
||||
*
|
||||
* See notes in [cc.tweaked.gradle.MinecraftConfigurations] for the general design behind these cursed ideas.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("cc-tweaked.kotlin-convention")
|
||||
id("cc-tweaked.java-convention")
|
||||
}
|
||||
|
||||
val main = sourceSets["main"]
|
||||
val client = sourceSets["client"]
|
||||
|
||||
// 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 + client.compileClasspath
|
||||
runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
|
||||
}
|
||||
|
||||
configurations {
|
||||
named(testMod.compileClasspathConfigurationName) {
|
||||
shouldResolveConsistentlyWith(compileClasspath.get())
|
||||
}
|
||||
|
||||
named(testMod.runtimeClasspathConfigurationName) {
|
||||
shouldResolveConsistentlyWith(runtimeClasspath.get())
|
||||
}
|
||||
}
|
||||
|
||||
// Like the main test configurations, we're safe to depend on source set outputs.
|
||||
dependencies {
|
||||
add(testMod.implementationConfigurationName, main.output)
|
||||
add(testMod.implementationConfigurationName, client.output)
|
||||
}
|
||||
|
||||
// Similar to java-test-fixtures, but tries to avoid putting the obfuscated jar on the classpath.
|
||||
|
||||
val testFixtures by sourceSets.creating {
|
||||
compileClasspath += main.compileClasspath + client.compileClasspath
|
||||
}
|
||||
|
||||
java.registerFeature("testFixtures") {
|
||||
usingSourceSet(testFixtures)
|
||||
disablePublication()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
add(testFixtures.apiConfigurationName, libs.findBundle("test").get())
|
||||
// Consumers of this project already have the common and client classes on the classpath, so it's fine for these
|
||||
// to be compile-only.
|
||||
add(testFixtures.compileOnlyApiConfigurationName, commonClasses(project))
|
||||
add(testFixtures.compileOnlyApiConfigurationName, clientClasses(project))
|
||||
|
||||
testImplementation(testFixtures(project))
|
||||
}
|
198
buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts
Normal file
198
buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts
Normal file
@@ -0,0 +1,198 @@
|
||||
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()
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://squiddev.cc/maven") {
|
||||
name = "SquidDev"
|
||||
content {
|
||||
includeGroup("org.squiddev")
|
||||
includeGroup("cc.tweaked")
|
||||
// Things we mirror
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
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")
|
||||
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("skipped", "failed")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JacocoReport::class.java).configureEach {
|
||||
reports.xml.required.set(true)
|
||||
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
|
||||
|
||||
fun FormatExtension.defaults() {
|
||||
endWithNewline()
|
||||
trimTrailingWhitespace()
|
||||
indentWithSpaces(4)
|
||||
}
|
||||
|
||||
val licenser = LicenseHeader.create(
|
||||
api = rootProject.file("config/license/api.txt"),
|
||||
main = rootProject.file("config/license/main.txt"),
|
||||
)
|
||||
|
||||
java {
|
||||
defaults()
|
||||
addStep(licenser)
|
||||
removeUnusedImports()
|
||||
}
|
||||
|
||||
val ktlintConfig = mapOf(
|
||||
"disabled_rules" to "no-wildcard-imports",
|
||||
"ij_kotlin_allow_trailing_comma" to "true",
|
||||
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
|
||||
)
|
||||
|
||||
kotlinGradle {
|
||||
defaults()
|
||||
ktlint().editorConfigOverride(ktlintConfig)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
defaults()
|
||||
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
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
import cc.tweaked.gradle.CCTweakedPlugin
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain {
|
||||
languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(KotlinCompile::class.java).configureEach {
|
||||
// So technically we shouldn't need to do this as the toolchain sets it above. However, the option only appears
|
||||
// to be set when the task executes, so doesn't get picked up by IDEs.
|
||||
kotlinOptions.jvmTarget = when {
|
||||
CCTweakedPlugin.JAVA_VERSION.asInt() > 8 -> CCTweakedPlugin.JAVA_VERSION.toString()
|
||||
else -> "1.${CCTweakedPlugin.JAVA_VERSION.asInt()}"
|
||||
}
|
||||
}
|
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)
|
||||
}
|
268
buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt
Normal file
268
buildSrc/src/main/kotlin/cc/tweaked/gradle/CCTweakedExtension.kt
Normal file
@@ -0,0 +1,268 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import net.ltgt.gradle.errorprone.CheckSeverity
|
||||
import net.ltgt.gradle.errorprone.errorprone
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.NamedDomainObjectProvider
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.attributes.TestSuiteType
|
||||
import org.gradle.api.file.FileSystemOperations
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.reporting.ReportingExtension
|
||||
import org.gradle.api.tasks.SourceSet
|
||||
import org.gradle.api.tasks.bundling.Jar
|
||||
import org.gradle.api.tasks.compile.JavaCompile
|
||||
import org.gradle.api.tasks.javadoc.Javadoc
|
||||
import org.gradle.configurationcache.extensions.capitalized
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
||||
import org.gradle.language.jvm.tasks.ProcessResources
|
||||
import org.gradle.process.JavaForkOptions
|
||||
import org.gradle.testing.jacoco.plugins.JacocoCoverageReport
|
||||
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
|
||||
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
|
||||
import org.gradle.testing.jacoco.tasks.JacocoReport
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
import java.util.regex.Pattern
|
||||
|
||||
abstract class CCTweakedExtension(
|
||||
private val project: Project,
|
||||
private val fs: FileSystemOperations,
|
||||
) {
|
||||
/** Get the hash of the latest git commit. */
|
||||
val gitHash: Provider<String> = gitProvider(project, "<no git hash>") {
|
||||
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "HEAD").trim()
|
||||
}
|
||||
|
||||
/** Get the current git branch. */
|
||||
val gitBranch: Provider<String> = gitProvider(project, "<no git branch>") {
|
||||
ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
.trim()
|
||||
}
|
||||
|
||||
/** Get a list of all contributors to the project. */
|
||||
val gitContributors: Provider<List<String>> = gitProvider(project, listOf()) {
|
||||
ProcessHelpers.captureLines(
|
||||
"git", "-C", project.rootProject.projectDir.absolutePath, "shortlog", "-ns",
|
||||
"--group=author", "--group=trailer:co-authored-by", "HEAD",
|
||||
)
|
||||
.asSequence()
|
||||
.map {
|
||||
val matcher = COMMIT_COUNTS.matcher(it)
|
||||
matcher.find()
|
||||
matcher.group(1)
|
||||
}
|
||||
.filter { !IGNORED_USERS.contains(it) }
|
||||
.toList()
|
||||
.sortedWith(String.CASE_INSENSITIVE_ORDER)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"
|
||||
|
||||
val jacoco = project.extensions.getByType(JacocoPluginExtension::class.java)
|
||||
task.configure {
|
||||
finalizedBy(reportTaskName)
|
||||
|
||||
doFirst("Clean class dump directory") { fs.delete { delete(classDump) } }
|
||||
|
||||
jacoco.applyTo(this)
|
||||
extensions.configure(JacocoTaskExtension::class.java) {
|
||||
includes = listOf("dan200.computercraft.*")
|
||||
classDumpDir = classDump
|
||||
|
||||
// Older versions of modlauncher don't include a protection domain (and thus no code
|
||||
// source). Jacoco skips such classes by default, so we need to explicitly include them.
|
||||
isIncludeNoLocationClasses = true
|
||||
}
|
||||
}
|
||||
|
||||
project.tasks.register(reportTaskName, JacocoReport::class.java) {
|
||||
group = LifecycleBasePlugin.VERIFICATION_GROUP
|
||||
description = "Generates code coverage report for the ${task.name} task."
|
||||
|
||||
executionData(task.get())
|
||||
classDirectories.from(classDump)
|
||||
|
||||
// Don't want to use sourceSets(...) here as we have a custom class directory.
|
||||
for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
|
||||
}
|
||||
|
||||
project.extensions.configure(ReportingExtension::class.java) {
|
||||
reports.register("${task.name}CodeCoverageReport", JacocoCoverageReport::class.java) {
|
||||
testType.set(TestSuiteType.INTEGRATION_TEST)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file by creating a dummy Ivy repository.
|
||||
*
|
||||
* This should only be used for one-off downloads. Using a more conventional Ivy or Maven repository is preferred
|
||||
* where possible.
|
||||
*/
|
||||
fun downloadFile(label: String, url: String): File {
|
||||
val url = URL(url)
|
||||
val path = File(url.path)
|
||||
|
||||
project.repositories.ivy {
|
||||
name = label
|
||||
setUrl(URI(url.protocol, url.userInfo, url.host, url.port, path.parent, null, null))
|
||||
patternLayout {
|
||||
artifact("[artifact].[ext]")
|
||||
}
|
||||
metadataSources {
|
||||
artifact()
|
||||
}
|
||||
content {
|
||||
includeModule("cc.tweaked.internal", path.nameWithoutExtension)
|
||||
}
|
||||
}
|
||||
|
||||
return project.configurations.detachedConfiguration(
|
||||
project.dependencies.create(
|
||||
mapOf(
|
||||
"group" to "cc.tweaked.internal",
|
||||
"name" to path.nameWithoutExtension,
|
||||
"ext" to path.extension,
|
||||
),
|
||||
),
|
||||
).resolve().single()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
|
||||
private val IGNORED_USERS = setOf(
|
||||
"GitHub", "Daniel Ratcliffe", "Weblate",
|
||||
)
|
||||
|
||||
private fun <T> gitProvider(project: Project, default: T, supplier: () -> T): Provider<T> {
|
||||
return project.provider {
|
||||
try {
|
||||
supplier()
|
||||
} 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"))
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
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
|
||||
|
||||
/**
|
||||
* Configures projects to match a shared configuration.
|
||||
*/
|
||||
class CCTweakedPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
val cct = project.extensions.create("cct", CCTweakedExtension::class.java)
|
||||
|
||||
project.plugins.withType(JavaPlugin::class.java) {
|
||||
val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
|
||||
cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main")))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val JAVA_VERSION = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
64
buildSrc/src/main/kotlin/cc/tweaked/gradle/CheckChangelog.kt
Normal file
64
buildSrc/src/main/kotlin/cc/tweaked/gradle/CheckChangelog.kt
Normal file
@@ -0,0 +1,64 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
||||
|
||||
/**
|
||||
* Checks the `changelog.md` and `whatsnew.md` files are well-formed.
|
||||
*/
|
||||
@CacheableTask
|
||||
abstract class CheckChangelog : DefaultTask() {
|
||||
init {
|
||||
group = LifecycleBasePlugin.VERIFICATION_GROUP
|
||||
description = "Verifies the changelog and whatsnew file are consistent."
|
||||
}
|
||||
|
||||
@get:Input
|
||||
abstract val version: Property<String>
|
||||
|
||||
@get:InputFile
|
||||
@get:PathSensitive(PathSensitivity.NONE)
|
||||
abstract val changelog: RegularFileProperty
|
||||
|
||||
@get:InputFile
|
||||
@get:PathSensitive(PathSensitivity.NONE)
|
||||
abstract val whatsNew: RegularFileProperty
|
||||
|
||||
@TaskAction
|
||||
fun check() {
|
||||
val version = version.get()
|
||||
|
||||
var ok = true
|
||||
|
||||
// Check we're targeting the current version
|
||||
var whatsNew = whatsNew.get().asFile.readLines()
|
||||
if (whatsNew[0] != "New features in CC: Tweaked $version") {
|
||||
ok = false
|
||||
logger.error("Expected `whatsnew.md' to target $version.")
|
||||
}
|
||||
|
||||
// Check "read more" exists and trim it
|
||||
val idx = whatsNew.indexOfFirst { it == "Type \"help changelog\" to see the full version history." }
|
||||
if (idx == -1) {
|
||||
ok = false
|
||||
logger.error("Must mention the changelog in whatsnew.md")
|
||||
} else {
|
||||
whatsNew = whatsNew.slice(0 until idx)
|
||||
}
|
||||
|
||||
// Check whatsnew and changelog match.
|
||||
val expectedChangelog = sequenceOf("# ${whatsNew[0]}") + whatsNew.slice(1 until whatsNew.size).asSequence()
|
||||
val changelog = changelog.get().asFile.readLines()
|
||||
val mismatch = expectedChangelog.zip(changelog.asSequence()).filter { (a, b) -> a != b }.firstOrNull()
|
||||
if (mismatch != null) {
|
||||
ok = false
|
||||
logger.error("whatsnew and changelog are not in sync")
|
||||
}
|
||||
|
||||
if (!ok) throw GradleException("Could not check release")
|
||||
}
|
||||
}
|
72
buildSrc/src/main/kotlin/cc/tweaked/gradle/CheckLicense.kt
Normal file
72
buildSrc/src/main/kotlin/cc/tweaked/gradle/CheckLicense.kt
Normal file
@@ -0,0 +1,72 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import com.diffplug.spotless.FormatterFunc
|
||||
import com.diffplug.spotless.FormatterStep
|
||||
import com.diffplug.spotless.generic.LicenseHeaderStep
|
||||
import java.io.File
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Similar to [LicenseHeaderStep], but supports multiple licenses.
|
||||
*/
|
||||
object LicenseHeader {
|
||||
/**
|
||||
* The current year to use in templates. Intentionally not dynamic to avoid failing the build.
|
||||
*/
|
||||
private const val YEAR = 2022
|
||||
|
||||
private val COMMENT = Regex("""^/\*(.*?)\*/\n?""", RegexOption.DOT_MATCHES_ALL)
|
||||
|
||||
fun create(api: File, main: File): FormatterStep = FormatterStep.createLazy(
|
||||
"license",
|
||||
{ Licenses(getTemplateText(api), getTemplateText(main)) },
|
||||
{ state -> FormatterFunc.NeedsFile { contents, file -> formatFile(state, contents, file) } },
|
||||
)
|
||||
|
||||
private fun getTemplateText(file: File): String =
|
||||
file.readText().trim().replace("\${year}", "$YEAR")
|
||||
|
||||
private fun formatFile(licenses: Licenses, contents: String, file: File): String {
|
||||
val license = getLicense(contents)
|
||||
val expectedLicense = getExpectedLicense(licenses, file.parentFile)
|
||||
|
||||
return when {
|
||||
license == null -> setLicense(expectedLicense, contents)
|
||||
license.second != expectedLicense -> setLicense(expectedLicense, contents, license.first)
|
||||
else -> contents
|
||||
}
|
||||
}
|
||||
|
||||
private fun getExpectedLicense(licenses: Licenses, root: File): String {
|
||||
var file: File? = root
|
||||
while (file != null) {
|
||||
if (file.name == "api" && file.parentFile?.name == "computercraft") return licenses.api
|
||||
file = file.parentFile
|
||||
}
|
||||
return licenses.main
|
||||
}
|
||||
|
||||
private fun getLicense(contents: String): Pair<Int, String>? {
|
||||
val match = COMMENT.find(contents) ?: return null
|
||||
val license = match.groups[1]!!.value
|
||||
.trim().lineSequence()
|
||||
.map { it.trimStart(' ', '*') }
|
||||
.joinToString("\n")
|
||||
return Pair(match.range.last + 1, license)
|
||||
}
|
||||
|
||||
private fun setLicense(license: String, contents: String, start: Int = 0): String {
|
||||
val out = StringBuilder()
|
||||
out.append("/*\n")
|
||||
for (line in license.lineSequence()) out.append(" * ").append(line).append("\n")
|
||||
out.append(" */\n")
|
||||
out.append(contents, start, contents.length)
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
private data class Licenses(val api: String, val main: String) : Serializable {
|
||||
companion object {
|
||||
private const val serialVersionUID: Long = 7741106448372435662L
|
||||
}
|
||||
}
|
||||
}
|
11
buildSrc/src/main/kotlin/cc/tweaked/gradle/ExecTasks.kt
Normal file
11
buildSrc/src/main/kotlin/cc/tweaked/gradle/ExecTasks.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.AbstractExecTask
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import java.io.File
|
||||
|
||||
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
|
||||
@get:OutputDirectory
|
||||
abstract val output: Property<File>
|
||||
}
|
117
buildSrc/src/main/kotlin/cc/tweaked/gradle/Extensions.kt
Normal file
117
buildSrc/src/main/kotlin/cc/tweaked/gradle/Extensions.kt
Normal file
@@ -0,0 +1,117 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
import org.gradle.api.tasks.JavaExec
|
||||
import org.gradle.process.BaseExecSpec
|
||||
import org.gradle.process.JavaExecSpec
|
||||
import org.gradle.process.ProcessForkOptions
|
||||
|
||||
/**
|
||||
* Add an annotation processor to all source sets.
|
||||
*/
|
||||
fun DependencyHandler.annotationProcessorEverywhere(dep: Any) {
|
||||
add("compileOnly", dep)
|
||||
add("annotationProcessor", dep)
|
||||
|
||||
add("clientCompileOnly", dep)
|
||||
add("clientAnnotationProcessor", dep)
|
||||
|
||||
add("testCompileOnly", dep)
|
||||
add("testAnnotationProcessor", dep)
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of [JavaExecSpec.copyTo] which copies *all* properties.
|
||||
*/
|
||||
fun JavaExec.copyToFull(spec: JavaExec) {
|
||||
copyTo(spec)
|
||||
|
||||
// Additional Java options
|
||||
spec.jvmArgs = jvmArgs // Fabric overrides getJvmArgs so copyTo doesn't do the right thing.
|
||||
spec.args = args
|
||||
spec.argumentProviders.addAll(argumentProviders)
|
||||
spec.mainClass.set(mainClass)
|
||||
spec.classpath = classpath
|
||||
spec.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",
|
||||
)
|
||||
}
|
||||
}
|
123
buildSrc/src/main/kotlin/cc/tweaked/gradle/Illuaminate.kt
Normal file
123
buildSrc/src/main/kotlin/cc/tweaked/gradle/Illuaminate.kt
Normal file
@@ -0,0 +1,123 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.AbstractExecTask
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
|
||||
abstract class IlluaminateExtension {
|
||||
/** The version of illuaminate to use. */
|
||||
abstract val version: Property<String>
|
||||
|
||||
/** The path to illuaminate. If not given, illuaminate will be downloaded automatically. */
|
||||
abstract val file: Property<File>
|
||||
}
|
||||
|
||||
class IlluaminatePlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
val extension = project.extensions.create("illuaminate", IlluaminateExtension::class.java)
|
||||
extension.file.convention(setupDependency(project, extension.version))
|
||||
|
||||
project.tasks.register(SetupIlluaminate.NAME, SetupIlluaminate::class.java) {
|
||||
file.set(extension.file.map { it.absolutePath })
|
||||
}
|
||||
}
|
||||
|
||||
/** Set up a repository for illuaminate and download our binary from it. */
|
||||
private fun setupDependency(project: Project, version: Provider<String>): Provider<File> {
|
||||
project.repositories.ivy {
|
||||
name = "Illuaminate"
|
||||
setUrl("https://squiddev.cc/illuaminate/bin/")
|
||||
patternLayout {
|
||||
artifact("[revision]/[artifact]-[ext]")
|
||||
}
|
||||
metadataSources {
|
||||
artifact()
|
||||
}
|
||||
content {
|
||||
includeModule("cc.squiddev", "illuaminate")
|
||||
}
|
||||
}
|
||||
|
||||
return version.map {
|
||||
val dep = illuaminateArtifact(project, it)
|
||||
val configuration = project.configurations.detachedConfiguration(dep)
|
||||
configuration.isTransitive = false
|
||||
configuration.resolve().single()
|
||||
}
|
||||
}
|
||||
|
||||
/** Define a dependency for illuaminate from a version number and the current operating system. */
|
||||
private fun illuaminateArtifact(project: Project, version: String): Dependency {
|
||||
val osName = System.getProperty("os.name").toLowerCase()
|
||||
val (os, suffix) = when {
|
||||
osName.contains("windows") -> Pair("windows", ".exe")
|
||||
osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "")
|
||||
osName.contains("linux") -> Pair("linux", "")
|
||||
else -> error("Unsupported OS $osName for illuaminate")
|
||||
}
|
||||
|
||||
val osArch = System.getProperty("os.arch").toLowerCase()
|
||||
val arch = when {
|
||||
// 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")
|
||||
}
|
||||
|
||||
return project.dependencies.create(
|
||||
mapOf(
|
||||
"group" to "cc.squiddev",
|
||||
"name" to "illuaminate",
|
||||
"version" to version,
|
||||
"ext" to "$os-$arch$suffix",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val Task.illuaminatePath: String? // "?" needed to avoid overload ambiguity in setExecutable below.
|
||||
get() = project.extensions.getByType(IlluaminateExtension::class.java).file.get().absolutePath
|
||||
|
||||
/** Prepares illuaminate for being run. This simply requests the dependency and then marks it as executable. */
|
||||
abstract class SetupIlluaminate : DefaultTask() {
|
||||
@get:Input
|
||||
abstract val file: Property<String>
|
||||
|
||||
@TaskAction
|
||||
fun setExecutable() {
|
||||
val file = File(this.file.get())
|
||||
if (file.canExecute()) {
|
||||
didWork = false
|
||||
return
|
||||
}
|
||||
|
||||
file.setExecutable(true)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NAME: String = "setupIlluaminate"
|
||||
}
|
||||
}
|
||||
|
||||
abstract class IlluaminateExec : AbstractExecTask<IlluaminateExec>(IlluaminateExec::class.java) {
|
||||
init {
|
||||
dependsOn(SetupIlluaminate.NAME)
|
||||
executable = illuaminatePath
|
||||
}
|
||||
}
|
||||
|
||||
abstract class IlluaminateExecToDir : ExecToDir() {
|
||||
init {
|
||||
dependsOn(SetupIlluaminate.NAME)
|
||||
executable = illuaminatePath
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
60
buildSrc/src/main/kotlin/cc/tweaked/gradle/Node.kt
Normal file
60
buildSrc/src/main/kotlin/cc/tweaked/gradle/Node.kt
Normal file
@@ -0,0 +1,60 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.Directory
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import java.io.File
|
||||
|
||||
class NodePlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) {
|
||||
val extension = project.extensions.create("node", NodeExtension::class.java)
|
||||
project.tasks.register(NpmInstall.TASK_NAME, NpmInstall::class.java) {
|
||||
projectRoot.convention(extension.projectRoot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class NodeExtension(project: Project) {
|
||||
/** The directory containing `package-lock.json` and `node_modules/`. */
|
||||
abstract val projectRoot: DirectoryProperty
|
||||
|
||||
init {
|
||||
projectRoot.convention(project.layout.projectDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
/** Installs node modules as dependencies. */
|
||||
abstract class NpmInstall : DefaultTask() {
|
||||
@get:Internal
|
||||
abstract val projectRoot: DirectoryProperty
|
||||
|
||||
@get:InputFile
|
||||
@get:PathSensitive(PathSensitivity.NONE)
|
||||
val packageLock: Provider<File> = projectRoot.file("package-lock.json").map { it.asFile }
|
||||
|
||||
@get:OutputDirectory
|
||||
val nodeModules: Provider<Directory> = projectRoot.dir("node_modules")
|
||||
|
||||
@TaskAction
|
||||
fun install() {
|
||||
project.exec {
|
||||
commandLine("npm", "ci")
|
||||
workingDir = projectRoot.get().asFile
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal const val TASK_NAME: String = "npmInstall"
|
||||
}
|
||||
}
|
||||
|
||||
abstract class NpxExecToDir : ExecToDir() {
|
||||
init {
|
||||
dependsOn(NpmInstall.TASK_NAME)
|
||||
executable = "npx"
|
||||
}
|
||||
}
|
50
buildSrc/src/main/kotlin/cc/tweaked/gradle/ProcessHelpers.kt
Normal file
50
buildSrc/src/main/kotlin/cc/tweaked/gradle/ProcessHelpers.kt
Normal file
@@ -0,0 +1,50 @@
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import org.codehaus.groovy.runtime.ProcessGroovyMethods
|
||||
import org.gradle.api.GradleException
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.InputStreamReader
|
||||
|
||||
internal object ProcessHelpers {
|
||||
fun startProcess(vararg command: String): Process {
|
||||
// Something randomly passes in "GIT_DIR=" as an environment variable which clobbers everything else. Don't
|
||||
// inherit the environment array!
|
||||
return ProcessBuilder()
|
||||
.command(*command)
|
||||
.redirectError(ProcessBuilder.Redirect.INHERIT)
|
||||
.also { it.environment().clear() }
|
||||
.start()
|
||||
}
|
||||
|
||||
fun captureOut(vararg command: String): String {
|
||||
val process = startProcess(*command)
|
||||
process.outputStream.close()
|
||||
|
||||
val result = ProcessGroovyMethods.getText(process)
|
||||
process.waitForOrThrow("Failed to run command")
|
||||
return result
|
||||
}
|
||||
|
||||
fun captureLines(vararg command: String): List<String> {
|
||||
val process = startProcess(*command)
|
||||
process.outputStream.close()
|
||||
|
||||
val out = BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
|
||||
reader.lines().filter { it.isNotEmpty() }.toList()
|
||||
}
|
||||
ProcessGroovyMethods.closeStreams(process)
|
||||
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" />
|
||||
@@ -78,7 +75,9 @@
|
||||
|
||||
<!-- 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"/>
|
||||
@@ -107,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" />
|
||||
|
||||
@@ -132,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" />
|
||||
|
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>
|
@@ -1,56 +0,0 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.0.1
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-merge-conflict
|
||||
|
||||
# Quick syntax checkers
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
- id: check-toml
|
||||
- id: check-json
|
||||
exclude: "tsconfig\\.json$"
|
||||
|
||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
||||
rev: 2.3.54
|
||||
hooks:
|
||||
- id: editorconfig-checker
|
||||
args: ['-disable-indentation']
|
||||
exclude: "^(.*\\.(bat)|LICENSE)$"
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: checkstyle
|
||||
name: Check Java codestyle
|
||||
files: ".*\\.java$"
|
||||
language: system
|
||||
entry: ./gradlew checkstyleMain checkstyleTest
|
||||
pass_filenames: false
|
||||
require_serial: true
|
||||
- id: license
|
||||
name: Check Java license headers
|
||||
files: ".*\\.java$"
|
||||
language: system
|
||||
entry: ./gradlew licenseFormat
|
||||
pass_filenames: false
|
||||
require_serial: true
|
||||
- id: illuaminate
|
||||
name: Check Lua code
|
||||
files: ".*\\.(lua|java|md)"
|
||||
language: script
|
||||
entry: config/pre-commit/illuaminate-lint.sh
|
||||
pass_filenames: false
|
||||
require_serial: true
|
||||
|
||||
exclude: |
|
||||
(?x)^(
|
||||
src/generated|
|
||||
src/test/resources/test-rom/data/json-parsing/|
|
||||
src/testMod/server-files/|
|
||||
config/idea/|
|
||||
.*\.dfpwm
|
||||
)
|
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
test -d bin || mkdir bin
|
||||
test -f bin/illuaminate || curl -s -obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
|
||||
chmod +x bin/illuaminate
|
||||
|
||||
if [ -n ${GITHUB_ACTIONS+x} ]; then
|
||||
# Register a problem matcher (see https://github.com/actions/toolkit/blob/master/docs/problem-matchers.md)
|
||||
# for illuaminate.
|
||||
echo "::add-matcher::.github/matchers/illuaminate.json"
|
||||
trap 'echo "::remove-matcher owner=illuaminate::"' EXIT
|
||||
fi
|
||||
|
||||
./gradlew luaJavadoc
|
||||
bin/illuaminate lint
|
@@ -3,19 +3,20 @@ module: [kind=event] alarm
|
||||
see: os.setAlarm To start an alarm.
|
||||
---
|
||||
|
||||
The @{timer} event is fired when an alarm started with @{os.setAlarm} completes.
|
||||
The @{alarm} event is fired when an alarm started with @{os.setAlarm} completes.
|
||||
|
||||
## Return Values
|
||||
1. @{string}: The event name.
|
||||
2. @{number}: The ID of the alarm that finished.
|
||||
|
||||
## Example
|
||||
Starts a timer and then prints its ID:
|
||||
Starts a timer and then waits for it to complete.
|
||||
|
||||
```lua
|
||||
local alarmID = os.setAlarm(os.time() + 0.05)
|
||||
local alarm_id = os.setAlarm(os.time() + 0.05)
|
||||
local event, id
|
||||
repeat
|
||||
event, id = os.pullEvent("alarm")
|
||||
until id == alarmID
|
||||
until id == alarm_id
|
||||
print("Alarm with ID " .. id .. " was fired")
|
||||
```
|
||||
|
@@ -3,7 +3,7 @@ module: [kind=event] char
|
||||
see: key To listen to any key press.
|
||||
---
|
||||
|
||||
The @{char} event is fired when a character is _typed_ on the keyboard.
|
||||
The @{char} event is fired when a character is typed on the keyboard.
|
||||
|
||||
The @{char} event is different to a key press. Sometimes multiple key presses may result in one character being
|
||||
typed (for instance, on some European keyboards). Similarly, some keys (e.g. <kbd>Ctrl</kbd>) do not have any
|
||||
@@ -16,9 +16,10 @@ corresponding character. The @{key} should be used if you want to listen to key
|
||||
|
||||
## Example
|
||||
Prints each character the user presses:
|
||||
|
||||
```lua
|
||||
while true do
|
||||
local event, character = os.pullEvent("char")
|
||||
print(character .. " was pressed.")
|
||||
local event, character = os.pullEvent("char")
|
||||
print(character .. " was pressed.")
|
||||
end
|
||||
```
|
||||
|
@@ -6,7 +6,7 @@ The @{computer_command} event is fired when the `/computercraft queue` command i
|
||||
|
||||
## Return Values
|
||||
1. @{string}: The event name.
|
||||
... @{string}: The arguments passed to the command.
|
||||
2. @{string}<abbr title="Variable number of arguments">…</abbr>: The arguments passed to the command.
|
||||
|
||||
## Example
|
||||
Prints the contents of messages sent:
|
||||
|
42
doc/events/file_transfer.md
Normal file
42
doc/events/file_transfer.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
module: [kind=event] file_transfer
|
||||
since: 1.101.0
|
||||
---
|
||||
|
||||
The @{file_transfer} event is queued when a user drags-and-drops a file on an open computer.
|
||||
|
||||
This event contains a single argument of type @{TransferredFiles}, which can be used to @{TransferredFiles.getFiles|get
|
||||
the files to be transferred}. Each file returned is a @{fs.BinaryReadHandle|binary file handle} with an additional
|
||||
@{TransferredFile.getName|getName} method.
|
||||
|
||||
## Return values
|
||||
1. @{string}: The event name
|
||||
2. @{TransferredFiles}: The list of transferred files.
|
||||
|
||||
## Example
|
||||
Waits for a user to drop files on top of the computer, then prints the list of files and the size of each file.
|
||||
|
||||
```lua
|
||||
local _, files = os.pullEvent("file_transfer")
|
||||
for _, file in ipairs(files.getFiles()) do
|
||||
-- Seek to the end of the file to get its size, then go back to the beginning.
|
||||
local size = file.seek("end")
|
||||
file.seek("set", 0)
|
||||
|
||||
print(file.getName() .. " " .. file.getSize())
|
||||
end
|
||||
```
|
||||
|
||||
## Example
|
||||
Save each transferred file to the computer's storage.
|
||||
|
||||
```lua
|
||||
local _, files = os.pullEvent("file_transfer")
|
||||
for _, file in ipairs(files.getFiles()) do
|
||||
local handle = fs.open(file.getName(), "wb")
|
||||
handle.write(file.readAll())
|
||||
|
||||
handle.close()
|
||||
file.close()
|
||||
end
|
||||
```
|
@@ -11,4 +11,4 @@ This event is normally handled inside @{http.checkURL}, but it can still be seen
|
||||
1. @{string}: The event name.
|
||||
2. @{string}: The URL requested to be checked.
|
||||
3. @{boolean}: Whether the check succeeded.
|
||||
4. @{string|nil}: If the check failed, a reason explaining why the check failed.
|
||||
4. <span class="type">@{string}|@{nil}</span>: If the check failed, a reason explaining why the check failed.
|
||||
|
@@ -11,7 +11,8 @@ This event is normally handled inside @{http.get} and @{http.post}, but it can s
|
||||
1. @{string}: The event name.
|
||||
2. @{string}: The URL of the site requested.
|
||||
3. @{string}: An error describing the failure.
|
||||
4. @{http.Response|nil}: A response handle if the connection succeeded, but the server's response indicated failure.
|
||||
4. <span class="type">@{http.Response}|@{nil}</span>: A response handle if the connection succeeded, but the server's
|
||||
response indicated failure.
|
||||
|
||||
## Example
|
||||
Prints an error why the website cannot be contacted:
|
||||
|
@@ -10,7 +10,7 @@ This event is normally handled inside @{http.get} and @{http.post}, but it can s
|
||||
## Return Values
|
||||
1. @{string}: The event name.
|
||||
2. @{string}: The URL of the site requested.
|
||||
3. @{http.Response}: The handle for the response text.
|
||||
3. @{http.Response}: The successful HTTP response.
|
||||
|
||||
## Example
|
||||
Prints the content of a website (this may fail if the request fails):
|
||||
|
@@ -10,7 +10,7 @@ The @{modem_message} event is fired when a message is received on an open channe
|
||||
3. @{number}: The channel that the message was sent on.
|
||||
4. @{number}: The reply channel set by the sender.
|
||||
5. @{any}: The message as sent by the sender.
|
||||
6. @{number}: The distance between the sender and the receiver, in blocks.
|
||||
6. <span class="type">@{number}|@{nil}</span>: The distance between the sender and the receiver in blocks, or @{nil} if the message was sent between dimensions.
|
||||
|
||||
## Example
|
||||
Wraps a @{modem} peripheral, opens channel 0 for listening, and prints all received messages.
|
||||
@@ -20,7 +20,9 @@ local modem = peripheral.find("modem") or error("No modem attached", 0)
|
||||
modem.open(0)
|
||||
|
||||
while true do
|
||||
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
|
||||
print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(side, channel, replyChannel, distance, tostring(message)))
|
||||
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
|
||||
print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(
|
||||
side, channel, replyChannel, distance, tostring(message)
|
||||
))
|
||||
end
|
||||
```
|
||||
|
@@ -6,10 +6,11 @@ The @{monitor_resize} event is fired when an adjacent or networked monitor's siz
|
||||
|
||||
## Return Values
|
||||
1. @{string}: The event name.
|
||||
2. @{string}: The side or network ID of the monitor that resized.
|
||||
2. @{string}: The side or network ID of the monitor that was resized.
|
||||
|
||||
## Example
|
||||
Prints a message when a monitor is resized:
|
||||
|
||||
```lua
|
||||
while true do
|
||||
local event, side = os.pullEvent("monitor_resize")
|
||||
|
@@ -14,7 +14,7 @@ This event is usually handled by @{rednet.receive}, but it can also be pulled ma
|
||||
1. @{string}: The event name.
|
||||
2. @{number}: The ID of the sending computer.
|
||||
3. @{any}: The message sent.
|
||||
4. @{string|nil}: The protocol of the message, if provided.
|
||||
4. <span class="type">@{string}|@{nil}</span>: The protocol of the message, if provided.
|
||||
|
||||
## Example
|
||||
Prints a message when one is sent:
|
||||
|
@@ -4,6 +4,9 @@ module: [kind=event] redstone
|
||||
|
||||
The @{event!redstone} event is fired whenever any redstone inputs on the computer change.
|
||||
|
||||
## Return values
|
||||
1. @{string}: The event name.
|
||||
|
||||
## Example
|
||||
Prints a message when a redstone input changes:
|
||||
```lua
|
||||
|
@@ -10,7 +10,7 @@ The @{task_complete} event is fired when an asynchronous task completes. This is
|
||||
2. @{number}: The ID of the task that completed.
|
||||
3. @{boolean}: Whether the command succeeded.
|
||||
4. @{string}: If the command failed, an error message explaining the failure. (This is not present if the command succeeded.)
|
||||
...: Any parameters returned from the command.
|
||||
5. <abbr title="Variable number of arguments">…</abbr>: Any parameters returned from the command.
|
||||
|
||||
## Example
|
||||
Prints the results of an asynchronous command:
|
||||
|
@@ -9,8 +9,12 @@ The @{term_resize} event is fired when the main terminal is resized. For instanc
|
||||
When this event fires, some parts of the terminal may have been moved or deleted. Simple terminal programs (those
|
||||
not using @{term.setCursorPos}) can ignore this event, but more complex GUI programs should redraw the entire screen.
|
||||
|
||||
## Return values
|
||||
1. @{string}: The event name.
|
||||
|
||||
## Example
|
||||
Prints :
|
||||
Print a message each time the terminal is resized.
|
||||
|
||||
```lua
|
||||
while true do
|
||||
os.pullEvent("term_resize")
|
||||
|
@@ -8,6 +8,9 @@ This event is normally handled by @{os.pullEvent}, and will not be returned. How
|
||||
|
||||
@{terminate} will be sent even when a filter is provided to @{os.pullEventRaw}. When using @{os.pullEventRaw} with a filter, make sure to check that the event is not @{terminate}.
|
||||
|
||||
## Return values
|
||||
1. @{string}: The event name.
|
||||
|
||||
## Example
|
||||
Prints a message when Ctrl-T is held:
|
||||
```lua
|
||||
|
@@ -10,12 +10,12 @@ The @{timer} event is fired when a timer started with @{os.startTimer} completes
|
||||
2. @{number}: The ID of the timer that finished.
|
||||
|
||||
## Example
|
||||
Starts a timer and then prints its ID:
|
||||
Start and wait for a timer to finish.
|
||||
```lua
|
||||
local timerID = os.startTimer(2)
|
||||
local timer_id = os.startTimer(2)
|
||||
local event, id
|
||||
repeat
|
||||
event, id = os.pullEvent("timer")
|
||||
until id == timerID
|
||||
until id == timer_id
|
||||
print("Timer with ID " .. id .. " was fired")
|
||||
```
|
||||
|
@@ -4,6 +4,9 @@ module: [kind=event] turtle_inventory
|
||||
|
||||
The @{turtle_inventory} event is fired when a turtle's inventory is changed.
|
||||
|
||||
## Return values
|
||||
1. @{string}: The event name.
|
||||
|
||||
## Example
|
||||
Prints a message when the inventory is changed:
|
||||
```lua
|
||||
|
90
doc/guides/gps_setup.md
Normal file
90
doc/guides/gps_setup.md
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
module: [kind=guide] gps_setup
|
||||
---
|
||||
|
||||
# Setting up GPS
|
||||
The @{gps} API allows computers and turtles to find their current position using wireless modems.
|
||||
|
||||
In order to use GPS, you'll need to set up multiple *GPS hosts*. These are computers running the special `gps host`
|
||||
program, which tell other computers the host's position. Several hosts running together are known as a *GPS
|
||||
constellation*.
|
||||
|
||||
In order to give the best results, a GPS constellation needs at least four computers. More than four GPS hosts per
|
||||
constellation is redundant, but it does not cause problems.
|
||||
|
||||
## Building a GPS constellation
|
||||
{.big-image}
|
||||
|
||||
We are going to build our GPS constellation as shown in the image above. You will need 4 computers and either 4 wireless
|
||||
modems or 4 ender modems. Try not to mix ender and wireless modems together as you might get some odd behavior when your
|
||||
requesting computers are out of range.
|
||||
|
||||
:::tip Ender modems vs wireless modems
|
||||
Ender modems have a very large range, which makes them very useful for setting up GPS hosts. If you do this then you
|
||||
will likely only need one GPS constellation for the whole dimension (such as the Overworld or Nether).
|
||||
|
||||
If you do use wireless modems then you may find that you need multiple GPS constellations to cover your needs.
|
||||
|
||||
A computer needs a wireless or ender modem and to be in range of a GPS constellation that is in the same dimension as it
|
||||
to use the GPS API. The reason for this is that ComputerCraft mimics real-life GPS by making use of the distance
|
||||
parameter of @{modem_message|modem messages} and some maths.
|
||||
:::
|
||||
|
||||
Locate where you want to place your GPS constellation. You will need an area at least 6 blocks high, 6 blocks wide, and
|
||||
6 blocks deep (6x6x6). If you are using wireless modems then you may want to build your constellation as high as you can
|
||||
because high altitude boosts modem message range and thus the radius that your constellation covers.
|
||||
|
||||
The GPS constellation will only work when it is in a loaded chunk. If you want your constellation to always be
|
||||
accessible, you may want to permanently load the chunk using a vanilla or modded chunk loader. Make sure that your 6x6x6
|
||||
area fits in a single chunk to reduce the number of chunks that need to be kept loaded.
|
||||
|
||||
Let's get started building the constellation! Place your first computer in one of the corners of your 6x6x6. Remember
|
||||
which computer this is as other computers need to be placed relative to it. Place the second computer 4 blocks above the
|
||||
first. Go back to your first computer and place your third computer 5 blocks in front of your first computer, leaving 4
|
||||
blocks of air between them. Finally for the fourth computer, go back to your first computer and place it 5 blocks right
|
||||
of your first computer, leaving 4 blocks of air between them.
|
||||
|
||||
With all four computers placed within the 6x6x6, place one modem on top of each computer. You should have 4 modems and 4
|
||||
computers all within your 6x6x6 where each modem is attached to a computer and each computer has a modem.
|
||||
|
||||
Currently your GPS constellation will not work, that's because each host is not aware that it's a GPS host. We will fix
|
||||
this in the next section.
|
||||
|
||||
## Configuring the constellation
|
||||
Now that the structure of your constellation is built, we need to configure each host in it.
|
||||
|
||||
Go back to the first computer that you placed and create a startup file, by running `edit startup`.
|
||||
|
||||
Type the following code into the file:
|
||||
```lua
|
||||
shell.run("gps", "host", x, y, z)
|
||||
```
|
||||
|
||||
Escape from the computer GUI and then press <kbd>F3</kbd> to open Minecraft's debug screen and then look at the computer
|
||||
(without opening the GUI). On the right of the screen about halfway down you should see an entry labeled `Targeted
|
||||
Block`, the numbers correspond to the position of the block that you are looking at. Replace `x` with the first number,
|
||||
`y` with the second number, and `z` with the third number.
|
||||
|
||||
For example, if I had a computer at x = 59, y = 5, z = -150, then my <kbd>F3</kbd> debug screen entry would be `Target
|
||||
Block: 59, 5, -150` and I would change my startup file to this `shell.run("gps", "host", 59, 5, -150)`.
|
||||
|
||||
To hide Minecraft's debug screen, press <kbd>F3</kbd> again.
|
||||
|
||||
Create similar startup files for the other computers in your constellation, making sure to input the each computer's own
|
||||
coordinates.
|
||||
|
||||
:::caution Modem messages come from the computer's position, not the modem's
|
||||
Wireless modems transmit from the block that they are attached to *not* the block space that they occupy, the
|
||||
coordinates that you input into your GPS host should be the position of the computer and not the position of the modem.
|
||||
:::
|
||||
|
||||
Congratulations, your constellation is now fully set up! You can test it by placing another computer close by, placing a
|
||||
wireless modem on it, and running the `gps locate` program (or calling the @{gps.locate} function).
|
||||
|
||||
:::info Why use Minecraft's coordinates?
|
||||
CC doesn't care if you use Minecraft's coordinate system, so long as all of the GPS hosts with overlapping ranges use
|
||||
the same reference point (requesting computers will get confused if hosts have different reference points). However,
|
||||
using MC's coordinate system does provide a nice standard to adopt server-wide. It also is consistent with how command
|
||||
computers get their location, they use MC's command system to get their block which returns that in MC's coordinate
|
||||
system.
|
||||
:::
|
@@ -25,7 +25,7 @@ single-player or multiplayer. Look for lines that look like this:
|
||||
```
|
||||
|
||||
On 1.95.0 and later, this will be a single entry with `host = "$private"`. On earlier versions, this will be a number of
|
||||
`[[http.rules]]` with various IP addresses. You will want to remove all of the `[[http.rules]]` entires that have
|
||||
`[[http.rules]]` with various IP addresses. You will want to remove all of the `[[http.rules]]` entries that have
|
||||
`action = "deny"`. Then save the file and relaunch Minecraft (Server).
|
||||
|
||||
Here's what it should look like after removing:
|
||||
@@ -54,7 +54,7 @@ like this:
|
||||
```toml
|
||||
#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
|
||||
#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
|
||||
#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
|
||||
#You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
|
||||
blacklist = ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"]
|
||||
```
|
||||
|
||||
@@ -65,7 +65,7 @@ Here's what it should look like after removing:
|
||||
```toml
|
||||
#A list of wildcards for domains or IP ranges that cannot be accessed through the "http" API on Computers.
|
||||
#If this is empty then all whitelisted domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
|
||||
#You can use domain names ("pastebin.com"), wilcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
|
||||
#You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
|
||||
blacklist = []
|
||||
```
|
||||
|
||||
|
@@ -185,7 +185,7 @@ end
|
||||
|
||||
:::note Confused?
|
||||
Don't worry if you don't understand this example. It's quite advanced, and does use some ideas that this guide doesn't
|
||||
cover. That said, don't be afraid to ask on [Discord] or [IRC] either!
|
||||
cover. That said, don't be afraid to ask on [GitHub Discussions] or [IRC] either!
|
||||
:::
|
||||
|
||||
It's worth noting that the examples of audio processing we've mentioned here are about manipulating the _amplitude_ of
|
||||
@@ -200,6 +200,5 @@ This is, I'm afraid, left as an exercise to the reader.
|
||||
[PCM]: https://en.wikipedia.org/wiki/Pulse-code_modulation "Pulse-code Modulation - Wikipedia"
|
||||
[Ring Buffer]: https://en.wikipedia.org/wiki/Circular_buffer "Circular buffer - Wikipedia"
|
||||
[Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia"
|
||||
|
||||
[Discord]: https://discord.computercraft.cc "The Minecraft Computer Mods Discord"
|
||||
[IRC]: http://webchat.esper.net/?channels=computercraft "IRC webchat on EsperNet"
|
||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
||||
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
||||
|
BIN
doc/images/gps-constellation-example.png
Normal file
BIN
doc/images/gps-constellation-example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 331 KiB |
@@ -37,8 +37,7 @@ little daunting getting started. Thankfully, there's several fantastic tutorials
|
||||
Once you're a little more familiar with the mod, the sidebar and links below provide more detailed documentation on the
|
||||
various APIs and peripherals provided by the mod.
|
||||
|
||||
If you get stuck, do pop in to the [Minecraft Computer Mod Discord guild][discord] or ComputerCraft's
|
||||
[IRC channel][irc].
|
||||
If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC].
|
||||
|
||||
## Get Involved
|
||||
CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug].
|
||||
@@ -51,5 +50,5 @@ CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please
|
||||
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
|
||||
[ccrestitched]: https://www.curseforge.com/minecraft/mc-mods/cc-restitched "Download CC: Restitched from CurseForge"
|
||||
[lua]: https://www.lua.org/ "Lua's main website"
|
||||
[discord]: https://discord.computercraft.cc "The Minecraft Computer Mods Discord"
|
||||
[irc]: http://webchat.esper.net/?channels=computercraft "IRC webchat on EsperNet"
|
||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
||||
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
||||
|
61
doc/mod-page.md
Normal file
61
doc/mod-page.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 
|
||||
CC: Tweaked is a mod for Minecraft which adds programmable computers, turtles and more to the game. A fork of the much-beloved [ComputerCraft], it continues its legacy with better performance, stability, and a wealth of new features.
|
||||
|
||||
**Fabric support is added by the [CC: Restitched][ccrestitched] project**
|
||||
|
||||
## Testimonials
|
||||
|
||||
> I'm not sure what that is [...] I don't know where that came from.
|
||||
>
|
||||
> \- [direwolf20, December 2020](https://youtu.be/D8Ue9I-SKeM?t=980)
|
||||
|
||||
> It is basically ComputerCraft. It has the turtles, and the computers, and writing Lua programs and all that stuff.
|
||||
>
|
||||
> \- [direwolf20, May 2022](https://youtu.be/7Ruq33XmQIQ?t=537)
|
||||
|
||||
|
||||
## Features
|
||||
Controlled using the [Lua programming language][lua], CC: Tweaked's computers provides all the tools you need to start
|
||||
writing code and automating your Minecraft world.
|
||||
|
||||

|
||||
|
||||
While computers are incredibly powerful, they're rather limited by their inability to move about. *Turtles* are the
|
||||
solution here. They can move about the world, placing and breaking blocks, swinging a sword to protect you from zombies,
|
||||
or whatever else you program them to!
|
||||
|
||||

|
||||
|
||||
Not all problems can be solved with a pickaxe though, and so CC: Tweaked also provides a bunch of additional peripherals
|
||||
for your computers. You can play a tune with speakers, display text or images on a monitor, connect all your
|
||||
computers together with modems, and much more.
|
||||
|
||||
Computers can now also interact with inventories such as chests, allowing you to build complex inventory and item
|
||||
management systems.
|
||||
|
||||

|
||||
|
||||
## Getting Started
|
||||
While ComputerCraft is lovely for both experienced programmers and for people who have never coded before, it can be a
|
||||
little daunting getting started. Thankfully, there's several fantastic tutorials out there:
|
||||
|
||||
- [Direwolf20's ComputerCraft tutorials](https://www.youtube.com/watch?v=wrUHUhfCY5A "ComputerCraft Tutorial Episode 1 - HELP! and Hello World")
|
||||
- [Sethbling's ComputerCraft series](https://www.youtube.com/watch?v=DSsx4VSe-Uk "Programming Tutorial with Minecraft Turtles -- Ep. 1: Intro to Turtles and If-Then-Else_End")
|
||||
- [Lyqyd's Computer Basics 1](http://www.computercraft.info/forums2/index.php?/topic/15033-computer-basics-i/ "Computer Basics I")
|
||||
|
||||
Once you're a little more familiar with the mod, the [wiki](https://tweaked.cc/) provides more detailed documentation on the
|
||||
various APIs and peripherals provided by the mod.
|
||||
|
||||
If you get stuck, do [ask a question on GitHub][GitHub Discussions] or pop in to the ComputerCraft's [IRC channel][IRC].
|
||||
|
||||
## Get Involved
|
||||
CC: Tweaked lives on [GitHub]. If you've got any ideas, feedback or bugs please do [create an issue][bug].
|
||||
|
||||
[github]: https://github.com/cc-tweaked/CC-Tweaked/ "CC: Tweaked on GitHub"
|
||||
[bug]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose
|
||||
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub"
|
||||
[forge]: https://files.minecraftforge.net/ "Download Minecraft Forge."
|
||||
[ccrestitched]: https://modrinth.com/mod/cc-restitched "Download CC: Restitched from Modrinth"
|
||||
[lua]: https://www.lua.org/ "Lua's main website"
|
||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
||||
[IRC]: http://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
@@ -1,34 +0,0 @@
|
||||
--- @module fs
|
||||
|
||||
--- Returns true if a path is mounted to the parent filesystem.
|
||||
--
|
||||
-- The root filesystem "/" is considered a mount, along with disk folders and
|
||||
-- the rom folder. Other programs (such as network shares) can exstend this to
|
||||
-- make other mount types by correctly assigning their return value for getDrive.
|
||||
--
|
||||
-- @tparam string path The path to check.
|
||||
-- @treturn boolean If the path is mounted, rather than a normal file/folder.
|
||||
-- @throws If the path does not exist.
|
||||
-- @see getDrive
|
||||
-- @since 1.87.0
|
||||
function isDriveRoot(path) end
|
||||
|
||||
--[[- Provides completion for a file or directory name, suitable for use with
|
||||
@{_G.read}.
|
||||
|
||||
When a directory is a possible candidate for completion, two entries are
|
||||
included - one with a trailing slash (indicating that entries within this
|
||||
directory exist) and one without it (meaning this entry is an immediate
|
||||
completion candidate). `include_dirs` can be set to @{false} to only include
|
||||
those with a trailing slash.
|
||||
|
||||
@tparam string path The path to complete.
|
||||
@tparam string location The location where paths are resolved from.
|
||||
@tparam[opt] boolean include_files When @{false}, only directories will be
|
||||
included in the returned list.
|
||||
@tparam[opt] boolean include_dirs When @{false}, "raw" directories will not be
|
||||
included in the returned list.
|
||||
@treturn { string... } A list of possible completion candidates.
|
||||
@since 1.74
|
||||
]]
|
||||
function complete(path, location, include_files, include_dirs) end
|
@@ -14,7 +14,7 @@ thread, not the whole program.
|
||||
|
||||
:::tip
|
||||
Because sleep internally uses timers, it is a function that yields. This means
|
||||
that you can use it to prevent "Too long without yielding" errors, however, as
|
||||
that you can use it to prevent "Too long without yielding" errors. However, as
|
||||
the minimum sleep time is 0.05 seconds, it will slow your program down.
|
||||
:::
|
||||
|
||||
|
@@ -1,177 +0,0 @@
|
||||
--- Make HTTP requests, sending and receiving data to a remote web server.
|
||||
--
|
||||
-- @module http
|
||||
-- @since 1.1
|
||||
-- @see local_ips To allow accessing servers running on your local network.
|
||||
|
||||
--- Asynchronously make a HTTP request to the given url.
|
||||
--
|
||||
-- This returns immediately, a @{http_success} or @{http_failure} will be queued
|
||||
-- once the request has completed.
|
||||
--
|
||||
-- @tparam string url The url to request
|
||||
-- @tparam[opt] string body An optional string containing the body of the
|
||||
-- request. If specified, a `POST` request will be made instead.
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of this request.
|
||||
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
|
||||
-- the body will not be UTF-8 encoded, and the received response will not be
|
||||
-- decoded.
|
||||
--
|
||||
-- @tparam[2] {
|
||||
-- url = string, body? = string, headers? = { [string] = string },
|
||||
-- binary? = boolean, method? = string, redirect? = boolean,
|
||||
-- } request Options for the request.
|
||||
--
|
||||
-- This table form is an expanded version of the previous syntax. All arguments
|
||||
-- from above are passed in as fields instead (for instance,
|
||||
-- `http.request("https://example.com")` becomes `http.request { url =
|
||||
-- "https://example.com" }`).
|
||||
--
|
||||
-- This table also accepts several additional options:
|
||||
--
|
||||
-- - `method`: Which HTTP method to use, for instance `"PATCH"` or `"DELETE"`.
|
||||
-- - `redirect`: Whether to follow HTTP redirects. Defaults to true.
|
||||
--
|
||||
-- @see http.get For a synchronous way to make GET requests.
|
||||
-- @see http.post For a synchronous way to make POST requests.
|
||||
--
|
||||
-- @changed 1.63 Added argument for headers.
|
||||
-- @changed 1.80pr1 Added argument for binary handles.
|
||||
-- @changed 1.80pr1.6 Added support for table argument.
|
||||
-- @changed 1.86.0 Added PATCH and TRACE methods.
|
||||
function request(...) end
|
||||
|
||||
--- Make a HTTP GET request to the given url.
|
||||
--
|
||||
-- @tparam string url The url to request
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of this request.
|
||||
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
|
||||
-- the body will not be UTF-8 encoded, and the received response will not be
|
||||
-- decoded.
|
||||
--
|
||||
-- @tparam[2] {
|
||||
-- url = string, headers? = { [string] = string },
|
||||
-- binary? = boolean, method? = string, redirect? = boolean,
|
||||
-- } request Options for the request. See @{http.request} for details on how
|
||||
-- these options behave.
|
||||
--
|
||||
-- @treturn Response The resulting http response, which can be read from.
|
||||
-- @treturn[2] nil When the http request failed, such as in the event of a 404
|
||||
-- error or connection timeout.
|
||||
-- @treturn string A message detailing why the request failed.
|
||||
-- @treturn Response|nil The failing http response, if available.
|
||||
--
|
||||
-- @changed 1.63 Added argument for headers.
|
||||
-- @changed 1.80pr1 Response handles are now returned on error if available.
|
||||
-- @changed 1.80pr1 Added argument for binary handles.
|
||||
-- @changed 1.80pr1.6 Added support for table argument.
|
||||
-- @changed 1.86.0 Added PATCH and TRACE methods.
|
||||
--
|
||||
-- @usage Make a request to [example.tweaked.cc](https://example.tweaked.cc),
|
||||
-- and print the returned page.
|
||||
-- ```lua
|
||||
-- local request = http.get("https://example.tweaked.cc")
|
||||
-- print(request.readAll())
|
||||
-- -- => HTTP is working!
|
||||
-- request.close()
|
||||
-- ```
|
||||
function get(...) end
|
||||
|
||||
--- Make a HTTP POST request to the given url.
|
||||
--
|
||||
-- @tparam string url The url to request
|
||||
-- @tparam string body The body of the POST request.
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of this request.
|
||||
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
|
||||
-- the body will not be UTF-8 encoded, and the received response will not be
|
||||
-- decoded.
|
||||
--
|
||||
-- @tparam[2] {
|
||||
-- url = string, body? = string, headers? = { [string] = string },
|
||||
-- binary? = boolean, method? = string, redirect? = boolean,
|
||||
-- } request Options for the request. See @{http.request} for details on how
|
||||
-- these options behave.
|
||||
--
|
||||
-- @treturn Response The resulting http response, which can be read from.
|
||||
-- @treturn[2] nil When the http request failed, such as in the event of a 404
|
||||
-- error or connection timeout.
|
||||
-- @treturn string A message detailing why the request failed.
|
||||
-- @treturn Response|nil The failing http response, if available.
|
||||
--
|
||||
-- @since 1.31
|
||||
-- @changed 1.63 Added argument for headers.
|
||||
-- @changed 1.80pr1 Response handles are now returned on error if available.
|
||||
-- @changed 1.80pr1 Added argument for binary handles.
|
||||
-- @changed 1.80pr1.6 Added support for table argument.
|
||||
-- @changed 1.86.0 Added PATCH and TRACE methods.
|
||||
function post(...) end
|
||||
|
||||
--- Asynchronously determine whether a URL can be requested.
|
||||
--
|
||||
-- If this returns `true`, one should also listen for @{http_check} which will
|
||||
-- container further information about whether the URL is allowed or not.
|
||||
--
|
||||
-- @tparam string url The URL to check.
|
||||
-- @treturn true When this url is not invalid. This does not imply that it is
|
||||
-- allowed - see the comment above.
|
||||
-- @treturn[2] false When this url is invalid.
|
||||
-- @treturn string A reason why this URL is not valid (for instance, if it is
|
||||
-- malformed, or blocked).
|
||||
--
|
||||
-- @see http.checkURL For a synchronous version.
|
||||
function checkURLAsync(url) end
|
||||
|
||||
--- Determine whether a URL can be requested.
|
||||
--
|
||||
-- If this returns `true`, one should also listen for @{http_check} which will
|
||||
-- container further information about whether the URL is allowed or not.
|
||||
--
|
||||
-- @tparam string url The URL to check.
|
||||
-- @treturn true When this url is valid and can be requested via @{http.request}.
|
||||
-- @treturn[2] false When this url is invalid.
|
||||
-- @treturn string A reason why this URL is not valid (for instance, if it is
|
||||
-- malformed, or blocked).
|
||||
--
|
||||
-- @see http.checkURLAsync For an asynchronous version.
|
||||
--
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- print(http.checkURL("https://example.tweaked.cc/"))
|
||||
-- -- => true
|
||||
-- print(http.checkURL("http://localhost/"))
|
||||
-- -- => false Domain not permitted
|
||||
-- print(http.checkURL("not a url"))
|
||||
-- -- => false URL malformed
|
||||
-- ```
|
||||
function checkURL(url) end
|
||||
|
||||
--- Open a websocket.
|
||||
--
|
||||
-- @tparam string url The websocket url to connect to. This should have the
|
||||
-- `ws://` or `wss://` protocol.
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of the initial websocket connection.
|
||||
--
|
||||
-- @treturn Websocket The websocket connection.
|
||||
-- @treturn[2] false If the websocket connection failed.
|
||||
-- @treturn string An error message describing why the connection failed.
|
||||
-- @since 1.80pr1.1
|
||||
-- @changed 1.80pr1.3 No longer asynchronous.
|
||||
-- @changed 1.95.3 Added User-Agent to default headers.
|
||||
function websocket(url, headers) end
|
||||
|
||||
--- Asynchronously open a websocket.
|
||||
--
|
||||
-- This returns immediately, a @{websocket_success} or @{websocket_failure}
|
||||
-- will be queued once the request has completed.
|
||||
--
|
||||
-- @tparam string url The websocket url to connect to. This should have the
|
||||
-- `ws://` or `wss://` protocol.
|
||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
|
||||
-- of the initial websocket connection.
|
||||
-- @since 1.80pr1.3
|
||||
-- @changed 1.95.3 Added User-Agent to default headers.
|
||||
function websocketAsync(url, headers) end
|
@@ -1,10 +1,12 @@
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
org.gradle.parallel=true
|
||||
|
||||
kotlin.stdlib.default.dependency=false
|
||||
kotlin.jvm.target.validation.mode=error
|
||||
|
||||
# Mod properties
|
||||
mod_version=1.100.7
|
||||
isUnstable=false
|
||||
modVersion=1.103.0
|
||||
|
||||
# Minecraft properties (update mods.toml when changing)
|
||||
mc_version=1.19
|
||||
mapping_version=2022.03.13
|
||||
forge_version=41.0.16
|
||||
# NO SERIOUSLY, UPDATE mods.toml WHEN CHANGING
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.19.3
|
||||
|
154
gradle/libs.versions.toml
Normal file
154
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,154 @@
|
||||
[versions]
|
||||
|
||||
# Minecraft
|
||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
||||
fabric-api = "0.68.1+1.19.3"
|
||||
fabric-loader = "0.14.11"
|
||||
forge = "44.1.0"
|
||||
forgeSpi = "6.0.0"
|
||||
mixin = "0.8.5"
|
||||
parchment = "2022.11.27"
|
||||
parchmentMc = "1.19.2"
|
||||
|
||||
# Normal dependencies
|
||||
asm = "9.3"
|
||||
autoService = "1.0.1"
|
||||
checkerFramework = "3.12.0"
|
||||
cobalt = "0.6.0"
|
||||
fastutil = "8.5.9"
|
||||
guava = "31.1-jre"
|
||||
jetbrainsAnnotations = "23.0.0"
|
||||
jsr305 = "3.0.2"
|
||||
kotlin = "1.8.0"
|
||||
kotlin-coroutines = "1.6.4"
|
||||
netty = "4.1.82.Final"
|
||||
nightConfig = "3.6.5"
|
||||
slf4j = "1.7.36"
|
||||
|
||||
# Minecraft mods
|
||||
forgeConfig = "5.0.4"
|
||||
iris = "1.19.3-v1.4.6"
|
||||
jei = "11.3.0.262"
|
||||
modmenu = "5.0.1"
|
||||
oculus = "1.2.5"
|
||||
rei = "10.0.578"
|
||||
rubidium = "0.6.1"
|
||||
sodium = "mc1.19.3-0.4.6"
|
||||
|
||||
# Testing
|
||||
byteBuddy = "1.12.19"
|
||||
hamcrest = "2.2"
|
||||
jqwik = "1.7.0"
|
||||
junit = "5.9.1"
|
||||
|
||||
# Build tools
|
||||
cctJavadoc = "1.6.0"
|
||||
checkstyle = "10.3.4"
|
||||
curseForgeGradle = "1.0.11"
|
||||
errorProne-core = "2.14.0"
|
||||
errorProne-plugin = "2.0.2"
|
||||
fabric-loom = "1.0-SNAPSHOT"
|
||||
forgeGradle = "5.1.+"
|
||||
githubRelease = "2.2.12"
|
||||
ideaExt = "1.1.6"
|
||||
illuaminate = "0.1.0-13-g689d73d"
|
||||
librarian = "1.+"
|
||||
minotaur = "2.+"
|
||||
mixinGradle = "0.7.+"
|
||||
nullAway = "0.9.9"
|
||||
quiltflower = "1.7.3"
|
||||
shadow = "7.1.2"
|
||||
spotless = "6.8.0"
|
||||
taskTree = "2.1.0"
|
||||
vanillaGradle = "0.2.1-SNAPSHOT"
|
||||
|
||||
[libraries]
|
||||
# Normal dependencies
|
||||
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
|
||||
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
|
||||
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
|
||||
cobalt = { module = "org.squiddev:Cobalt", version.ref = "cobalt" }
|
||||
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
|
||||
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
|
||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
|
||||
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
|
||||
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
|
||||
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
|
||||
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
|
||||
netty-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
|
||||
nightConfig-core = { module = "com.electronwill.night-config:core", version.ref = "nightConfig" }
|
||||
nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref = "nightConfig" }
|
||||
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
|
||||
# Minecraft mods
|
||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
|
||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
|
||||
forgeConfig = { module = "fuzs.forgeconfigapiport:forgeconfigapiport-fabric", version.ref = "forgeConfig" }
|
||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
|
||||
jei-api = { module = "mezz.jei:jei-1.19.2-common-api", version.ref = "jei" }
|
||||
jei-fabric = { module = "mezz.jei:jei-1.19.2-fabric", version.ref = "jei" }
|
||||
jei-forge = { module = "mezz.jei:jei-1.19.2-forge", version.ref = "jei" }
|
||||
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
|
||||
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
|
||||
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
|
||||
rei-api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" }
|
||||
rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" }
|
||||
rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
|
||||
rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
|
||||
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
|
||||
|
||||
# Testing
|
||||
byteBuddyAgent = { module ="net.bytebuddy:byte-buddy-agent", version.ref = "byteBuddy" }
|
||||
byteBuddy = { module ="net.bytebuddy:byte-buddy", version.ref = "byteBuddy" }
|
||||
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
|
||||
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
|
||||
jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
|
||||
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
|
||||
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
|
||||
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
|
||||
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
|
||||
|
||||
# Build tools
|
||||
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
|
||||
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
|
||||
errorProne-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "errorProne-core" }
|
||||
errorProne-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "errorProne-core" }
|
||||
errorProne-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne-core" }
|
||||
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
|
||||
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
|
||||
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
|
||||
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
|
||||
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
|
||||
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
|
||||
quiltflower = { module = "io.github.juuxel:loom-quiltflower", version.ref = "quiltflower" }
|
||||
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
|
||||
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
|
||||
|
||||
[plugins]
|
||||
curseForgeGradle = { id = "net.darkhax.curseforgegradle", version.ref = "curseForgeGradle" }
|
||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
|
||||
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
|
||||
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
|
||||
minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" }
|
||||
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
|
||||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
|
||||
|
||||
[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"]
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@@ -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/docs/lua)
|
||||
; 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,37 +81,37 @@
|
||||
;; 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)
|
||||
|
||||
(lint
|
||||
(globals
|
||||
:max sleep write
|
||||
cct_test describe expect howlci fail it pending stub)))
|
||||
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__)))
|
||||
|
640
package-lock.json
generated
640
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@rollup/plugin-url": "^6.1.0",
|
||||
"@rollup/plugin-url": "^7.0.0",
|
||||
"@types/glob": "^7.2.0",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"glob": "^8.0.3",
|
||||
|
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,72 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
|
||||
* For help using the API, and posting your mods, visit the forums at computercraft.info.
|
||||
*/
|
||||
package dan200.computercraft.api;
|
||||
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
/**
|
||||
* Tags provided by ComputerCraft.
|
||||
*/
|
||||
public class ComputerCraftTags {
|
||||
public static class Items {
|
||||
public static final TagKey<Item> COMPUTER = make("computer");
|
||||
public static final TagKey<Item> TURTLE = make("turtle");
|
||||
public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
|
||||
public static final TagKey<Item> MONITOR = make("monitor");
|
||||
|
||||
/**
|
||||
* Items which can be {@linkplain Item#use(Level, Player, InteractionHand) used} when calling
|
||||
* {@code turtle.place()}.
|
||||
* <p>
|
||||
* This does not cover items who handle placing inside {@link Item#useOn(UseOnContext)}, as that is always
|
||||
* called.
|
||||
*/
|
||||
public static final TagKey<Item> TURTLE_CAN_PLACE = make("turtle_can_place");
|
||||
|
||||
private static TagKey<Item> make(String name) {
|
||||
return TagKey.create(Registries.ITEM, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Blocks {
|
||||
public static final TagKey<Block> COMPUTER = make("computer");
|
||||
public static final TagKey<Block> TURTLE = make("turtle");
|
||||
public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
|
||||
public static final TagKey<Block> MONITOR = make("monitor");
|
||||
|
||||
/**
|
||||
* Blocks which can be broken by any turtle tool.
|
||||
*/
|
||||
public static final TagKey<Block> TURTLE_ALWAYS_BREAKABLE = make("turtle_always_breakable");
|
||||
|
||||
/**
|
||||
* Blocks which can be broken by the default shovel tool.
|
||||
*/
|
||||
public static final TagKey<Block> TURTLE_SHOVEL_BREAKABLE = make("turtle_shovel_harvestable");
|
||||
|
||||
/**
|
||||
* Blocks which can be broken with the default sword tool.
|
||||
*/
|
||||
public static final TagKey<Block> TURTLE_SWORD_BREAKABLE = make("turtle_sword_harvestable");
|
||||
|
||||
/**
|
||||
* Blocks which can be broken with the default hoe tool.
|
||||
*/
|
||||
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
|
||||
|
||||
private static TagKey<Block> make(String name) {
|
||||
return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,7 +10,6 @@ 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.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
@@ -22,14 +21,12 @@ import javax.annotation.Nullable;
|
||||
* @param blockEntity The block entity at this position, if it exists.
|
||||
*/
|
||||
public record BlockReference(
|
||||
@Nonnull Level level,
|
||||
@Nonnull BlockPos pos,
|
||||
@Nonnull BlockState state,
|
||||
Level level,
|
||||
BlockPos pos,
|
||||
BlockState state,
|
||||
@Nullable BlockEntity blockEntity
|
||||
)
|
||||
{
|
||||
public BlockReference( Level level, BlockPos pos )
|
||||
{
|
||||
this( level, pos, level.getBlockState( pos ), level.getBlockEntity( pos ) );
|
||||
) {
|
||||
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);
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A registry which provides computer-visible detail about in-game objects such as blocks, items or fluids.
|
||||
* <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 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> {
|
||||
/**
|
||||
* Registers a detail provider.
|
||||
*
|
||||
* @param provider The detail provider to register.
|
||||
* @see DetailProvider
|
||||
*/
|
||||
void addProvider(DetailProvider<T> provider);
|
||||
|
||||
/**
|
||||
* Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable
|
||||
* for when you need to compute the details for a large number of values.
|
||||
* <p>
|
||||
* This method <em>MAY</em> be thread safe: consult the instance's documentation for details.
|
||||
*
|
||||
* @param object The object to get details for.
|
||||
* @return The basic details.
|
||||
*/
|
||||
Map<String, Object> getBasicDetails(T object);
|
||||
|
||||
/**
|
||||
* Compute all details about an object, using {@link #getBasicDetails(Object)} and any registered providers.
|
||||
* <p>
|
||||
* This method is <em>NOT</em> thread safe. It should only be called from the computer thread.
|
||||
*
|
||||
* @param object The object to get details for.
|
||||
* @return The computed details.
|
||||
*/
|
||||
Map<String, Object> getDetails(T object);
|
||||
}
|
@@ -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.detail;
|
||||
|
||||
import dan200.computercraft.impl.ComputerCraftAPIService;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
/**
|
||||
* {@link DetailRegistry}s for built-in Minecraft types.
|
||||
*/
|
||||
public class VanillaDetailRegistries {
|
||||
/**
|
||||
* Provides details for {@link ItemStack}s.
|
||||
* <p>
|
||||
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe (assuming the stack is immutable)
|
||||
* and may be called from the computer thread.
|
||||
*/
|
||||
public static final DetailRegistry<ItemStack> ITEM_STACK = ComputerCraftAPIService.get().getItemStackDetailRegistry();
|
||||
|
||||
/**
|
||||
* Provides details for {@link BlockReference}, a reference to a {@link Block} in the world.
|
||||
* <p>
|
||||
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe and may be called from the computer
|
||||
* thread.
|
||||
*/
|
||||
public static final DetailRegistry<BlockReference> BLOCK_IN_WORLD = ComputerCraftAPIService.get().getBlockInWorldDetailRegistry();
|
||||
}
|
@@ -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);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user