mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-16 22:47:38 +00:00
Compare commits
274 Commits
v1.19.3-1.
...
v1.95.3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
78a685c2cb | ||
![]() |
a475f55b9c | ||
![]() |
9353ded2d5 | ||
![]() |
d197268e53 | ||
![]() |
9648265056 | ||
![]() |
264dbc570f | ||
![]() |
e4ad8bc117 | ||
![]() |
1b31972092 | ||
![]() |
6cd9fa9fb2 | ||
![]() |
d10f297ca0 | ||
![]() |
9ae5636d41 | ||
![]() |
a60daf78cf | ||
![]() |
76e0507c3b | ||
![]() |
d8e2fab2b1 | ||
![]() |
5c5e510075 | ||
![]() |
5a309258af | ||
![]() |
b0782ec38b | ||
![]() |
46846a4fde | ||
![]() |
d955443b21 | ||
![]() |
57e6c49844 | ||
![]() |
66dbab7a6b | ||
![]() |
558976e4ca | ||
![]() |
3a7470a108 | ||
![]() |
cf2b332c3c | ||
![]() |
8984ebcf80 | ||
![]() |
a28e7e2db3 | ||
![]() |
3860e2466c | ||
![]() |
5b31c2536a | ||
![]() |
98dc7a6e58 | ||
![]() |
5e0ceda7ce | ||
![]() |
7fe3ac9222 | ||
![]() |
a65b8ed04c | ||
![]() |
3e14b84c23 | ||
![]() |
1710ad9861 | ||
![]() |
9c48c99be7 | ||
![]() |
d10d1b45fe | ||
![]() |
70a1cf5c5a | ||
![]() |
b546a10bd6 | ||
![]() |
2f35bbb538 | ||
![]() |
891dde43a9 | ||
![]() |
094e0d4f33 | ||
![]() |
df40adce20 | ||
![]() |
e12ce95b2d | ||
![]() |
bcc0effd00 | ||
![]() |
64f3aa2dba | ||
![]() |
515ccfebd3 | ||
![]() |
0b6dbe7778 | ||
![]() |
19054684c6 | ||
![]() |
a72a5e6deb | ||
![]() |
eb2d9482a2 | ||
![]() |
95794fdaf3 | ||
![]() |
af477a401a | ||
![]() |
27531c161d | ||
![]() |
a8f208c394 | ||
![]() |
148d9e9dda | ||
![]() |
67fc21271b | ||
![]() |
ce28e2659a | ||
![]() |
47e4826f2b | ||
![]() |
9bde2d8d13 | ||
![]() |
46acb6866e | ||
![]() |
c0506108f9 | ||
![]() |
0dd7c3695f | ||
![]() |
2a290be248 | ||
![]() |
c4a6c16330 | ||
![]() |
f8b3b544d1 | ||
![]() |
53ae689468 | ||
![]() |
73e460e533 | ||
![]() |
a4e68d637a | ||
![]() |
1f1b20c81e | ||
![]() |
20a9ff7a3f | ||
![]() |
58cd5e3df7 | ||
![]() |
5922d7548c | ||
![]() |
386e01364e | ||
![]() |
70eff6aa63 | ||
![]() |
45ac601946 | ||
![]() |
d6c5a5bd27 | ||
![]() |
02c7903cb7 | ||
![]() |
37e856efdc | ||
![]() |
8a38097887 | ||
![]() |
884b417ec2 | ||
![]() |
1d1d6227eb | ||
![]() |
d5eb82db60 | ||
![]() |
3575654d02 | ||
![]() |
79fcc7241b | ||
![]() |
b203d3aa0a | ||
![]() |
f1176af9d1 | ||
![]() |
717686d855 | ||
![]() |
92811ca0bc | ||
![]() |
f8a58dbcb1 | ||
![]() |
98aabe2cfb | ||
![]() |
f938ed9779 | ||
![]() |
03c9274b27 | ||
![]() |
92c94ac039 | ||
![]() |
5c6fd80b0c | ||
![]() |
f1ec59df15 | ||
![]() |
624b23c7ac | ||
![]() |
9b77a4aaee | ||
![]() |
d8b0037cfa | ||
![]() |
91a9e7fdf7 | ||
![]() |
2c3573719b | ||
![]() |
46bc42d5a7 | ||
![]() |
1b9e93a80f | ||
![]() |
7b93f91ffa | ||
![]() |
31a1027401 | ||
![]() |
e0fc994819 | ||
![]() |
43408bf085 | ||
![]() |
d28f42e8b7 | ||
![]() |
ac452582c1 | ||
![]() |
7e65c6b25c | ||
![]() |
457a863842 | ||
![]() |
eef36e1358 | ||
![]() |
feda08b915 | ||
![]() |
0240ce50ce | ||
![]() |
592b83e784 | ||
![]() |
5d91491ec7 | ||
![]() |
7326d1110d | ||
![]() |
0aa6ac05a0 | ||
![]() |
27a2c063b9 | ||
![]() |
89a195ec06 | ||
![]() |
0e5fd4e8e0 | ||
![]() |
aa4ec53bb6 | ||
![]() |
6b49327462 | ||
![]() |
74ad934889 | ||
![]() |
08b3dbbad5 | ||
![]() |
54eac0a2f8 | ||
![]() |
51ca5e620c | ||
![]() |
076d572831 | ||
![]() |
75f2b91fef | ||
![]() |
79534e5630 | ||
![]() |
d483a91459 | ||
![]() |
71d764f122 | ||
![]() |
2e527eb11e | ||
![]() |
19273b3696 | ||
![]() |
4643641d51 | ||
![]() |
63cd9c5bc7 | ||
![]() |
e9c11ff325 | ||
![]() |
452464aa01 | ||
![]() |
8885462175 | ||
![]() |
b8bd64913b | ||
![]() |
00b458c39a | ||
![]() |
88722d484f | ||
![]() |
6d103e2114 | ||
![]() |
42f23d56ae | ||
![]() |
89d5211bd7 | ||
![]() |
83e70377f7 | ||
![]() |
f6a26f75c3 | ||
![]() |
664df62d5d | ||
![]() |
1348ee0588 | ||
![]() |
145dce7653 | ||
![]() |
7f2651c23e | ||
![]() |
05464107a8 | ||
![]() |
86705787f0 | ||
![]() |
b34d8387d9 | ||
![]() |
4d00969ef0 | ||
![]() |
01d3d12992 | ||
![]() |
5e31dcde83 | ||
![]() |
5184883af1 | ||
![]() |
0c45112262 | ||
![]() |
0bf1672f45 | ||
![]() |
e1b8ac1f84 | ||
![]() |
deea552d99 | ||
![]() |
54229c2ce1 | ||
![]() |
1346a26179 | ||
![]() |
14df44f09d | ||
![]() |
1a21529499 | ||
![]() |
2546990f41 | ||
![]() |
b39ca02464 | ||
![]() |
628618105c | ||
![]() |
075ba03f5d | ||
![]() |
10bf84b631 | ||
![]() |
c346e22a45 | ||
![]() |
418e1335b1 | ||
![]() |
d52372df31 | ||
![]() |
cc72e1c2bd | ||
![]() |
d28afcc6a9 | ||
![]() |
220ed21e6e | ||
![]() |
09d465774d | ||
![]() |
dfc8f48f12 | ||
![]() |
abe2ec4686 | ||
![]() |
5333cda44e | ||
![]() |
dc88fbeb12 | ||
![]() |
c5eb7a9501 | ||
![]() |
bc969db2be | ||
![]() |
50b7646178 | ||
![]() |
3fa6b5bc9d | ||
![]() |
33e65e39e3 | ||
![]() |
0380e60590 | ||
![]() |
2918892ee2 | ||
![]() |
e43dd9f7c6 | ||
![]() |
244fd95034 | ||
![]() |
14e98e2fcb | ||
![]() |
dcf5d59109 | ||
![]() |
b95083c77e | ||
![]() |
c54c8c3ea6 | ||
![]() |
f79c67e243 | ||
![]() |
6ec7ebe439 | ||
![]() |
76fe33760d | ||
![]() |
cb549d8f43 | ||
![]() |
7b400fdcdd | ||
![]() |
ab70d918b5 | ||
![]() |
c64644b9ec | ||
![]() |
afb12eb342 | ||
![]() |
64f5ca02b3 | ||
![]() |
84bca21b0c | ||
![]() |
b6757c416f | ||
![]() |
dc9d3f2d15 | ||
![]() |
807825d74e | ||
![]() |
14c17676c6 | ||
![]() |
324519575c | ||
![]() |
fbcf26bdc9 | ||
![]() |
30ab6bd045 | ||
![]() |
2ea816b78b | ||
![]() |
56dcc57755 | ||
![]() |
a4830aff86 | ||
![]() |
621bc526be | ||
![]() |
605e1f6b9b | ||
![]() |
cb66ef7e30 | ||
![]() |
fd2f6a38c1 | ||
![]() |
229821d398 | ||
![]() |
1f2e0c444d | ||
![]() |
429baa350c | ||
![]() |
7fe62485fa | ||
![]() |
fe10c68099 | ||
![]() |
06092cfddd | ||
![]() |
9967dc5740 | ||
![]() |
b920e04c59 | ||
![]() |
baa1b5a5c9 | ||
![]() |
e84663a5c5 | ||
![]() |
1c46220c42 | ||
![]() |
889b445855 | ||
![]() |
19553a981e | ||
![]() |
15f4dbd061 | ||
![]() |
98e12c7c3e | ||
![]() |
dfad319864 | ||
![]() |
111b58f533 | ||
![]() |
9345652808 | ||
![]() |
d40163b409 | ||
![]() |
96400966d7 | ||
![]() |
db83bd4f64 | ||
![]() |
579a38d366 | ||
![]() |
0e6797c7da | ||
![]() |
c935577768 | ||
![]() |
c2b4077aa1 | ||
![]() |
ac020859f3 | ||
![]() |
238be8955b | ||
![]() |
f997b02b8f | ||
![]() |
799bb77847 | ||
![]() |
2f66792a0d | ||
![]() |
6cae8e211e | ||
![]() |
48b1b2f01d | ||
![]() |
8c45fd362a | ||
![]() |
07b13dd2b4 | ||
![]() |
45e84e1ede | ||
![]() |
725dfa764f | ||
![]() |
c221502ec9 | ||
![]() |
234f18c228 | ||
![]() |
006ad109cb | ||
![]() |
0db080154c | ||
![]() |
200311033f | ||
![]() |
3192dc81ac | ||
![]() |
b11d4bb209 | ||
![]() |
2a716244e9 | ||
![]() |
19b7ed538a | ||
![]() |
b0d9dc0b88 | ||
![]() |
e6094a59fa | ||
![]() |
e8d7e6a562 | ||
![]() |
536c2d9b2d | ||
![]() |
f15a278f3b | ||
![]() |
26b73c2ff3 | ||
![]() |
c1e08fc3c7 | ||
![]() |
b9ec6f236d | ||
![]() |
b1fff97bff | ||
![]() |
c81bc70475 | ||
![]() |
55a7ee4acf |
@@ -8,22 +8,8 @@ charset = utf-8
|
|||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = 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]
|
[*.md]
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.sexp]
|
[*.properties]
|
||||||
indent_size = 2
|
insert_final_newline = false
|
||||||
|
|
||||||
[*.yml]
|
|
||||||
indent_size = 2
|
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
# Reformat everything
|
|
||||||
f478c4ffc4fb9fc2200ec9b0bc751d047057ce81
|
|
16
.gitattributes
vendored
16
.gitattributes
vendored
@@ -1,16 +0,0 @@
|
|||||||
# Ignore changes in generated files
|
|
||||||
projects/*/src/generated/** linguist-generated
|
|
||||||
projects/common/src/testMod/resources/data/cctest/structures/* linguist-generated
|
|
||||||
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
*.gradle eol=lf diff=java
|
|
||||||
*.java eol=lf diff=java
|
|
||||||
*.kt eol=lf diff=java
|
|
||||||
*.lua eol=lf
|
|
||||||
*.md eol=lf diff=markdown
|
|
||||||
*.txt eol=lf
|
|
||||||
|
|
||||||
*.png binary
|
|
||||||
*.jar binary
|
|
||||||
*.dfpwm binary
|
|
15
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report some misbehaviour in the mod
|
||||||
|
labels: bug
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
## Before reporting
|
||||||
|
- Search for the bug on the issue tracker. Make sure to look at closed issues too!
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Useful information to include:
|
||||||
|
- Minecraft version
|
||||||
|
- CC: Tweaked version
|
||||||
|
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.
|
34
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
34
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -1,34 +0,0 @@
|
|||||||
name: Bug report
|
|
||||||
description: Report some misbehaviour in the mod
|
|
||||||
labels: [ bug ]
|
|
||||||
body:
|
|
||||||
- type: dropdown
|
|
||||||
id: mc-version
|
|
||||||
attributes:
|
|
||||||
label: Minecraft Version
|
|
||||||
description: What version of Minecraft are you using?
|
|
||||||
options:
|
|
||||||
- 1.16.x
|
|
||||||
- 1.18.x
|
|
||||||
- 1.19.x
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: version
|
|
||||||
attributes:
|
|
||||||
label: Version
|
|
||||||
description: "What version of CC: Tweaked are you using?"
|
|
||||||
placeholder: "e.g. 1.96.0"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: details
|
|
||||||
attributes:
|
|
||||||
label: Details
|
|
||||||
description: |
|
|
||||||
Description of the bug. Please include the following:
|
|
||||||
- Logs: These will be located in the `logs/` directory of your Minecraft
|
|
||||||
instance. Please upload them as a gist or directly into this editor.
|
|
||||||
- Detailed reproduction steps: sometimes I can spot a bug pretty easily,
|
|
||||||
but often it's much more obscure. The more information I have to help
|
|
||||||
reproduce it, the quicker it'll get fixed.
|
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: GitHub Discussions
|
|
||||||
url: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
|
||||||
about: Ask questions on GitHub Discussions.
|
|
4
.github/ISSUE_TEMPLATE/something_else.md
vendored
4
.github/ISSUE_TEMPLATE/something_else.md
vendored
@@ -1,4 +0,0 @@
|
|||||||
---
|
|
||||||
name: Something else
|
|
||||||
about: An issue about something else.
|
|
||||||
---
|
|
17
.github/matchers/checkstyle.json
vendored
17
.github/matchers/checkstyle.json
vendored
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"problemMatcher": [
|
|
||||||
{
|
|
||||||
"owner": "checkstyle",
|
|
||||||
"pattern": [
|
|
||||||
{
|
|
||||||
"regexp": "^([a-z]+) ([\\w./-]+):(\\d+):(\\d+): (.*)$",
|
|
||||||
"severity": 1,
|
|
||||||
"file": 2,
|
|
||||||
"line": 3,
|
|
||||||
"column": 4,
|
|
||||||
"message": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
18
.github/matchers/illuaminate.json
vendored
18
.github/matchers/illuaminate.json
vendored
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"problemMatcher": [
|
|
||||||
{
|
|
||||||
"owner": "illuaminate",
|
|
||||||
"severity": "warning",
|
|
||||||
"pattern": [
|
|
||||||
{
|
|
||||||
"regexp": "^([\\w./-]+):\\[(\\d+):(\\d+)\\-(?:\\d+):(?:\\d+)\\]: (.*) \\[([a-z:-]+)\\]$",
|
|
||||||
"file": 1,
|
|
||||||
"line": 2,
|
|
||||||
"column": 3,
|
|
||||||
"message": 4,
|
|
||||||
"code": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
15
.github/matchers/junit.json
vendored
15
.github/matchers/junit.json
vendored
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"problemMatcher": [
|
|
||||||
{
|
|
||||||
"owner": "junit",
|
|
||||||
"pattern": [
|
|
||||||
{
|
|
||||||
"regexp": "^## ([\\w./-]+):(\\d+): (.*)$",
|
|
||||||
"file": 1,
|
|
||||||
"line": 2,
|
|
||||||
"message": 3
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
113
.github/workflows/main-ci.yml
vendored
113
.github/workflows/main-ci.yml
vendored
@@ -3,96 +3,33 @@ name: Build
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repository
|
- uses: actions/checkout@v2
|
||||||
uses: actions/checkout@v3
|
- name: Checkout submodules
|
||||||
|
run: git submodule update --init --recursive
|
||||||
|
|
||||||
- name: Set up Java
|
- name: Set up Java 8
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 8
|
||||||
distribution: 'temurin'
|
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Cache gradle dependencies
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
path: ~/.gradle/caches
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('gradle.properties') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
|
||||||
- name: Disable Gradle daemon
|
- name: Build with Gradle
|
||||||
run: |
|
run: ./gradlew build --no-daemon || ./gradlew build --no-daemon
|
||||||
mkdir -p ~/.gradle
|
|
||||||
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
|
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Upload Jar
|
||||||
run: |
|
uses: actions/upload-artifact@v1
|
||||||
./gradlew assemble || ./gradlew assemble
|
with:
|
||||||
./gradlew downloadAssets || ./gradlew downloadAssets
|
name: cc-restiched
|
||||||
./gradlew build
|
path: build/libs
|
||||||
|
|
||||||
- name: Run client tests
|
|
||||||
run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
|
|
||||||
# These are a little flaky on GH actions: its useful to run them, but don't break the build.
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Prepare Jars
|
|
||||||
run: |
|
|
||||||
# Find the main jar and append the git hash onto it.
|
|
||||||
mkdir -p jars
|
|
||||||
find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
|
|
||||||
|
|
||||||
- name: Upload Jar
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: CC-Tweaked
|
|
||||||
path: ./jars
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
uses: codecov/codecov-action@v3
|
|
||||||
|
|
||||||
- name: Parse test reports
|
|
||||||
run: ./tools/parse-reports.py
|
|
||||||
if: ${{ failure() }}
|
|
||||||
|
|
||||||
- name: Run linters
|
|
||||||
uses: pre-commit/action@v3.0.0
|
|
||||||
|
|
||||||
build-core:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- name: Windows
|
|
||||||
uses: windows-latest
|
|
||||||
|
|
||||||
- name: macOS
|
|
||||||
uses: macos-latest
|
|
||||||
|
|
||||||
name: Test on ${{ matrix.name }}
|
|
||||||
runs-on: ${{ matrix.uses }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Clone repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Java
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: 17
|
|
||||||
distribution: 'temurin'
|
|
||||||
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/gradle-build-action@v2
|
|
||||||
with:
|
|
||||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: |
|
|
||||||
./gradlew --configure-on-demand :core:test
|
|
||||||
|
|
||||||
- name: Parse test reports
|
|
||||||
run: python3 ./tools/parse-reports.py
|
|
||||||
if: ${{ failure() }}
|
|
||||||
|
19
.github/workflows/make-doc.sh
vendored
19
.github/workflows/make-doc.sh
vendored
@@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
DEST="${GITHUB_REF#refs/*/}"
|
|
||||||
echo "Uploading docs to https://tweaked.cc/$DEST"
|
|
||||||
|
|
||||||
# Setup ssh key
|
|
||||||
mkdir -p "$HOME/.ssh/"
|
|
||||||
echo "$SSH_KEY" > "$HOME/.ssh/key"
|
|
||||||
chmod 600 "$HOME/.ssh/key"
|
|
||||||
|
|
||||||
# And upload
|
|
||||||
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
|
|
||||||
"$GITHUB_WORKSPACE/projects/web/build/site/" \
|
|
||||||
"$SSH_USER@$SSH_HOST:/$DEST"
|
|
||||||
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
|
|
||||||
"$GITHUB_WORKSPACE/projects/common-api/build/docs/javadoc/" \
|
|
||||||
"$SSH_USER@$SSH_HOST:/$DEST/javadoc"
|
|
40
.github/workflows/make-doc.yml
vendored
40
.github/workflows/make-doc.yml
vendored
@@ -1,40 +0,0 @@
|
|||||||
name: Build documentation
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- mc-1.19.x
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
make_doc:
|
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Clone repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Java
|
|
||||||
uses: actions/setup-java@v1
|
|
||||||
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: Build with Gradle
|
|
||||||
run: ./gradlew compileJava --no-daemon || ./gradlew compileJava --no-daemon
|
|
||||||
|
|
||||||
- name: Generate documentation
|
|
||||||
run: ./gradlew docWebsite :common-api:javadoc --no-daemon
|
|
||||||
|
|
||||||
- name: Upload documentation
|
|
||||||
run: .github/workflows/make-doc.sh 2> /dev/null
|
|
||||||
env:
|
|
||||||
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
||||||
SSH_USER: ${{ secrets.SSH_USER }}
|
|
||||||
SSH_HOST: ${{ secrets.SSH_HOST }}
|
|
||||||
SSH_PORT: ${{ secrets.SSH_PORT }}
|
|
23
.gitignore
vendored
23
.gitignore
vendored
@@ -2,17 +2,17 @@
|
|||||||
/classes
|
/classes
|
||||||
/logs
|
/logs
|
||||||
/build
|
/build
|
||||||
/projects/*/logs
|
|
||||||
/projects/*/build
|
|
||||||
/buildSrc/build
|
|
||||||
/out
|
/out
|
||||||
/doc/out/
|
|
||||||
/node_modules
|
|
||||||
.jqwik-database
|
|
||||||
|
|
||||||
# Runtime directories
|
# Runtime directories
|
||||||
/run
|
/run
|
||||||
/projects/*/run
|
/run-*
|
||||||
|
/test-files
|
||||||
|
|
||||||
|
# Autogenerated by IDE
|
||||||
|
/bin
|
||||||
|
/.settings
|
||||||
|
.classpath
|
||||||
|
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
@@ -20,11 +20,4 @@
|
|||||||
.idea
|
.idea
|
||||||
.gradle
|
.gradle
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
|
.project
|
||||||
/.classpath
|
|
||||||
/.project
|
|
||||||
/.settings
|
|
||||||
/.vscode
|
|
||||||
*.launch
|
|
||||||
|
|
||||||
/projects/*/src/generated/resources/.cache
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "src/main/resources/resourcepacks/overhaul"]
|
||||||
|
path = src/main/resources/resourcepacks/overhaul
|
||||||
|
url = https://github.com/3prm3/cc-pack
|
22
.gitpod.yml
22
.gitpod.yml
@@ -1,22 +0,0 @@
|
|||||||
image:
|
|
||||||
file: config/gitpod/Dockerfile
|
|
||||||
|
|
||||||
ports:
|
|
||||||
- port: 25565
|
|
||||||
onOpen: notify
|
|
||||||
|
|
||||||
vscode:
|
|
||||||
extensions:
|
|
||||||
- eamodio.gitlens
|
|
||||||
- github.vscode-pull-request-github
|
|
||||||
- ms-azuretools.vscode-docker
|
|
||||||
- redhat.java
|
|
||||||
- richardwillis.vscode-gradle
|
|
||||||
- vscjava.vscode-java-debug
|
|
||||||
- vscode.github
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Setup pre-commit hool
|
|
||||||
init: pre-commit install --allow-missing-config
|
|
||||||
- name: Install npm packages
|
|
||||||
init: npm ci
|
|
34
.luacheckrc
Normal file
34
.luacheckrc
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
std = "max"
|
||||||
|
|
||||||
|
ignore = {
|
||||||
|
-- Allow access to undefined globals or their fields. In the future we'll
|
||||||
|
-- define all of CC's globals within this file
|
||||||
|
'113', '143',
|
||||||
|
|
||||||
|
-- FIXME: Ignore unused arguments and loop variables
|
||||||
|
'212', '213',
|
||||||
|
|
||||||
|
-- Disable line is too long for now. It would be possible to clean
|
||||||
|
-- this up in the future.
|
||||||
|
'631',
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Only run the linter on ROM and bios for now, as the treasure disks
|
||||||
|
-- are largely unsupported.
|
||||||
|
include_files = {
|
||||||
|
'src/main/resources/assets/computercraft/lua/rom',
|
||||||
|
'src/main/resources/assets/computercraft/lua/bios.lua'
|
||||||
|
}
|
||||||
|
|
||||||
|
files['src/main/resources/assets/computercraft/lua/bios.lua'] = {
|
||||||
|
-- Allow declaring and mutating globals
|
||||||
|
allow_defined_top = true,
|
||||||
|
ignore = { '112', '121', '122', '131', '142' },
|
||||||
|
}
|
||||||
|
|
||||||
|
files['src/main/resources/assets/computercraft/lua/rom/apis'] = {
|
||||||
|
-- APIs may define globals on the top level. We'll ignore unused globals,
|
||||||
|
-- as obviously they may be used outside that API.
|
||||||
|
allow_defined_top = true,
|
||||||
|
ignore = { '131' },
|
||||||
|
}
|
@@ -1,54 +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: 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
|
|
||||||
)
|
|
14
.travis.yml
Normal file
14
.travis.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
language: java
|
||||||
|
|
||||||
|
script: ./gradlew build --no-daemon
|
||||||
|
|
||||||
|
before_cache:
|
||||||
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
|
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.gradle/caches/
|
||||||
|
- $HOME/.gradle/wrapper/s
|
||||||
|
|
||||||
|
jdk:
|
||||||
|
- oraclejdk8
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"java.configuration.updateBuildConfiguration": "automatic"
|
||||||
|
}
|
@@ -1,133 +0,0 @@
|
|||||||
|
|
||||||
# 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
|
|
105
CONTRIBUTING.md
105
CONTRIBUTING.md
@@ -1,105 +0,0 @@
|
|||||||
# Contributing to CC: Tweaked
|
|
||||||
As with many open source projects, CC: Tweaked thrives on contributions from other people! This document (hopefully)
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Translations
|
|
||||||
Translations are managed through [Weblate], an online interface for managing language strings. This is synced
|
|
||||||
automatically with GitHub, so please don't submit PRs adding/changing translations!
|
|
||||||
|
|
||||||
## Setting up a development environment
|
|
||||||
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
|
|
||||||
|
|
||||||
- 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].
|
|
||||||
|
|
||||||
- Download CC: Tweaked's source code:
|
|
||||||
```
|
|
||||||
git clone https://github.com/cc-tweaked/CC-Tweaked.git
|
|
||||||
cd CC-Tweaked
|
|
||||||
```
|
|
||||||
|
|
||||||
- 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!
|
|
||||||
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
## 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]!
|
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
You'll first need to [set up a development environment as above](#setting-up-a-development-environment).
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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!
|
|
||||||
|
|
||||||
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."
|
|
||||||
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
|
|
||||||
[checkstyle]: https://checkstyle.org/
|
|
||||||
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
|
|
||||||
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
|
|
||||||
[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
|
|
14
LICENSE
14
LICENSE
@@ -19,14 +19,14 @@ Mod: The mod code designated by the present license, in source form, binary
|
|||||||
form, as obtained standalone, as part of a wider distribution or resulting from
|
form, as obtained standalone, as part of a wider distribution or resulting from
|
||||||
the compilation of the original or modified sources.
|
the compilation of the original or modified sources.
|
||||||
|
|
||||||
Dependency: Code required for the mod to work properly. This includes
|
Dependency: Code required for the mod to work properly. This includes
|
||||||
dependencies required to compile the code as well as any file or modification
|
dependencies required to compile the code as well as any file or modification
|
||||||
that is explicitly or implicitly required for the mod to be working.
|
that is explicitly or implicitly required for the mod to be working.
|
||||||
|
|
||||||
1. Scope
|
1. Scope
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The present license is granted to any user of the mod. As a prerequisite,
|
The present license is granted to any user of the mod. As a prerequisite,
|
||||||
a user must own a legally acquired copy of Minecraft
|
a user must own a legally acquired copy of Minecraft
|
||||||
|
|
||||||
2. Liability
|
2. Liability
|
||||||
@@ -41,13 +41,13 @@ or misuse of this mod fall on the user.
|
|||||||
3. Play rights
|
3. Play rights
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
The user is allowed to install this mod on a Minecraft client or server and to play
|
The user is allowed to install this mod on a Minecraft client or server and to play
|
||||||
without restriction.
|
without restriction.
|
||||||
|
|
||||||
4. Modification rights
|
4. Modification rights
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
The user has the right to decompile the source code, look at either the
|
The user has the right to decompile the source code, look at either the
|
||||||
decompiled version or the original source code, and to modify it.
|
decompiled version or the original source code, and to modify it.
|
||||||
|
|
||||||
5. Distribution of original or modified copy rights
|
5. Distribution of original or modified copy rights
|
||||||
@@ -61,10 +61,10 @@ include:
|
|||||||
- patch to its source or binary files
|
- patch to its source or binary files
|
||||||
- any copy of a portion of its binary source files
|
- any copy of a portion of its binary source files
|
||||||
|
|
||||||
The user is allowed to redistribute this mod partially, in totality, or
|
The user is allowed to redistribute this mod partially, in totality, or
|
||||||
included in a distribution.
|
included in a distribution.
|
||||||
|
|
||||||
When distributing binary files, the user must provide means to obtain its
|
When distributing binary files, the user must provide means to obtain its
|
||||||
entire set of sources or modified sources at no cost.
|
entire set of sources or modified sources at no cost.
|
||||||
|
|
||||||
All distributions of this mod must remain licensed under the CCPL.
|
All distributions of this mod must remain licensed under the CCPL.
|
||||||
@@ -92,7 +92,7 @@ must be made available at no cost and remain licensed under the CCPL.
|
|||||||
---------------
|
---------------
|
||||||
|
|
||||||
If you choose to contribute code or assets to be included in this mod, you
|
If you choose to contribute code or assets to be included in this mod, you
|
||||||
agree that, if added to to the main repository at
|
agree that, if added to to the main repository at
|
||||||
https://github.com/dan200/ComputerCraft, your contributions will be covered by
|
https://github.com/dan200/ComputerCraft, your contributions will be covered by
|
||||||
this license, and that Daniel Ratcliffe will retain the right to re-license the
|
this license, and that Daniel Ratcliffe will retain the right to re-license the
|
||||||
mod, including your contributions, in part or in whole, under other licenses.
|
mod, including your contributions, in part or in whole, under other licenses.
|
||||||
|
102
README.md
102
README.md
@@ -1,66 +1,52 @@
|
|||||||
# 
|
# CC:Restitched Patchwork
|
||||||
[](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status") [][CurseForge]
|
# This is a Work In Progress Port
|
||||||
|
*it runs and works-ish*
|
||||||
|
<img src="logo.png" alt="CC: Restitched" width="100%"/>
|
||||||
|
|
||||||
CC: Tweaked is a mod for Minecraft which adds programmable computers, turtles and more to the game. A fork of the
|
[](https://github.com/o-iM-nI/cc-restitched/actions "Current build status") [](https://www.curseforge.com/minecraft/mc-mods/cc-restitched-updated "Download CC: Restitched on CurseForge")
|
||||||
much-beloved [ComputerCraft], it continues its legacy with better performance, stability, and a wealth of new features.
|
|
||||||
|
|
||||||
CC: Tweaked can be installed from [CurseForge] or [Modrinth]. It requires the [Minecraft Forge][forge] mod loader, but
|
This is an fork of [Zundrel/cc-tweaked-fabric](https://github.com/Zundrel/cc-tweaked-fabric) who's goal was to port [SquidDev-CC/CC-Tweaked](https://github.com/SquidDev-CC/CC-Tweaked) to fabric. I picked up maintaining the mod because the team working on Zundrel's fork, admitted they had gotten lazy so I picked it up to make it up to snuff with CC:T
|
||||||
[versions are available for Fabric][ccrestitched].
|
|
||||||
|
## Resource Packs
|
||||||
|
This mod includes textures that are more in-line with the style of Mojang's new texture-artist, Jappa. If you prefer the original textures, enable the "Classic" resource pack provided by the mod.
|
||||||
|
|
||||||
|
We also have a second resourcepack made by [3prm3](https://github.com/3prm3), it features a complete overhaul and can be enabled by enabling the `overhaul` resource pack, go check out his resource pack over here!
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/3prm3/cc-pack/main/pack.png" alt="CC: Restitched" width="16" height="16"/> [3prm3/cc-pack](https://github.com/3prm3/cc-pack/)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you want to get started
|
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. In order to start helping develop CC:R there are a few rules
|
||||||
developing the mod, [check out the instructions here](CONTRIBUTING.md#developing).
|
1) Any updates that port commits from CC:T, ***MUST*** follow the format defined in [patchwork.md](patchwork.md) otherwise they will not be accepted,
|
||||||
|
* Commit Message must be the same as it is in CC:T,
|
||||||
|
* patchwork.md must be updated in the following format
|
||||||
|
> Comments, optional but useful if you had to do something differently than in CC:T (outside of Fabric/forge differences
|
||||||
|
>
|
||||||
|
> \`\`\`
|
||||||
|
>
|
||||||
|
>commitID
|
||||||
|
>
|
||||||
|
> commit title
|
||||||
|
>
|
||||||
|
> commit desc
|
||||||
|
>
|
||||||
|
> \`\`\`
|
||||||
|
2) Follow the fabirc programming guidelines as close as possible. This means you have to use `loom` mappings,
|
||||||
|
3) You cannot intentionally implement bugs and security vulnerabilities
|
||||||
|
4) Unless the commit is a "patchwork" compliant commit, (IE: taken from CC:T), the lua code is off limits,s
|
||||||
|
## Bleeding Edge Builds
|
||||||
|
Bleeding edge builds can be found [here](https://github.com/Merith-TK/cc-restitched/actions) at github actions to simplify things
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
|
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about ComputerCraft, here is the [Forum](https://forums.computercraft.cc/) and the [Discord](https://discord.gg/H2UyJXe)
|
||||||
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").
|
## Known Issues
|
||||||
|
Main Known issue
|
||||||
|
* Mods that add blocks that can be used as peripherals for CC:T On forge, dont work with CC:R.
|
||||||
|
* This is because of the differences between forge and fabric, and that mod devs, to my knowledge have not agreed upon a standard method in which to implement cross compatibility between mods,
|
||||||
|
* [Fixed (d10f297c): please report if bug persists]</br> ~~Storage Peripherals throw a java "StackOverflowError" when using `pushItems()`,~~
|
||||||
|
* ~~Work around, you are probably using `pushItems(chest, 1)` or similar. please use `pushItems(chest, 1, nil, 1)`.~~
|
||||||
|
|
||||||
## Using
|
## Known Working mods that add Peripherals
|
||||||
CC: Tweaked is hosted on my maven repo, and so is relatively simple to depend on. You may wish to add a soft (or hard)
|
* Please let me know of other mods that work with this one
|
||||||
dependency in your `mods.toml` file, with the appropriate version bounds, to ensure that API functionality you depend
|
* Better End
|
||||||
on is present.
|
* Better Nether
|
||||||
|
|
||||||
```groovy
|
|
||||||
repositories {
|
|
||||||
maven {
|
|
||||||
url "https://squiddev.cc/maven/"
|
|
||||||
content {
|
|
||||||
includeGroup("cc.tweaked")
|
|
||||||
includeModule("org.squiddev", "Cobalt")
|
|
||||||
includeModule("fuzs.forgeconfigapiport", "forgeconfigapiport-fabric")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
// Vanilla (i.e. for multi-loader systems)
|
|
||||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
|
|
||||||
|
|
||||||
// Forge Gradle
|
|
||||||
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
|
|
||||||
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
|
|
||||||
|
|
||||||
// Fabric Loom
|
|
||||||
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")
|
|
||||||
modRuntimeOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric:$cctVersion")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
|
|
||||||
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
|
|
||||||
exposing more features.
|
|
||||||
|
|
||||||
We bundle the API sources with the jar, so documentation should be easily viewable within your editor. Alternatively,
|
|
||||||
the generated documentation [can be browsed online](https://tweaked.cc/javadoc/).
|
|
||||||
|
|
||||||
[computercraft]: https://github.com/dan200/ComputerCraft "ComputerCraft on GitHub"
|
|
||||||
[curseforge]: https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked from CurseForge"
|
|
||||||
[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"
|
|
||||||
|
124
build.gradle
Normal file
124
build.gradle
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
plugins {
|
||||||
|
id 'fabric-loom' version '0.6-SNAPSHOT'
|
||||||
|
id 'maven-publish'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
|
||||||
|
version = mod_version
|
||||||
|
|
||||||
|
group = "dan200.computercraft"
|
||||||
|
archivesBaseName = "cc-restiched"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
jcenter()
|
||||||
|
maven {
|
||||||
|
name "SquidDev"
|
||||||
|
url "https://squiddev.cc/maven"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compile.extendsFrom shade
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
minecraft "com.mojang:minecraft:${mc_version}"
|
||||||
|
mappings "net.fabricmc:yarn:${mc_version}+build.${mappings_version}:v2"
|
||||||
|
modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}"
|
||||||
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
||||||
|
|
||||||
|
compile 'com.electronwill.night-config:json:3.6.0'
|
||||||
|
|
||||||
|
modImplementation "me.shedaniel.cloth:config-2:${cloth_config_version}"
|
||||||
|
modImplementation "io.github.prospector:modmenu:${modmenu_version}"
|
||||||
|
|
||||||
|
modApi "me.shedaniel.cloth.api:cloth-utils-v1:${project.cloth_api_version}"
|
||||||
|
include "me.shedaniel.cloth.api:cloth-utils-v1:${project.cloth_api_version}"
|
||||||
|
|
||||||
|
implementation "blue.endless:jankson:${jankson_version}"
|
||||||
|
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||||
|
|
||||||
|
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
|
||||||
|
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
|
||||||
|
|
||||||
|
include "me.shedaniel.cloth:config-2:${cloth_config_version}"
|
||||||
|
include "blue.endless:jankson:${jankson_version}"
|
||||||
|
include 'javax.vecmath:vecmath:1.5.2'
|
||||||
|
|
||||||
|
compile 'javax.vecmath:vecmath:1.5.2'
|
||||||
|
|
||||||
|
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
modRuntime "me.shedaniel:RoughlyEnoughItems-api:5.8.9"
|
||||||
|
modRuntime "me.shedaniel:RoughlyEnoughItems:5.8.9"
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
exclude 'dan200/computercraft/shared/integration'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
inputs.property "version", project.version
|
||||||
|
|
||||||
|
from(sourceSets.main.resources.srcDirs) {
|
||||||
|
include "fabric.mod.json"
|
||||||
|
expand "version": project.version
|
||||||
|
}
|
||||||
|
|
||||||
|
from(sourceSets.main.resources.srcDirs) {
|
||||||
|
exclude "fabric.mod.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
||||||
|
// this fixes some edge cases with special characters not displaying correctly
|
||||||
|
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
||||||
|
// if it is present.
|
||||||
|
// If you remove this task, sources will not be generated.
|
||||||
|
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||||
|
classifier = "sources"
|
||||||
|
from sourceSets.main.allSource
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
from "LICENSE"
|
||||||
|
|
||||||
|
from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure the maven publication
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenJava(MavenPublication) {
|
||||||
|
// add all the jars that should be included when publishing to maven
|
||||||
|
artifact(remapJar) {
|
||||||
|
builtBy remapJar
|
||||||
|
}
|
||||||
|
artifact(sourcesJar) {
|
||||||
|
builtBy remapSourcesJar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// select the repositories you want to publish to
|
||||||
|
repositories {
|
||||||
|
// uncomment to publish to the local maven
|
||||||
|
// mavenLocal()
|
||||||
|
}
|
||||||
|
}
|
@@ -1,52 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@@ -1,70 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
dependencyResolutionManagement {
|
|
||||||
versionCatalogs {
|
|
||||||
create("libs") {
|
|
||||||
from(files("../gradle/libs.versions.toml"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,66 +0,0 @@
|
|||||||
/** Default configuration for Fabric projects. */
|
|
||||||
|
|
||||||
import cc.tweaked.gradle.CCTweakedExtension
|
|
||||||
import cc.tweaked.gradle.CCTweakedPlugin
|
|
||||||
import cc.tweaked.gradle.IdeaRunConfigurations
|
|
||||||
import cc.tweaked.gradle.MinecraftConfigurations
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
`java-library`
|
|
||||||
id("fabric-loom")
|
|
||||||
id("io.github.juuxel.loom-quiltflower")
|
|
||||||
id("cc-tweaked.java-convention")
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.apply(CCTweakedPlugin::class.java)
|
|
||||||
|
|
||||||
val mcVersion: String by extra
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven("https://maven.parchmentmc.org/") {
|
|
||||||
name = "Parchment"
|
|
||||||
content {
|
|
||||||
includeGroup("org.parchmentmc.data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loom {
|
|
||||||
splitEnvironmentSourceSets()
|
|
||||||
splitModDependencies.set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
MinecraftConfigurations.setup(project)
|
|
||||||
|
|
||||||
extensions.configure(CCTweakedExtension::class.java) {
|
|
||||||
linters(minecraft = true, loader = "fabric")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
|
||||||
|
|
||||||
minecraft("com.mojang:minecraft:$mcVersion")
|
|
||||||
mappings(
|
|
||||||
loom.layered {
|
|
||||||
officialMojangMappings()
|
|
||||||
parchment(
|
|
||||||
project.dependencies.create(
|
|
||||||
group = "org.parchmentmc.data",
|
|
||||||
name = "parchment-${libs.findVersion("parchmentMc").get()}",
|
|
||||||
version = libs.findVersion("parchment").get().toString(),
|
|
||||||
ext = "zip",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
modImplementation(libs.findLibrary("fabric-loader").get())
|
|
||||||
modImplementation(libs.findLibrary("fabric-api").get())
|
|
||||||
|
|
||||||
// Depend on error prone annotations to silence a lot of compile warnings.
|
|
||||||
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.ideaSyncTask {
|
|
||||||
doLast { IdeaRunConfigurations(project).patch() }
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
/** Default configuration for Forge projects. */
|
|
||||||
|
|
||||||
import cc.tweaked.gradle.CCTweakedExtension
|
|
||||||
import cc.tweaked.gradle.CCTweakedPlugin
|
|
||||||
import cc.tweaked.gradle.IdeaRunConfigurations
|
|
||||||
import cc.tweaked.gradle.MinecraftConfigurations
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("cc-tweaked.java-convention")
|
|
||||||
id("net.minecraftforge.gradle")
|
|
||||||
id("org.parchmentmc.librarian.forgegradle")
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.apply(CCTweakedPlugin::class.java)
|
|
||||||
|
|
||||||
val mcVersion: String by extra
|
|
||||||
|
|
||||||
minecraft {
|
|
||||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
|
||||||
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
|
|
||||||
|
|
||||||
accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
|
|
||||||
}
|
|
||||||
|
|
||||||
MinecraftConfigurations.setup(project)
|
|
||||||
|
|
||||||
extensions.configure(CCTweakedExtension::class.java) {
|
|
||||||
linters(minecraft = true, loader = "forge")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
|
||||||
"minecraft"("net.minecraftforge:forge:$mcVersion-${libs.findVersion("forge").get()}")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.configureEach {
|
|
||||||
// genIntellijRuns isn't registered until much later, so we need this silly hijinks.
|
|
||||||
if (name == "genIntellijRuns") doLast { IdeaRunConfigurations(project).patch() }
|
|
||||||
}
|
|
@@ -1,60 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
@@ -1,198 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
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()}"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,45 +0,0 @@
|
|||||||
import org.gradle.kotlin.dsl.`maven-publish`
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
`java-library`
|
|
||||||
`maven-publish`
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
register<MavenPublication>("maven") {
|
|
||||||
artifactId = base.archivesName.get()
|
|
||||||
from(components["java"])
|
|
||||||
|
|
||||||
pom {
|
|
||||||
name.set("CC: Tweaked")
|
|
||||||
description.set("CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.")
|
|
||||||
url.set("https://github.com/cc-tweaked/CC-Tweaked")
|
|
||||||
|
|
||||||
scm {
|
|
||||||
url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
|
|
||||||
}
|
|
||||||
|
|
||||||
issueManagement {
|
|
||||||
system.set("github")
|
|
||||||
url.set("https://github.com/cc-tweaked/CC-Tweaked/issues")
|
|
||||||
}
|
|
||||||
|
|
||||||
licenses {
|
|
||||||
license {
|
|
||||||
name.set("ComputerCraft Public License, Version 1.0")
|
|
||||||
url.set("https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven("https://squiddev.cc/maven") {
|
|
||||||
name = "SquidDev"
|
|
||||||
|
|
||||||
credentials(PasswordCredentials::class)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,31 +0,0 @@
|
|||||||
/** Default configuration for non-modloader-specific Minecraft projects. */
|
|
||||||
|
|
||||||
import cc.tweaked.gradle.CCTweakedExtension
|
|
||||||
import cc.tweaked.gradle.CCTweakedPlugin
|
|
||||||
import cc.tweaked.gradle.MinecraftConfigurations
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("cc-tweaked.java-convention")
|
|
||||||
id("org.spongepowered.gradle.vanilla")
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.apply(CCTweakedPlugin::class.java)
|
|
||||||
|
|
||||||
val mcVersion: String by extra
|
|
||||||
|
|
||||||
minecraft {
|
|
||||||
version(mcVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
|
||||||
|
|
||||||
// Depend on error prone annotations to silence a lot of compile warnings.
|
|
||||||
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
|
|
||||||
}
|
|
||||||
|
|
||||||
MinecraftConfigurations.setup(project)
|
|
||||||
|
|
||||||
extensions.configure(CCTweakedExtension::class.java) {
|
|
||||||
linters(minecraft = true, loader = null)
|
|
||||||
}
|
|
@@ -1,268 +0,0 @@
|
|||||||
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"))
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,64 +0,0 @@
|
|||||||
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 targetting 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")
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,72 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
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>
|
|
||||||
}
|
|
@@ -1,117 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,170 +0,0 @@
|
|||||||
package cc.tweaked.gradle
|
|
||||||
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.logging.Logging
|
|
||||||
import org.w3c.dom.Attr
|
|
||||||
import org.w3c.dom.Document
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
import org.xml.sax.InputSource
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
|
||||||
import javax.xml.transform.TransformerFactory
|
|
||||||
import javax.xml.transform.dom.DOMSource
|
|
||||||
import javax.xml.transform.stream.StreamResult
|
|
||||||
import javax.xml.xpath.XPathConstants
|
|
||||||
import javax.xml.xpath.XPathFactory
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Patches up run configurations from ForgeGradle and Loom.
|
|
||||||
*
|
|
||||||
* Would be good to PR some (or all) of these changes upstream at some point.
|
|
||||||
*
|
|
||||||
* @see net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask
|
|
||||||
* @see net.minecraftforge.gradle.common.util.runs.IntellijRunGenerator
|
|
||||||
*/
|
|
||||||
internal class IdeaRunConfigurations(project: Project) {
|
|
||||||
private val rootProject = project.rootProject
|
|
||||||
|
|
||||||
private val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
|
||||||
private val xpath = XPathFactory.newInstance().newXPath()
|
|
||||||
private val writer = TransformerFactory.newInstance().newTransformer()
|
|
||||||
|
|
||||||
private val ideaDir = rootProject.file(".idea/")
|
|
||||||
private val buildDir: Lazy<String?> = lazy {
|
|
||||||
val ideaMisc = ideaDir.resolve("misc.xml")
|
|
||||||
|
|
||||||
try {
|
|
||||||
val doc = Files.newBufferedReader(ideaMisc.toPath()).use {
|
|
||||||
documentBuilder.parse(InputSource(it))
|
|
||||||
}
|
|
||||||
val node =
|
|
||||||
xpath.evaluate("//component[@name=\"ProjectRootManager\"]/output", doc, XPathConstants.NODE) as Node
|
|
||||||
val attr = node.attributes.getNamedItem("url") as Attr
|
|
||||||
attr.value.removePrefix("file://")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
LOGGER.error("Failed to find root directory", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun patch() = synchronized(LOCK) {
|
|
||||||
val runConfigDir = ideaDir.resolve("runConfigurations")
|
|
||||||
if (!runConfigDir.isDirectory) return
|
|
||||||
|
|
||||||
Files.list(runConfigDir.toPath()).use {
|
|
||||||
for (configuration in it) {
|
|
||||||
val filename = configuration.fileName.toString();
|
|
||||||
when {
|
|
||||||
filename.endsWith("_fabric.xml") -> patchFabric(configuration)
|
|
||||||
filename.startsWith("forge_") && filename.endsWith(".xml") -> patchForge(configuration)
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun patchFabric(path: Path) = withXml(path) {
|
|
||||||
setXml("//configuration", "folderName") { "Fabric" }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun patchForge(path: Path) = withXml(path) {
|
|
||||||
val configId = path.fileName.toString().removePrefix("forge_").removeSuffix(".xml")
|
|
||||||
val sourceSet = forgeConfigs[configId]
|
|
||||||
if (sourceSet == null) {
|
|
||||||
LOGGER.error("[{}] Cannot map run configuration to a known source set", path)
|
|
||||||
return@withXml
|
|
||||||
}
|
|
||||||
|
|
||||||
setXml("//configuration", "folderName") { "Forge" }
|
|
||||||
setXml("//configuration/module", "name") { "${rootProject.name}.forge.$sourceSet" }
|
|
||||||
|
|
||||||
if (buildDir.value == null) return@withXml
|
|
||||||
setXml("//configuration/envs/env[@name=\"MOD_CLASSES\"]", "value") { classpath ->
|
|
||||||
val classes = classpath!!.split(':')
|
|
||||||
val newClasses = mutableListOf<String>()
|
|
||||||
fun appendUnique(x: String) {
|
|
||||||
if (!newClasses.contains(x)) newClasses.add(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (entry in classes) {
|
|
||||||
if (!entry.contains("/out/")) {
|
|
||||||
appendUnique(entry)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val match = CLASSPATH_ENTRY.matchEntire(entry)
|
|
||||||
if (match != null) {
|
|
||||||
val modId = match.groups["modId"]!!.value
|
|
||||||
val proj = match.groups["proj"]!!.value
|
|
||||||
var component = match.groups["component"]!!.value
|
|
||||||
if (component == "production") component = "main"
|
|
||||||
|
|
||||||
appendUnique(forgeModEntry(modId, proj, component))
|
|
||||||
} else {
|
|
||||||
LOGGER.warn("[{}] Unknown classpath entry {}", path, entry)
|
|
||||||
appendUnique(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure common code is on the classpath
|
|
||||||
for (proj in listOf("common", "common-api")) {
|
|
||||||
for (component in listOf("main", "client")) {
|
|
||||||
appendUnique(forgeModEntry("computercraft", proj, component))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newClasses.any { it.startsWith("cctest%%") }) {
|
|
||||||
appendUnique(forgeModEntry("cctest", "core", "testFixtures"))
|
|
||||||
appendUnique(forgeModEntry("cctest", "common", "testFixtures"))
|
|
||||||
appendUnique(forgeModEntry("cctest", "common", "testMod"))
|
|
||||||
}
|
|
||||||
|
|
||||||
newClasses.joinToString(":")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun forgeModEntry(mod: String, project: String, component: String) =
|
|
||||||
"$mod%%${buildDir.value}/production/${rootProject.name}.$project.$component"
|
|
||||||
|
|
||||||
private fun LocatedDocument.setXml(xpath: String, attribute: String, value: (String?) -> String) {
|
|
||||||
val node = this@IdeaRunConfigurations.xpath.evaluate(xpath, document, XPathConstants.NODE) as Node?
|
|
||||||
if (node == null) {
|
|
||||||
LOGGER.error("[{}] Cannot find {}", path.fileName, xpath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val attr = node.attributes.getNamedItem(attribute) as Attr? ?: document.createAttribute(attribute)
|
|
||||||
val oldValue = attr.value
|
|
||||||
attr.value = value(attr.value)
|
|
||||||
node.attributes.setNamedItem(attr)
|
|
||||||
|
|
||||||
if (oldValue != attr.value) {
|
|
||||||
LOGGER.info("[{}] Setting {}@{}:\n Old: {}\n New: {}", path.fileName, xpath, attribute, oldValue, attr.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun withXml(path: Path, run: LocatedDocument.() -> Unit) {
|
|
||||||
val doc = Files.newBufferedReader(path).use { documentBuilder.parse(InputSource(it)) }
|
|
||||||
run(LocatedDocument(path, doc))
|
|
||||||
Files.newBufferedWriter(path).use { writer.transform(DOMSource(doc), StreamResult(it)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LocatedDocument(val path: Path, val document: Document)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val LOGGER = Logging.getLogger(IdeaRunConfigurations::class.java)
|
|
||||||
private val LOCK = Any()
|
|
||||||
|
|
||||||
private val CLASSPATH_ENTRY =
|
|
||||||
Regex("(?<modId>[a-z]+)%%\\\$PROJECT_DIR\\\$/projects/(?<proj>[a-z-]+)/out/(?<component>\\w+)/(?<type>[a-z]+)\$")
|
|
||||||
|
|
||||||
private val forgeConfigs = mapOf(
|
|
||||||
"runClient" to "client",
|
|
||||||
"runData" to "main",
|
|
||||||
"runGameTestServer" to "testMod",
|
|
||||||
"runServer" to "main",
|
|
||||||
"runTestClient" to "testMod",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,123 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,62 +0,0 @@
|
|||||||
package cc.tweaked.gradle
|
|
||||||
|
|
||||||
import org.gradle.api.artifacts.Dependency
|
|
||||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency
|
|
||||||
import org.gradle.api.publish.maven.MavenPublication
|
|
||||||
import org.gradle.api.specs.Spec
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A dependency in a POM file.
|
|
||||||
*/
|
|
||||||
data class MavenDependency(val groupId: String?, val artifactId: String?, val version: String?, val scope: String?)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A spec specifying which dependencies to include/exclude.
|
|
||||||
*/
|
|
||||||
class MavenDependencySpec {
|
|
||||||
private val excludeSpecs = mutableListOf<Spec<MavenDependency>>()
|
|
||||||
|
|
||||||
fun exclude(spec: Spec<MavenDependency>) {
|
|
||||||
excludeSpecs.add(spec)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exclude(dep: Dependency) {
|
|
||||||
exclude {
|
|
||||||
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
|
|
||||||
(dep.name.isNullOrEmpty() || dep.name == it.artifactId) &&
|
|
||||||
(dep.version.isNullOrEmpty() || dep.version == it.version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exclude(dep: MinimalExternalModuleDependency) {
|
|
||||||
exclude {
|
|
||||||
dep.module.group == it.groupId && dep.module.name == it.artifactId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isIncluded(dep: MavenDependency) = !excludeSpecs.any { it.isSatisfiedBy(dep) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure dependencies present in this publication's POM file.
|
|
||||||
*
|
|
||||||
* While this approach is very ugly, it's the easiest way to handle it!
|
|
||||||
*/
|
|
||||||
fun MavenPublication.mavenDependencies(action: MavenDependencySpec.() -> Unit) {
|
|
||||||
val spec = MavenDependencySpec()
|
|
||||||
action(spec)
|
|
||||||
|
|
||||||
pom.withXml {
|
|
||||||
val dependencies = XmlUtil.findChild(asNode(), "dependencies") ?: return@withXml
|
|
||||||
dependencies.children().map { it as groovy.util.Node }.forEach {
|
|
||||||
val dep = MavenDependency(
|
|
||||||
groupId = XmlUtil.findChild(it, "groupId")?.text(),
|
|
||||||
artifactId = XmlUtil.findChild(it, "artifactId")?.text(),
|
|
||||||
version = XmlUtil.findChild(it, "version")?.text(),
|
|
||||||
scope = XmlUtil.findChild(it, "scope")?.text(),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!spec.isIncluded(dep)) it.parent().remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,189 +0,0 @@
|
|||||||
package cc.tweaked.gradle
|
|
||||||
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.artifacts.Configuration
|
|
||||||
import org.gradle.api.artifacts.ModuleDependency
|
|
||||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
|
||||||
import org.gradle.api.attributes.Bundling
|
|
||||||
import org.gradle.api.attributes.Category
|
|
||||||
import org.gradle.api.attributes.LibraryElements
|
|
||||||
import org.gradle.api.attributes.Usage
|
|
||||||
import org.gradle.api.attributes.java.TargetJvmVersion
|
|
||||||
import org.gradle.api.capabilities.Capability
|
|
||||||
import org.gradle.api.plugins.BasePlugin
|
|
||||||
import org.gradle.api.plugins.JavaPluginExtension
|
|
||||||
import org.gradle.api.tasks.SourceSet
|
|
||||||
import org.gradle.api.tasks.bundling.Jar
|
|
||||||
import org.gradle.api.tasks.javadoc.Javadoc
|
|
||||||
import org.gradle.kotlin.dsl.get
|
|
||||||
import org.gradle.kotlin.dsl.named
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This sets up a separate client-only source set, and extends that and the main/common source set with additional
|
|
||||||
* metadata, to make it easier to consume jars downstream.
|
|
||||||
*/
|
|
||||||
class MinecraftConfigurations private constructor(private val project: Project) {
|
|
||||||
private val java = project.extensions.getByType(JavaPluginExtension::class.java)
|
|
||||||
private val sourceSets = java.sourceSets
|
|
||||||
private val configurations = project.configurations
|
|
||||||
private val objects = project.objects
|
|
||||||
|
|
||||||
private val main = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
|
|
||||||
private val test = sourceSets[SourceSet.TEST_SOURCE_SET_NAME]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs the initial setup of our configurations.
|
|
||||||
*/
|
|
||||||
private fun setup() {
|
|
||||||
// Define a client source set.
|
|
||||||
val client = sourceSets.maybeCreate("client")
|
|
||||||
|
|
||||||
// Ensure the client classpaths behave the same as the main ones.
|
|
||||||
configurations.named(client.compileClasspathConfigurationName) {
|
|
||||||
shouldResolveConsistentlyWith(configurations[main.compileClasspathConfigurationName])
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations.named(client.runtimeClasspathConfigurationName) {
|
|
||||||
shouldResolveConsistentlyWith(configurations[main.runtimeClasspathConfigurationName])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up an API configuration for clients (to ensure it's consistent with the main source set).
|
|
||||||
val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply {
|
|
||||||
isVisible = false
|
|
||||||
isCanBeConsumed = false
|
|
||||||
isCanBeResolved = false
|
|
||||||
}
|
|
||||||
configurations.named(client.implementationConfigurationName) { extendsFrom(clientApi) }
|
|
||||||
|
|
||||||
/*
|
|
||||||
Now add outgoing variants for the main and common source sets that we can consume downstream. This is possibly
|
|
||||||
the worst way to do things, but unfortunately the alternatives don't actually work very well:
|
|
||||||
|
|
||||||
- Just using source set outputs: This means dependencies don't propagate, which means when :fabric depends
|
|
||||||
on :fabric-api, we don't inherit the fake :common-api in IDEA.
|
|
||||||
|
|
||||||
- Having separate common/main jars: Nice in principle, but unfortunately Forge needs a separate deobf jar
|
|
||||||
task (as the original jar is obfuscated), and IDEA is not able to map its output back to a source set.
|
|
||||||
|
|
||||||
This works for now, but is incredibly brittle. It's part of the reason we can't use testFixtures inside our
|
|
||||||
MC projects, as that adds a project(self) -> test dependency, which would pull in the jar instead.
|
|
||||||
|
|
||||||
Note we register a fake client jar here. It's not actually needed, but is there to make sure IDEA has
|
|
||||||
a way to tell that client classes are needed at runtime.
|
|
||||||
|
|
||||||
I'm so sorry, deeply aware how cursed this is.
|
|
||||||
*/
|
|
||||||
setupOutgoing(main, "CommonOnly")
|
|
||||||
project.tasks.register(client.jarTaskName, Jar::class.java) {
|
|
||||||
description = "An empty jar standing in for the client classes."
|
|
||||||
group = BasePlugin.BUILD_GROUP
|
|
||||||
archiveClassifier.set("client")
|
|
||||||
}
|
|
||||||
setupOutgoing(client)
|
|
||||||
|
|
||||||
// Reset the client classpath (Loom configures it slightly differently to this) and add a main -> client
|
|
||||||
// dependency. Here we /can/ use source set outputs as we add transitive deps by patching the classpath. Nasty,
|
|
||||||
// but avoids accidentally pulling in Forge's obfuscated jar.
|
|
||||||
client.compileClasspath = client.compileClasspath + main.compileClasspath
|
|
||||||
client.runtimeClasspath = client.runtimeClasspath + main.runtimeClasspath
|
|
||||||
project.dependencies.add(client.apiConfigurationName, main.output)
|
|
||||||
|
|
||||||
// Also add client classes to the test classpath. We do the same nasty tricks as needed for main -> client.
|
|
||||||
test.compileClasspath += client.compileClasspath
|
|
||||||
test.runtimeClasspath += client.runtimeClasspath
|
|
||||||
project.dependencies.add(test.implementationConfigurationName, client.output)
|
|
||||||
|
|
||||||
// Configure some tasks to include our additional files.
|
|
||||||
project.tasks.named("javadoc", Javadoc::class.java) {
|
|
||||||
source(client.allJava)
|
|
||||||
classpath = main.compileClasspath + main.output + client.compileClasspath + client.output
|
|
||||||
}
|
|
||||||
// This are already done by Fabric, but we need it for Forge and vanilla. It shouldn't conflict at all.
|
|
||||||
project.tasks.named("jar", Jar::class.java) { from(client.output) }
|
|
||||||
project.tasks.named("sourcesJar", Jar::class.java) { from(client.allSource) }
|
|
||||||
|
|
||||||
project.extensions.configure(CCTweakedExtension::class.java) {
|
|
||||||
sourceDirectories.add(SourceSetReference.internal(client))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {
|
|
||||||
setupOutgoing("${sourceSet.apiElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_API)) {
|
|
||||||
description = "API elements for ${sourceSet.name}"
|
|
||||||
extendsFrom(configurations[sourceSet.apiConfigurationName])
|
|
||||||
}
|
|
||||||
|
|
||||||
setupOutgoing("${sourceSet.runtimeElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_RUNTIME)) {
|
|
||||||
description = "Runtime elements for ${sourceSet.name}"
|
|
||||||
extendsFrom(configurations[sourceSet.implementationConfigurationName], configurations[sourceSet.runtimeOnlyConfigurationName])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up an outgoing configuration for a specific source set. We set an additional "main" or "client" capability
|
|
||||||
* (depending on the source set name) which allows downstream projects to consume them separately (see
|
|
||||||
* [DependencyHandler.commonClasses] and [DependencyHandler.clientClasses]).
|
|
||||||
*/
|
|
||||||
private fun setupOutgoing(name: String, sourceSet: SourceSet, usage: Usage, configure: Configuration.() -> Unit) {
|
|
||||||
configurations.register(name) {
|
|
||||||
isVisible = false
|
|
||||||
isCanBeConsumed = true
|
|
||||||
isCanBeResolved = false
|
|
||||||
|
|
||||||
configure(this)
|
|
||||||
|
|
||||||
attributes {
|
|
||||||
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
|
|
||||||
attribute(Usage.USAGE_ATTRIBUTE, usage)
|
|
||||||
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
|
|
||||||
attributeProvider(
|
|
||||||
TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,
|
|
||||||
java.toolchain.languageVersion.map { it.asInt() },
|
|
||||||
)
|
|
||||||
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
|
|
||||||
}
|
|
||||||
|
|
||||||
outgoing {
|
|
||||||
capability(BasicOutgoingCapability(project, sourceSet.name))
|
|
||||||
|
|
||||||
// We have two outgoing variants here: the original jar and the classes.
|
|
||||||
artifact(project.tasks.named(sourceSet.jarTaskName))
|
|
||||||
|
|
||||||
variants.create("classes") {
|
|
||||||
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.CLASSES))
|
|
||||||
sourceSet.output.classesDirs.forEach { artifact(it) { builtBy(sourceSet.output) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun setup(project: Project) {
|
|
||||||
MinecraftConfigurations(project).setup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BasicIncomingCapability(private val module: ModuleDependency, private val name: String) : Capability {
|
|
||||||
override fun getGroup(): String = module.group!!
|
|
||||||
override fun getName(): String = "${module.name}-$name"
|
|
||||||
override fun getVersion(): String? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BasicOutgoingCapability(private val project: Project, private val name: String) : Capability {
|
|
||||||
override fun getGroup(): String = project.group.toString()
|
|
||||||
override fun getName(): String = "${project.name}-$name"
|
|
||||||
override fun getVersion(): String = project.version.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency {
|
|
||||||
val dep = create(notation) as ModuleDependency
|
|
||||||
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "client")) }
|
|
||||||
return dep
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency {
|
|
||||||
val dep = create(notation) as ModuleDependency
|
|
||||||
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "main")) }
|
|
||||||
return dep
|
|
||||||
}
|
|
@@ -1,191 +0,0 @@
|
|||||||
package cc.tweaked.gradle
|
|
||||||
|
|
||||||
import org.gradle.api.GradleException
|
|
||||||
import org.gradle.api.file.FileSystemOperations
|
|
||||||
import org.gradle.api.invocation.Gradle
|
|
||||||
import org.gradle.api.provider.Provider
|
|
||||||
import org.gradle.api.services.BuildService
|
|
||||||
import org.gradle.api.services.BuildServiceParameters
|
|
||||||
import org.gradle.api.tasks.*
|
|
||||||
import org.gradle.kotlin.dsl.getByName
|
|
||||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.function.Supplier
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [JavaExec] task for client-tests. This sets some common setup, and uses [MinecraftRunnerService] to ensure only one
|
|
||||||
* test runs at once.
|
|
||||||
*/
|
|
||||||
abstract class ClientJavaExec : JavaExec() {
|
|
||||||
private val clientRunner: Provider<MinecraftRunnerService> = MinecraftRunnerService.get(project.gradle)
|
|
||||||
|
|
||||||
init {
|
|
||||||
group = LifecycleBasePlugin.VERIFICATION_GROUP
|
|
||||||
usesService(clientRunner)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When [false], tests will not be run automatically, allowing the user to debug rendering.
|
|
||||||
*/
|
|
||||||
@get:Input
|
|
||||||
val clientDebug get() = project.hasProperty("clientDebug")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When [false], tests will not run under a framebuffer.
|
|
||||||
*/
|
|
||||||
@get:Input
|
|
||||||
val useFramebuffer get() = !clientDebug && !project.hasProperty("clientNoFramebuffer")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The path test results are written to.
|
|
||||||
*/
|
|
||||||
@get:OutputFile
|
|
||||||
val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy configuration from a task with the given name.
|
|
||||||
*/
|
|
||||||
fun copyFrom(path: String) = copyFrom(project.tasks.getByName(path, JavaExec::class))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy configuration from an existing [JavaExec] task.
|
|
||||||
*/
|
|
||||||
fun copyFrom(task: JavaExec) {
|
|
||||||
for (dep in task.dependsOn) dependsOn(dep)
|
|
||||||
task.copyToFull(this)
|
|
||||||
|
|
||||||
if (!clientDebug) systemProperty("cctest.client", "")
|
|
||||||
systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
|
|
||||||
workingDir(project.buildDir.resolve("gametest").resolve(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only run tests with the given tags.
|
|
||||||
*/
|
|
||||||
fun tags(vararg tags: String) {
|
|
||||||
systemProperty("cctest.tags", tags.joinToString(","))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a file with the given contents before starting Minecraft. This may be useful for writing config files.
|
|
||||||
*/
|
|
||||||
fun withFileContents(path: Any, contents: Supplier<String>) {
|
|
||||||
val file = project.file(path).toPath()
|
|
||||||
doFirst {
|
|
||||||
Files.createDirectories(file.parent)
|
|
||||||
Files.writeString(file, contents.get())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy a file to the provided path before starting Minecraft. This copy only occurs if the file does not already
|
|
||||||
* exist.
|
|
||||||
*/
|
|
||||||
fun withFileFrom(path: Any, source: Supplier<File>) {
|
|
||||||
val file = project.file(path).toPath()
|
|
||||||
doFirst {
|
|
||||||
Files.createDirectories(file.parent)
|
|
||||||
if (!Files.exists(file)) Files.copy(source.get().toPath(), file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
override fun exec() {
|
|
||||||
Files.createDirectories(workingDir.toPath())
|
|
||||||
fsOperations.delete { delete(workingDir.resolve("screenshots")) }
|
|
||||||
|
|
||||||
if (useFramebuffer) {
|
|
||||||
clientRunner.get().wrapClient(this) { super.exec() }
|
|
||||||
} else {
|
|
||||||
super.exec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Inject
|
|
||||||
protected abstract val fsOperations: FileSystemOperations
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A service for [JavaExec] tasks which start Minecraft.
|
|
||||||
*
|
|
||||||
* Tasks may run `usesService(MinecraftRunnerService.get(gradle))` to ensure that only one Minecraft-related task runs
|
|
||||||
* at once.
|
|
||||||
*/
|
|
||||||
abstract class MinecraftRunnerService : BuildService<BuildServiceParameters.None> {
|
|
||||||
private val hasXvfb = lazy {
|
|
||||||
System.getProperty("os.name", "").equals("linux", ignoreCase = true) && ProcessHelpers.onPath("xvfb-run")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun wrapClient(exec: JavaExec, run: () -> Unit) = when {
|
|
||||||
hasXvfb.value -> runXvfb(exec, run)
|
|
||||||
else -> run()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run a program under Xvfb, preventing it spawning a window.
|
|
||||||
*/
|
|
||||||
private fun runXvfb(exec: JavaExec, run: () -> Unit) {
|
|
||||||
fun ProcessBuilder.startVerbose(): Process {
|
|
||||||
exec.logger.info("Running ${this.command()}")
|
|
||||||
return start()
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseScope().use { scope ->
|
|
||||||
val dir = Files.createTempDirectory("cctweaked").toAbsolutePath()
|
|
||||||
scope.add { fsOperations.delete { delete(dir) } }
|
|
||||||
|
|
||||||
val authFile = Files.createTempFile(dir, "Xauthority", "").toAbsolutePath()
|
|
||||||
|
|
||||||
val cookie = StringBuilder().also {
|
|
||||||
for (i in 0..31) it.append("0123456789abcdef"[Random.nextInt(16)])
|
|
||||||
}.toString()
|
|
||||||
|
|
||||||
val xvfb =
|
|
||||||
ProcessBuilder("Xvfb", "-displayfd", "1", "-screen", "0", "640x480x24", "-nolisten", "tcp").also {
|
|
||||||
it.inheritIO()
|
|
||||||
it.environment()["XAUTHORITY"] = authFile.toString()
|
|
||||||
it.redirectOutput(ProcessBuilder.Redirect.PIPE)
|
|
||||||
}.startVerbose()
|
|
||||||
scope.add { xvfb.destroyForcibly().waitFor() }
|
|
||||||
|
|
||||||
val server = xvfb.inputReader().use { it.readLine().trim() }
|
|
||||||
exec.logger.info("Running at :$server (XAUTHORITY=$authFile.toA")
|
|
||||||
|
|
||||||
ProcessBuilder("xauth", "add", ":$server", ".", cookie).also {
|
|
||||||
it.inheritIO()
|
|
||||||
it.environment()["XAUTHORITY"] = authFile.toString()
|
|
||||||
}.startVerbose().waitForOrThrow("Failed to setup XAuthority file")
|
|
||||||
|
|
||||||
scope.add {
|
|
||||||
ProcessBuilder("xauth", "remove", ":$server").also {
|
|
||||||
it.inheritIO()
|
|
||||||
it.environment()["XAUTHORITY"] = authFile.toString()
|
|
||||||
}.startVerbose().waitFor()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait a few seconds for Xvfb to start. Ugly, but identical to xvfb-run.
|
|
||||||
if (xvfb.waitFor(3, TimeUnit.SECONDS)) {
|
|
||||||
throw GradleException("Xvfb unexpectedly exited (with status code ${xvfb.exitValue()})")
|
|
||||||
}
|
|
||||||
|
|
||||||
exec.environment("XAUTHORITY", authFile.toString())
|
|
||||||
exec.environment("DISPLAY", ":$server")
|
|
||||||
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Inject
|
|
||||||
protected abstract val fsOperations: FileSystemOperations
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun get(gradle: Gradle): Provider<MinecraftRunnerService> =
|
|
||||||
gradle.sharedServices.registerIfAbsent("cc.tweaked.gradle.ClientJavaExec", MinecraftRunnerService::class.java) {
|
|
||||||
maxParallelUsages.set(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,60 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,50 +0,0 @@
|
|||||||
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)")
|
|
||||||
}
|
|
@@ -1,20 +0,0 @@
|
|||||||
package cc.tweaked.gradle
|
|
||||||
|
|
||||||
import org.gradle.api.tasks.SourceSet
|
|
||||||
|
|
||||||
data class SourceSetReference(
|
|
||||||
val sourceSet: SourceSet,
|
|
||||||
val classes: Boolean,
|
|
||||||
val external: Boolean,
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
/** A source set in the current project. */
|
|
||||||
fun internal(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = false)
|
|
||||||
|
|
||||||
/** A source set from another project. */
|
|
||||||
fun external(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = true)
|
|
||||||
|
|
||||||
/** A source set which is inlined into the current project. */
|
|
||||||
fun inline(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = false, external = false)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,12 +0,0 @@
|
|||||||
package cc.tweaked.gradle
|
|
||||||
|
|
||||||
import groovy.util.Node
|
|
||||||
import groovy.util.NodeList
|
|
||||||
|
|
||||||
object XmlUtil {
|
|
||||||
fun findChild(node: Node, name: String): Node? = when (val child = node.get(name)) {
|
|
||||||
is Node -> child
|
|
||||||
is NodeList -> child.singleOrNull() as Node?
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
2491
codeInspectionSettings.xml
Normal file
2491
codeInspectionSettings.xml
Normal file
File diff suppressed because it is too large
Load Diff
61
codeStyleSettings.xml
Normal file
61
codeStyleSettings.xml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<JSON>
|
||||||
|
<option name="OBJECT_WRAPPING" value="1" />
|
||||||
|
<option name="ARRAY_WRAPPING" value="1" />
|
||||||
|
</JSON>
|
||||||
|
<JavaCodeStyleSettings>
|
||||||
|
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="JD_P_AT_EMPTY_LINES" value="false" />
|
||||||
|
<option name="JD_PRESERVE_LINE_FEEDS" value="true" />
|
||||||
|
</JavaCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
|
||||||
|
<option name="BRACE_STYLE" value="2" />
|
||||||
|
<option name="CLASS_BRACE_STYLE" value="2" />
|
||||||
|
<option name="METHOD_BRACE_STYLE" value="2" />
|
||||||
|
<option name="LAMBDA_BRACE_STYLE" value="5" />
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="CATCH_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="FINALLY_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_METHOD_CALL_PARENTHESES" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_METHOD_PARENTHESES" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_IF_PARENTHESES" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_WHILE_PARENTHESES" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_FOR_PARENTHESES" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_TRY_PARENTHESES" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_CATCH_PARENTHESES" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_SWITCH_PARENTHESES" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_SYNCHRONIZED_PARENTHESES" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_IF_PARENTHESES" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_TRY_PARENTHESES" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="1" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="1" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="1" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="1" />
|
||||||
|
<option name="SPACE_WITHIN_ANNOTATION_PARENTHESES" value="true" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JSON">
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="SPACE_WITHIN_BRACKETS" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_BRACES" value="true" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="4" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
@@ -1,167 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE module PUBLIC
|
|
||||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
|
||||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
|
||||||
<module name="Checker">
|
|
||||||
<property name="tabWidth" value="4"/>
|
|
||||||
<property name="charset" value="UTF-8" />
|
|
||||||
|
|
||||||
<module name="SuppressionFilter">
|
|
||||||
<property name="file" value="${config_loc}/suppressions.xml" />
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="BeforeExecutionExclusionFileFilter">
|
|
||||||
<property name="fileNamePattern" value="render_old"/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="TreeWalker">
|
|
||||||
<!-- Annotations -->
|
|
||||||
<module name="AnnotationLocation" />
|
|
||||||
<module name="AnnotationUseStyle">
|
|
||||||
<!-- We want trailing commas on multiline arrays. -->
|
|
||||||
<property name="trailingArrayComma" value="ignore" />
|
|
||||||
</module>
|
|
||||||
<module name="MissingDeprecated" />
|
|
||||||
<module name="MissingOverride" />
|
|
||||||
|
|
||||||
<!-- Blocks -->
|
|
||||||
<module name="EmptyBlock" />
|
|
||||||
<module name="EmptyCatchBlock">
|
|
||||||
<property name="exceptionVariableName" value="ignored" />
|
|
||||||
</module>
|
|
||||||
<module name="LeftCurly" />
|
|
||||||
<module name="NeedBraces">
|
|
||||||
<property name="allowSingleLineStatement" value="true"/>
|
|
||||||
</module>
|
|
||||||
<module name="RightCurly" />
|
|
||||||
|
|
||||||
<!-- Class design. As if we've ever followed good practice here. -->
|
|
||||||
<module name="FinalClass" />
|
|
||||||
<module name="InterfaceIsType" />
|
|
||||||
<module name="MutableException" />
|
|
||||||
<module name="OneTopLevelClass" />
|
|
||||||
|
|
||||||
<!-- Coding -->
|
|
||||||
<module name="ArrayTrailingComma" />
|
|
||||||
<module name="EqualsHashCode" />
|
|
||||||
<!-- FallThrough does not handle unreachable code well -->
|
|
||||||
<module name="IllegalInstantiation" />
|
|
||||||
<module name="IllegalThrows" />
|
|
||||||
<module name="ModifiedControlVariable" />
|
|
||||||
<module name="NoClone" />
|
|
||||||
<module name="NoFinalizer" />
|
|
||||||
<module name="OneStatementPerLine" />
|
|
||||||
<module name="PackageDeclaration" />
|
|
||||||
<module name="SimplifyBooleanExpression" />
|
|
||||||
<module name="SimplifyBooleanReturn" />
|
|
||||||
<module name="StringLiteralEquality" />
|
|
||||||
<module name="UnnecessaryParentheses">
|
|
||||||
<!-- Default minus LAND. -->
|
|
||||||
<property name="tokens" value="EXPR,IDENT,NUM_DOUBLE,NUM_FLOAT,NUM_INT,NUM_LONG,STRING_LITERAL,LITERAL_NULL,LITERAL_FALSE,LITERAL_TRUE,ASSIGN,BAND_ASSIGN,BOR_ASSIGN,BSR_ASSIGN,BXOR_ASSIGN,DIV_ASSIGN,MINUS_ASSIGN,MOD_ASSIGN,PLUS_ASSIGN,SL_ASSIGN,SR_ASSIGN,STAR_ASSIGN,LAMBDA,TEXT_BLOCK_LITERAL_BEGIN,LITERAL_INSTANCEOF,GT,LT,GE,LE,EQUAL,NOT_EQUAL,UNARY_MINUS,UNARY_PLUS,INC,DEC,LNOT,BNOT,POST_INC,POST_DEC" />
|
|
||||||
</module>
|
|
||||||
<module name="UnnecessarySemicolonAfterTypeMemberDeclaration" />
|
|
||||||
<module name="UnnecessarySemicolonInTryWithResources" />
|
|
||||||
<module name="UnnecessarySemicolonInEnumeration" />
|
|
||||||
|
|
||||||
<!-- Imports -->
|
|
||||||
<module name="CustomImportOrder">
|
|
||||||
<property name="customImportOrderRules"
|
|
||||||
value="THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE###STATIC"
|
|
||||||
/>
|
|
||||||
</module>
|
|
||||||
<module name="IllegalImport" />
|
|
||||||
<module name="RedundantImport" />
|
|
||||||
<module name="UnusedImports" />
|
|
||||||
|
|
||||||
<!-- Javadoc -->
|
|
||||||
<!-- TODO: Missing* checks for the dan200.computercraft.api package? -->
|
|
||||||
<module name="AtclauseOrder">
|
|
||||||
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
|
||||||
</module>
|
|
||||||
<module name="InvalidJavadocPosition" />
|
|
||||||
<module name="JavadocBlockTagLocation" />
|
|
||||||
<module name="JavadocMethod"/>
|
|
||||||
<module name="JavadocType"/>
|
|
||||||
<module name="JavadocStyle">
|
|
||||||
<property name="checkHtml" value="false" />
|
|
||||||
</module>
|
|
||||||
<module name="NonEmptyAtclauseDescription" />
|
|
||||||
<module name="SingleLineJavadoc" />
|
|
||||||
<module name="SummaryJavadocCheck"/>
|
|
||||||
|
|
||||||
<!-- Misc -->
|
|
||||||
<module name="ArrayTypeStyle" />
|
|
||||||
<module name="CommentsIndentation" />
|
|
||||||
<module name="Indentation" />
|
|
||||||
<module name="OuterTypeFilename" />
|
|
||||||
|
|
||||||
<!-- Modifiers -->
|
|
||||||
<module name="ModifierOrder" />
|
|
||||||
<module name="RedundantModifier" />
|
|
||||||
|
|
||||||
<!-- Naming -->
|
|
||||||
<module name="ClassTypeParameterName" />
|
|
||||||
<module name="InterfaceTypeParameterName" />
|
|
||||||
<module name="LambdaParameterName" />
|
|
||||||
<module name="LocalFinalVariableName" />
|
|
||||||
<module name="LocalVariableName" />
|
|
||||||
<module name="MemberName" />
|
|
||||||
<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|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_]+)?$" />
|
|
||||||
</module>
|
|
||||||
<module name="TypeName" />
|
|
||||||
|
|
||||||
<!-- Whitespace -->
|
|
||||||
<module name="EmptyForInitializerPad"/>
|
|
||||||
<module name="EmptyForIteratorPad">
|
|
||||||
<property name="option" value="space"/>
|
|
||||||
</module>
|
|
||||||
<module name="GenericWhitespace" />
|
|
||||||
<module name="MethodParamPad" />
|
|
||||||
<module name="NoLineWrap" />
|
|
||||||
<module name="NoWhitespaceAfter">
|
|
||||||
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP,METHOD_REF" />
|
|
||||||
</module>
|
|
||||||
<module name="NoWhitespaceBefore" />
|
|
||||||
<!-- TODO: Decide on an OperatorWrap style. -->
|
|
||||||
<module name="ParenPad" />
|
|
||||||
<module name="SeparatorWrap">
|
|
||||||
<property name="option" value="eol" />
|
|
||||||
<property name="tokens" value="COMMA,SEMI,ELLIPSIS,ARRAY_DECLARATOR,RBRACK,METHOD_REF" />
|
|
||||||
</module>
|
|
||||||
<module name="SeparatorWrap">
|
|
||||||
<property name="option" value="nl" />
|
|
||||||
<property name="tokens" value="DOT,AT" />
|
|
||||||
</module>
|
|
||||||
<module name="SingleSpaceSeparator" />
|
|
||||||
<module name="TypecastParenPad" />
|
|
||||||
<module name="WhitespaceAfter">
|
|
||||||
<property name="tokens" value="COMMA" />
|
|
||||||
</module>
|
|
||||||
<module name="WhitespaceAround">
|
|
||||||
<property name="ignoreEnhancedForColon" value="false" />
|
|
||||||
<!-- Allow empty functions -->
|
|
||||||
<property name="allowEmptyLambdas" value="true" />
|
|
||||||
<property name="allowEmptyMethods" value="true" />
|
|
||||||
<property name="allowEmptyConstructors" value="true" />
|
|
||||||
<property name="allowEmptyTypes" value="true" />
|
|
||||||
|
|
||||||
<property name="tokens" value="ASSIGN,BAND,BAND_ASSIGN,BOR,BOR_ASSIGN,BSR,BSR_ASSIGN,BXOR,BXOR_ASSIGN,COLON,DIV,DIV_ASSIGN,EQUAL,GE,GT,LAMBDA,LAND,LCURLY,LE,LITERAL_RETURN,LOR,LT,MINUS,MINUS_ASSIGN,MOD,MOD_ASSIGN,NOT_EQUAL,PLUS,PLUS_ASSIGN,QUESTION,RCURLY,SL,SLIST,SL_ASSIGN,SR,SR_ASSIGN,STAR,STAR_ASSIGN,LITERAL_ASSERT,TYPE_EXTENSION_AND" />
|
|
||||||
</module>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="FileTabCharacter" />
|
|
||||||
<module name="NewlineAtEndOfFile" />
|
|
||||||
<module name="RegexpSingleline">
|
|
||||||
<property name="format" value="\s+$"/>
|
|
||||||
<property name="message" value="Trailing whitespace"/>
|
|
||||||
</module>
|
|
||||||
</module>
|
|
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE suppressions PUBLIC
|
|
||||||
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
|
|
||||||
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
|
||||||
<suppressions>
|
|
||||||
<!-- All the config options and method fields. -->
|
|
||||||
<suppress checks="StaticVariableName" files=".*[\\/]ComputerCraft.java" />
|
|
||||||
<suppress checks="StaticVariableName" files=".*[\\/]ComputerCraftAPI.java" />
|
|
||||||
|
|
||||||
<!-- The commands API is documented in Lua. -->
|
|
||||||
<suppress checks="SummaryJavadocCheck" files=".*[\\/]CommandAPI.java" />
|
|
||||||
</suppressions>
|
|
@@ -1,8 +0,0 @@
|
|||||||
FROM gitpod/workspace-base
|
|
||||||
|
|
||||||
USER gitpod
|
|
||||||
|
|
||||||
RUN sudo apt-get -q update \
|
|
||||||
&& sudo apt-get install -yq openjdk-16-jdk python3-pip npm \
|
|
||||||
&& sudo pip3 install pre-commit \
|
|
||||||
&& sudo update-java-alternatives --set java-1.16.0-openjdk-amd64
|
|
@@ -1,3 +0,0 @@
|
|||||||
This file is part of the public ComputerCraft API - http://www.computercraft.info
|
|
||||||
Copyright Daniel Ratcliffe, 2011-${year}. 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.
|
|
@@ -1,3 +0,0 @@
|
|||||||
This file is part of ComputerCraft - http://www.computercraft.info
|
|
||||||
Copyright Daniel Ratcliffe, 2011-${year}. Do not distribute without permission.
|
|
||||||
Send enquiries to dratcliffe@gmail.com
|
|
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] alarm
|
|
||||||
see: os.setAlarm To start an alarm.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{timer} event is fired when an alarm started with @{os.setAlarm} completes.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{number}: The ID of the alarm that finished.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Starts a timer and then prints its ID:
|
|
||||||
```lua
|
|
||||||
local alarmID = os.setAlarm(os.time() + 0.05)
|
|
||||||
local event, id
|
|
||||||
repeat
|
|
||||||
event, id = os.pullEvent("alarm")
|
|
||||||
until id == alarmID
|
|
||||||
print("Alarm with ID " .. id .. " was fired")
|
|
||||||
```
|
|
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
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 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
|
|
||||||
corresponding character. The @{key} should be used if you want to listen to key presses themselves.
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The string representing the character that was pressed.
|
|
||||||
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints each character the user presses:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, character = os.pullEvent("char")
|
|
||||||
print(character .. " was pressed.")
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] computer_command
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{computer_command} event is fired when the `/computercraft queue` command is run for the current computer.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
... @{string}: The arguments passed to the command.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints the contents of messages sent:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event = {os.pullEvent("computer_command")}
|
|
||||||
print("Received message:", table.unpack(event, 2))
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] disk
|
|
||||||
see: disk_eject For the event sent when a disk is removed.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{disk} event is fired when a disk is inserted into an adjacent or networked disk drive.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The side of the disk drive that had a disk inserted.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when a disk is inserted:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, side = os.pullEvent("disk")
|
|
||||||
print("Inserted a disk on side " .. side)
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] disk_eject
|
|
||||||
see: disk For the event sent when a disk is inserted.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{disk_eject} event is fired when a disk is removed from an adjacent or networked disk drive.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The side of the disk drive that had a disk removed.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when a disk is removed:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, side = os.pullEvent("disk_eject")
|
|
||||||
print("Removed a disk on side " .. side)
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
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, that in turn has a single method @{TransferredFiles.getFiles|getFiles}. This
|
|
||||||
returns the list of files that are being transferred. Each file is a @{fs.BinaryReadHandle|binary file handle} with an
|
|
||||||
additional @{TransferredFile.getName|getName} method.
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
1. @{string}: The event name
|
|
||||||
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
|
|
||||||
```
|
|
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] http_check
|
|
||||||
see: http.checkURLAsync To check a URL asynchronously.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{http_check} event is fired when a URL check finishes.
|
|
||||||
|
|
||||||
This event is normally handled inside @{http.checkURL}, but it can still be seen when using @{http.checkURLAsync}.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
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.
|
|
@@ -1,39 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] http_failure
|
|
||||||
see: http.request To send an HTTP request.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{http_failure} event is fired when an HTTP request fails.
|
|
||||||
|
|
||||||
This event is normally handled inside @{http.get} and @{http.post}, but it can still be seen when using @{http.request}.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints an error why the website cannot be contacted:
|
|
||||||
```lua
|
|
||||||
local myURL = "https://does.not.exist.tweaked.cc"
|
|
||||||
http.request(myURL)
|
|
||||||
local event, url, err
|
|
||||||
repeat
|
|
||||||
event, url, err = os.pullEvent("http_failure")
|
|
||||||
until url == myURL
|
|
||||||
print("The URL " .. url .. " could not be reached: " .. err)
|
|
||||||
```
|
|
||||||
|
|
||||||
Prints the contents of a webpage that does not exist:
|
|
||||||
```lua
|
|
||||||
local myURL = "https://tweaked.cc/this/does/not/exist"
|
|
||||||
http.request(myURL)
|
|
||||||
local event, url, err, handle
|
|
||||||
repeat
|
|
||||||
event, url, err, handle = os.pullEvent("http_failure")
|
|
||||||
until url == myURL
|
|
||||||
print("The URL " .. url .. " could not be reached: " .. err)
|
|
||||||
print(handle.getResponseCode())
|
|
||||||
handle.close()
|
|
||||||
```
|
|
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] http_success
|
|
||||||
see: http.request To make an HTTP request.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{http_success} event is fired when an HTTP request returns successfully.
|
|
||||||
|
|
||||||
This event is normally handled inside @{http.get} and @{http.post}, but it can still be seen when using @{http.request}.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The URL of the site requested.
|
|
||||||
3. @{http.Response}: The handle for the response text.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints the content of a website (this may fail if the request fails):
|
|
||||||
```lua
|
|
||||||
local myURL = "https://tweaked.cc/"
|
|
||||||
http.request(myURL)
|
|
||||||
local event, url, handle
|
|
||||||
repeat
|
|
||||||
event, url, handle = os.pullEvent("http_success")
|
|
||||||
until url == myURL
|
|
||||||
print("Contents of " .. url .. ":")
|
|
||||||
print(handle.readAll())
|
|
||||||
handle.close()
|
|
||||||
```
|
|
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] key
|
|
||||||
---
|
|
||||||
|
|
||||||
This event is fired when any key is pressed while the terminal is focused.
|
|
||||||
|
|
||||||
This event returns a numerical "key code" (for instance, <kbd>F1</kbd> is 290). This value may vary between versions and
|
|
||||||
so it is recommended to use the constants in the @{keys} API rather than hard coding numeric values.
|
|
||||||
|
|
||||||
If the button pressed represented a printable character, then the @{key} event will be followed immediately by a @{char}
|
|
||||||
event. If you are consuming text input, use a @{char} event instead!
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{number}: The numerical key value of the key pressed.
|
|
||||||
3. @{boolean}: Whether the key event was generated while holding the key (@{true}), rather than pressing it the first time (@{false}).
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints each key when the user presses it, and if the key is being held.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, key, is_held = os.pullEvent("key")
|
|
||||||
print(("%s held=%s"):format(keys.getName(key), is_held))
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] key_up
|
|
||||||
see: keys For a lookup table of the given keys.
|
|
||||||
---
|
|
||||||
|
|
||||||
Fired whenever a key is released (or the terminal is closed while a key was being pressed).
|
|
||||||
|
|
||||||
This event returns a numerical "key code" (for instance, <kbd>F1</kbd> is 290). This value may vary between versions and
|
|
||||||
so it is recommended to use the constants in the @{keys} API rather than hard coding numeric values.
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{number}: The numerical key value of the key pressed.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints each key released on the keyboard whenever a @{key_up} event is fired.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, key = os.pullEvent("key_up")
|
|
||||||
local name = keys.getName(key) or "unknown key"
|
|
||||||
print(name .. " was released.")
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] modem_message
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{modem_message} event is fired when a message is received on an open channel on any @{modem}.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The side of the modem that received the message.
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Wraps a @{modem} peripheral, opens channel 0 for listening, and prints all received messages.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
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)))
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] monitor_resize
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{monitor_resize} event is fired when an adjacent or networked monitor's size is changed.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The side or network ID of the monitor that resized.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when a monitor is resized:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, side = os.pullEvent("monitor_resize")
|
|
||||||
print("The monitor on side " .. side .. " was resized.")
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] monitor_touch
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{monitor_touch} event is fired when an adjacent or networked Advanced Monitor is right-clicked.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The side or network ID of the monitor that was touched.
|
|
||||||
3. @{number}: The X coordinate of the touch, in characters.
|
|
||||||
4. @{number}: The Y coordinate of the touch, in characters.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when a monitor is touched:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, side, x, y = os.pullEvent("monitor_touch")
|
|
||||||
print("The monitor on side " .. side .. " was touched at (" .. x .. ", " .. y .. ")")
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] mouse_click
|
|
||||||
---
|
|
||||||
|
|
||||||
This event is fired when the terminal is clicked with a mouse. This event is only fired on advanced computers (including
|
|
||||||
advanced turtles and pocket computers).
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{number}: The mouse button that was clicked.
|
|
||||||
3. @{number}: The X-coordinate of the click.
|
|
||||||
4. @{number}: The Y-coordinate of the click.
|
|
||||||
|
|
||||||
## Mouse buttons
|
|
||||||
Several mouse events (@{mouse_click}, @{mouse_up}, @{mouse_scroll}) contain a "mouse button" code. This takes a
|
|
||||||
numerical value depending on which button on your mouse was last pressed when this event occurred.
|
|
||||||
|
|
||||||
| Button Code | Mouse Button |
|
|
||||||
|------------:|---------------|
|
|
||||||
| 1 | Left button |
|
|
||||||
| 2 | Right button |
|
|
||||||
| 3 | Middle button |
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Print the button and the coordinates whenever the mouse is clicked.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, button, x, y = os.pullEvent("mouse_click")
|
|
||||||
print(("The mouse button %s was pressed at %d, %d"):format(button, x, y))
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] mouse_drag
|
|
||||||
see: mouse_click For when a mouse button is initially pressed.
|
|
||||||
---
|
|
||||||
|
|
||||||
This event is fired every time the mouse is moved while a mouse button is being held.
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{number}: The [mouse button](mouse_click.html#Mouse_buttons) that is being pressed.
|
|
||||||
3. @{number}: The X-coordinate of the mouse.
|
|
||||||
4. @{number}: The Y-coordinate of the mouse.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Print the button and the coordinates whenever the mouse is dragged.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, button, x, y = os.pullEvent("mouse_drag")
|
|
||||||
print(("The mouse button %s was dragged at %d, %d"):format(button, x, y))
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] mouse_scroll
|
|
||||||
---
|
|
||||||
|
|
||||||
This event is fired when a mouse wheel is scrolled in the terminal.
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{number}: The direction of the scroll. (-1 = up, 1 = down)
|
|
||||||
3. @{number}: The X-coordinate of the mouse when scrolling.
|
|
||||||
4. @{number}: The Y-coordinate of the mouse when scrolling.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints the direction of each scroll, and the position of the mouse at the time.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, dir, x, y = os.pullEvent("mouse_scroll")
|
|
||||||
print(("The mouse was scrolled in direction %s at %d, %d"):format(dir, x, y))
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] mouse_up
|
|
||||||
---
|
|
||||||
|
|
||||||
This event is fired when a mouse button is released or a held mouse leaves the computer's terminal.
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{number}: The [mouse button](mouse_click.html#Mouse_buttons) that was released.
|
|
||||||
3. @{number}: The X-coordinate of the mouse.
|
|
||||||
4. @{number}: The Y-coordinate of the mouse.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints the coordinates and button number whenever the mouse is released.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, button, x, y = os.pullEvent("mouse_up")
|
|
||||||
print(("The mouse button %s was released at %d, %d"):format(button, x, y))
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] paste
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{paste} event is fired when text is pasted into the computer through Ctrl-V (or ⌘V on Mac).
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string} The text that was pasted.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints pasted text:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, text = os.pullEvent("paste")
|
|
||||||
print('"' .. text .. '" was pasted')
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] peripheral
|
|
||||||
see: peripheral_detach For the event fired when a peripheral is detached.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{peripheral} event is fired when a peripheral is attached on a side or to a modem.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The side the peripheral was attached to.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when a peripheral is attached:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, side = os.pullEvent("peripheral")
|
|
||||||
print("A peripheral was attached on side " .. side)
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] peripheral_detach
|
|
||||||
see: peripheral For the event fired when a peripheral is attached.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{peripheral_detach} event is fired when a peripheral is detached from a side or from a modem.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The side the peripheral was detached from.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when a peripheral is detached:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, side = os.pullEvent("peripheral_detach")
|
|
||||||
print("A peripheral was detached on side " .. side)
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] rednet_message
|
|
||||||
see: modem_message For raw modem messages sent outside of Rednet.
|
|
||||||
see: rednet.receive To wait for a Rednet message with an optional timeout and protocol filter.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{rednet_message} event is fired when a message is sent over Rednet.
|
|
||||||
|
|
||||||
This event is usually handled by @{rednet.receive}, but it can also be pulled manually.
|
|
||||||
|
|
||||||
@{rednet_message} events are sent by @{rednet.run} in the top-level coroutine in response to @{modem_message} events. A @{rednet_message} event is always preceded by a @{modem_message} event. They are generated inside CraftOS rather than being sent by the ComputerCraft machine.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when one is sent:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event, sender, message, protocol = os.pullEvent("rednet_message")
|
|
||||||
if protocol ~= nil then
|
|
||||||
print("Received message from " .. sender .. " with protocol " .. protocol .. " and message " .. tostring(message))
|
|
||||||
else
|
|
||||||
print("Received message from " .. sender .. " with message " .. tostring(message))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] redstone
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{event!redstone} event is fired whenever any redstone inputs on the computer change.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when a redstone input changes:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
os.pullEvent("redstone")
|
|
||||||
print("A redstone input has changed!")
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] speaker_audio_empty
|
|
||||||
see: speaker.playAudio To play audio using the speaker
|
|
||||||
---
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The name of the speaker which is available to play more audio.
|
|
||||||
|
|
||||||
|
|
||||||
## Example
|
|
||||||
This uses @{io.lines} to read audio data in blocks of 16KiB from "example_song.dfpwm", and then attempts to play it
|
|
||||||
using @{speaker.playAudio}. If the speaker's buffer is full, it waits for an event and tries again.
|
|
||||||
|
|
||||||
```lua {data-peripheral=speaker}
|
|
||||||
local dfpwm = require("cc.audio.dfpwm")
|
|
||||||
local speaker = peripheral.find("speaker")
|
|
||||||
|
|
||||||
local decoder = dfpwm.make_decoder()
|
|
||||||
for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
|
|
||||||
local buffer = decoder(chunk)
|
|
||||||
|
|
||||||
while not speaker.playAudio(buffer) do
|
|
||||||
os.pullEvent("speaker_audio_empty")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] task_complete
|
|
||||||
see: commands.execAsync To run a command which fires a task_complete event.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{task_complete} event is fired when an asynchronous task completes. This is usually handled inside the function call that queued the task; however, functions such as @{commands.execAsync} return immediately so the user can wait for completion.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints the results of an asynchronous command:
|
|
||||||
```lua
|
|
||||||
local taskID = commands.execAsync("say Hello")
|
|
||||||
local event
|
|
||||||
repeat
|
|
||||||
event = {os.pullEvent("task_complete")}
|
|
||||||
until event[2] == taskID
|
|
||||||
if event[3] == true then
|
|
||||||
print("Task " .. event[2] .. " succeeded:", table.unpack(event, 4))
|
|
||||||
else
|
|
||||||
print("Task " .. event[2] .. " failed: " .. event[4])
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] term_resize
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{term_resize} event is fired when the main terminal is resized. For instance:
|
|
||||||
- When a the tab bar is shown or hidden in @{multishell}.
|
|
||||||
- When the terminal is redirected to a monitor via the "monitor" program and the monitor is resized.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints :
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
os.pullEvent("term_resize")
|
|
||||||
local w, h = term.getSize()
|
|
||||||
print("The term was resized to (" .. w .. ", " .. h .. ")")
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] terminate
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{terminate} event is fired when <kbd>Ctrl-T</kbd> is held down.
|
|
||||||
|
|
||||||
This event is normally handled by @{os.pullEvent}, and will not be returned. However, @{os.pullEventRaw} will return this event when fired.
|
|
||||||
|
|
||||||
@{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}.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when Ctrl-T is held:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
local event = os.pullEventRaw("terminate")
|
|
||||||
if event == "terminate" then print("Terminate requested!") end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Exits when Ctrl-T is held:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
os.pullEvent()
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] timer
|
|
||||||
see: os.startTimer To start a timer.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{timer} event is fired when a timer started with @{os.startTimer} completes.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{number}: The ID of the timer that finished.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Starts a timer and then prints its ID:
|
|
||||||
```lua
|
|
||||||
local timerID = os.startTimer(2)
|
|
||||||
local event, id
|
|
||||||
repeat
|
|
||||||
event, id = os.pullEvent("timer")
|
|
||||||
until id == timerID
|
|
||||||
print("Timer with ID " .. id .. " was fired")
|
|
||||||
```
|
|
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] turtle_inventory
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{turtle_inventory} event is fired when a turtle's inventory is changed.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when the inventory is changed:
|
|
||||||
```lua
|
|
||||||
while true do
|
|
||||||
os.pullEvent("turtle_inventory")
|
|
||||||
print("The inventory was changed.")
|
|
||||||
end
|
|
||||||
```
|
|
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] websocket_closed
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{websocket_closed} event is fired when an open WebSocket connection is closed.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The URL of the WebSocket that was closed.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message when a WebSocket is closed (this may take a minute):
|
|
||||||
```lua
|
|
||||||
local myURL = "wss://example.tweaked.cc/echo"
|
|
||||||
local ws = http.websocket(myURL)
|
|
||||||
local event, url
|
|
||||||
repeat
|
|
||||||
event, url = os.pullEvent("websocket_closed")
|
|
||||||
until url == myURL
|
|
||||||
print("The WebSocket at " .. url .. " was closed.")
|
|
||||||
```
|
|
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] websocket_failure
|
|
||||||
see: http.websocketAsync To send an HTTP request.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{websocket_failure} event is fired when a WebSocket connection request fails.
|
|
||||||
|
|
||||||
This event is normally handled inside @{http.websocket}, but it can still be seen when using @{http.websocketAsync}.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The URL of the site requested.
|
|
||||||
3. @{string}: An error describing the failure.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints an error why the website cannot be contacted:
|
|
||||||
```lua
|
|
||||||
local myURL = "wss://example.tweaked.cc/not-a-websocket"
|
|
||||||
http.websocketAsync(myURL)
|
|
||||||
local event, url, err
|
|
||||||
repeat
|
|
||||||
event, url, err = os.pullEvent("websocket_failure")
|
|
||||||
until url == myURL
|
|
||||||
print("The URL " .. url .. " could not be reached: " .. err)
|
|
||||||
```
|
|
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] websocket_message
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{websocket_message} event is fired when a message is received on an open WebSocket connection.
|
|
||||||
|
|
||||||
This event is normally handled by @{http.Websocket.receive}, but it can also be pulled manually.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The URL of the WebSocket.
|
|
||||||
3. @{string}: The contents of the message.
|
|
||||||
4. @{boolean}: Whether this is a binary message.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints a message sent by a WebSocket:
|
|
||||||
```lua
|
|
||||||
local myURL = "wss://example.tweaked.cc/echo"
|
|
||||||
local ws = http.websocket(myURL)
|
|
||||||
ws.send("Hello!")
|
|
||||||
local event, url, message
|
|
||||||
repeat
|
|
||||||
event, url, message = os.pullEvent("websocket_message")
|
|
||||||
until url == myURL
|
|
||||||
print("Received message from " .. url .. " with contents " .. message)
|
|
||||||
ws.close()
|
|
||||||
```
|
|
@@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=event] websocket_success
|
|
||||||
see: http.websocketAsync To open a WebSocket asynchronously.
|
|
||||||
---
|
|
||||||
|
|
||||||
The @{websocket_success} event is fired when a WebSocket connection request returns successfully.
|
|
||||||
|
|
||||||
This event is normally handled inside @{http.websocket}, but it can still be seen when using @{http.websocketAsync}.
|
|
||||||
|
|
||||||
## Return Values
|
|
||||||
1. @{string}: The event name.
|
|
||||||
2. @{string}: The URL of the site.
|
|
||||||
3. @{http.Websocket}: The handle for the WebSocket.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
Prints the content of a website (this may fail if the request fails):
|
|
||||||
```lua
|
|
||||||
local myURL = "wss://example.tweaked.cc/echo"
|
|
||||||
http.websocketAsync(myURL)
|
|
||||||
local event, url, handle
|
|
||||||
repeat
|
|
||||||
event, url, handle = os.pullEvent("websocket_success")
|
|
||||||
until url == myURL
|
|
||||||
print("Connected to " .. url)
|
|
||||||
handle.send("Hello!")
|
|
||||||
print(handle.receive())
|
|
||||||
handle.close()
|
|
||||||
```
|
|
@@ -1,90 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
:::
|
|
@@ -1,99 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=guide] local_ips
|
|
||||||
---
|
|
||||||
|
|
||||||
# Allowing access to local IPs
|
|
||||||
By default, ComputerCraft blocks access to local IP addresses for security. This means you can't normally access any
|
|
||||||
HTTP server running on your computer. However, this may be useful for testing programs without having a remote
|
|
||||||
server. You can unblock these IPs in the ComputerCraft config.
|
|
||||||
|
|
||||||
- [Minecraft 1.13 and later, CC:T 1.87.0 and later](#cc-1.87.0)
|
|
||||||
- [Minecraft 1.13 and later, CC:T 1.86.2 and earlier](#cc-1.86.2)
|
|
||||||
- [Minecraft 1.12.2 and earlier](#mc-1.12)
|
|
||||||
|
|
||||||
## Minecraft 1.13 and later, CC:T 1.87.0 and later {#cc-1.87.0}
|
|
||||||
The configuration file can be located at `serverconfig/computercraft-server.toml` inside the world folder on either
|
|
||||||
single-player or multiplayer. Look for lines that look like this:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
#A list of rules which control behaviour of the "http" API for specific domains or IPs.
|
|
||||||
#Each rule is an item with a 'host' to match against, and a series of properties. The host may be a domain name ("pastebin.com"),
|
|
||||||
#wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). If no rules, the domain is blocked.
|
|
||||||
[[http.rules]]
|
|
||||||
host = "$private"
|
|
||||||
action = "deny"
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
|
||||||
`action = "deny"`. Then save the file and relaunch Minecraft (Server).
|
|
||||||
|
|
||||||
Here's what it should look like after removing:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
#A list of rules which control behaviour of the "http" API for specific domains or IPs.
|
|
||||||
#Each rule is an item with a 'host' to match against, and a series of properties. The host may be a domain name ("pastebin.com"),
|
|
||||||
#wildcard ("*.pastebin.com") or CIDR notation ("127.0.0.0/8"). If no rules, the domain is blocked.
|
|
||||||
[[http.rules]]
|
|
||||||
#The maximum size (in bytes) that a computer can send or receive in one websocket packet.
|
|
||||||
max_websocket_message = 131072
|
|
||||||
host = "*"
|
|
||||||
#The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text.
|
|
||||||
max_upload = 4194304
|
|
||||||
action = "allow"
|
|
||||||
#The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client.
|
|
||||||
max_download = 16777216
|
|
||||||
#The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited.
|
|
||||||
timeout = 30000
|
|
||||||
```
|
|
||||||
|
|
||||||
## Minecraft 1.13 and later, CC:T 1.86.2 and earlier {#cc-1.86.2}
|
|
||||||
The configuration file for singleplayer is at `.minecraft/config/computercraft-common.toml`. Look for lines that look
|
|
||||||
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").
|
|
||||||
blacklist = ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fd00::/8"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Remove everything inside the array, leaving the last line as `blacklist = []`. Then save the file and relaunch Minecraft.
|
|
||||||
|
|
||||||
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").
|
|
||||||
blacklist = []
|
|
||||||
```
|
|
||||||
|
|
||||||
## Minecraft 1.12.2 and earlier {#mc-1.12}
|
|
||||||
On singleplayer, the configuration file is located at `.minecraft\config\ComputerCraft.cfg`. On multiplayer, the
|
|
||||||
configuration file is located at `<server folder>\config\ComputerCraft.cfg`. Look for lines that look like this:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
# 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 explicitly allowed domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
|
|
||||||
# You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
|
|
||||||
S:blocked_domains <
|
|
||||||
127.0.0.0/8
|
|
||||||
10.0.0.0/8
|
|
||||||
172.16.0.0/12
|
|
||||||
192.168.0.0/16
|
|
||||||
fd00::/8
|
|
||||||
>
|
|
||||||
```
|
|
||||||
|
|
||||||
Delete everything between the `<>`, leaving the last line as `S:blocked_domains = <>`. Then save the file and relaunch
|
|
||||||
Minecraft (Server).
|
|
||||||
|
|
||||||
Here's what it should look like after removing:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
# 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 explicitly allowed domains will be accessible. Example: "*.github.com" will block access to all subdomains of github.com.
|
|
||||||
# You can use domain names ("pastebin.com"), wildcards ("*.pastebin.com") or CIDR notation ("127.0.0.0/8").
|
|
||||||
S:blocked_domains <>
|
|
||||||
```
|
|
@@ -1,204 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=guide] speaker_audio
|
|
||||||
see: speaker.playAudio Play PCM audio using a speaker.
|
|
||||||
see: cc.audio.dfpwm Provides utilities for encoding and decoding DFPWM files.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Playing audio with speakers
|
|
||||||
CC: Tweaked's speaker peripheral provides a powerful way to play any audio you like with the @{speaker.playAudio}
|
|
||||||
method. However, for people unfamiliar with digital audio, it's not the most intuitive thing to use. This guide provides
|
|
||||||
an introduction to digital audio, demonstrates how to play music with CC: Tweaked's speakers, and then briefly discusses
|
|
||||||
the more complex topic of audio processing.
|
|
||||||
|
|
||||||
## A short introduction to digital audio
|
|
||||||
When sound is recorded it is captured as an analogue signal, effectively the electrical version of a sound
|
|
||||||
wave. However, this signal is continuous, and so can't be used directly by a computer. Instead, we measure (or *sample*)
|
|
||||||
the amplitude of the wave many times a second and then *quantise* that amplitude, rounding it to the nearest
|
|
||||||
representable value.
|
|
||||||
|
|
||||||
This representation of sound - a long, uniformally sampled list of amplitudes is referred to as [Pulse-code
|
|
||||||
Modulation][PCM] (PCM). PCM can be thought of as the "standard" audio format, as it's incredibly easy to work with. For
|
|
||||||
instance, to mix two pieces of audio together, you can just add samples from the two tracks together and take the average.
|
|
||||||
|
|
||||||
CC: Tweaked's speakers also work with PCM audio. It plays back 48,000 samples a second, where each sample is an integer
|
|
||||||
between -128 and 127. This is more commonly referred to as 48kHz and an 8-bit resolution.
|
|
||||||
|
|
||||||
Let's now look at a quick example. We're going to generate a [Sine Wave] at 220Hz, which sounds like a low monotonous
|
|
||||||
hum. First we wrap our speaker peripheral, and then we fill a table (also referred to as a *buffer*) with 128×1024
|
|
||||||
samples - this is the maximum number of samples a speaker can accept in one go.
|
|
||||||
|
|
||||||
In order to fill this buffer, we need to do a little maths. We want to play 220 sine waves each second, where each sine
|
|
||||||
wave completes a full oscillation in 2π "units". This means one seconds worth of audio is 2×π×220 "units" long. We then
|
|
||||||
need to split this into 48k samples, basically meaning for each sample we move 2×π×220/48k "along" the sine curve.
|
|
||||||
|
|
||||||
```lua {data-peripheral=speaker}
|
|
||||||
local speaker = peripheral.find("speaker")
|
|
||||||
|
|
||||||
local buffer = {}
|
|
||||||
local t, dt = 0, 2 * math.pi * 220 / 48000
|
|
||||||
for i = 1, 128 * 1024 do
|
|
||||||
buffer[i] = math.floor(math.sin(t) * 127)
|
|
||||||
t = (t + dt) % (math.pi * 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
speaker.playAudio(buffer)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Streaming audio
|
|
||||||
You might notice that the above snippet only generates a short bit of audio - 2.7s seconds to be precise. While we could
|
|
||||||
try increasing the number of loop iterations, we'll get an error when we try to play it through the speaker: the sound
|
|
||||||
buffer is too large for it to handle.
|
|
||||||
|
|
||||||
Our 2.7 seconds of audio is stored in a table with over 130 _thousand_ elements. If we wanted to play a full minute of
|
|
||||||
sine waves (and why wouldn't you?), you'd need a table with almost 3 _million_. Suddenly you find these numbers adding
|
|
||||||
up very quickly, and these tables take up more and more memory.
|
|
||||||
|
|
||||||
Instead of building our entire song (well, sine wave) in one go, we can produce it in small batches, each of which get
|
|
||||||
passed off to @{speaker.playAudio} when the time is right. This allows us to build a _stream_ of audio, where we read
|
|
||||||
chunks of audio one at a time (either from a file or a tone generator like above), do some optional processing to each
|
|
||||||
one, and then play them.
|
|
||||||
|
|
||||||
Let's adapt our example from above to do that instead.
|
|
||||||
|
|
||||||
```lua {data-peripheral=speaker}
|
|
||||||
local speaker = peripheral.find("speaker")
|
|
||||||
|
|
||||||
local t, dt = 0, 2 * math.pi * 220 / 48000
|
|
||||||
while true do
|
|
||||||
local buffer = {}
|
|
||||||
for i = 1, 16 * 1024 * 8 do
|
|
||||||
buffer[i] = math.floor(math.sin(t) * 127)
|
|
||||||
t = (t + dt) % (math.pi * 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
while not speaker.playAudio(buffer) do
|
|
||||||
os.pullEvent("speaker_audio_empty")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
It looks pretty similar to before, aside from we've wrapped the generation and playing code in a while loop, and added a
|
|
||||||
rather odd loop with @{speaker.playAudio} and @{os.pullEvent}.
|
|
||||||
|
|
||||||
Let's talk about this loop, why do we need to keep calling @{speaker.playAudio}? Remember that what we're trying to do
|
|
||||||
here is avoid keeping too much audio in memory at once. However, if we're generating audio quicker than the speakers can
|
|
||||||
play it, we're not helping at all - all this audio is still hanging around waiting to be played!
|
|
||||||
|
|
||||||
In order to avoid this, the speaker rejects any new chunks of audio if its backlog is too large. When this happens,
|
|
||||||
@{speaker.playAudio} returns false. Once enough audio has played, and the backlog has been reduced, a
|
|
||||||
@{speaker_audio_empty} event is queued, and we can try to play our chunk once more.
|
|
||||||
|
|
||||||
## Storing audio
|
|
||||||
PCM is a fantastic way of representing audio when we want to manipulate it, but it's not very efficient when we want to
|
|
||||||
store it to disk. Compare the size of a WAV file (which uses PCM) to an equivalent MP3, it's often 5 times the size.
|
|
||||||
Instead, we store audio in special formats (or *codecs*) and then convert them to PCM when we need to do processing on
|
|
||||||
them.
|
|
||||||
|
|
||||||
Modern audio codecs use some incredibly impressive techniques to compress the audio as much as possible while preserving
|
|
||||||
sound quality. However, due to CC: Tweaked's limited processing power, it's not really possible to use these from your
|
|
||||||
computer. Instead, we need something much simpler.
|
|
||||||
|
|
||||||
DFPWM (Dynamic Filter Pulse Width Modulation) is the de facto standard audio format of the ComputerCraft (and
|
|
||||||
OpenComputers) world. Originally popularised by the addon mod [Computronics], CC:T now has built-in support for it with
|
|
||||||
the @{cc.audio.dfpwm} module. This allows you to read DFPWM files from disk, decode them to PCM, and then play them
|
|
||||||
using the speaker.
|
|
||||||
|
|
||||||
Let's dive in with an example, and we'll explain things afterwards:
|
|
||||||
|
|
||||||
```lua {data-peripheral=speaker}
|
|
||||||
local dfpwm = require("cc.audio.dfpwm")
|
|
||||||
local speaker = peripheral.find("speaker")
|
|
||||||
|
|
||||||
local decoder = dfpwm.make_decoder()
|
|
||||||
for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
|
|
||||||
local buffer = decoder(chunk)
|
|
||||||
|
|
||||||
while not speaker.playAudio(buffer) do
|
|
||||||
os.pullEvent("speaker_audio_empty")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Once again, we see the @{speaker.playAudio}/@{speaker_audio_empty} loop. However, the rest of the program is a little
|
|
||||||
different.
|
|
||||||
|
|
||||||
First, we require the dfpwm module and call @{cc.audio.dfpwm.make_decoder} to construct a new decoder. This decoder
|
|
||||||
accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker.
|
|
||||||
|
|
||||||
As mentioned above, @{speaker.playAudio} accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
|
|
||||||
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
|
|
||||||
@{io.lines}, which provides a nice way to loop over chunks of a file. You can of course just use @{fs.open} and
|
|
||||||
@{fs.BinaryReadHandle.read} if you prefer.
|
|
||||||
|
|
||||||
## Processing audio
|
|
||||||
As mentioned near the beginning of this guide, PCM audio is pretty easy to work with as it's just a list of amplitudes.
|
|
||||||
You can mix together samples from different streams by adding their amplitudes, change the rate of playback by removing
|
|
||||||
samples, etc...
|
|
||||||
|
|
||||||
Let's put together a small demonstration here. We're going to add a small delay effect to the song above, so that you
|
|
||||||
hear a faint echo a second and a half later.
|
|
||||||
|
|
||||||
In order to do this, we'll follow a format similar to the previous example, decoding the audio and then playing it.
|
|
||||||
However, we'll also add some new logic between those two steps, which loops over every sample in our chunk of audio, and
|
|
||||||
adds the sample from 1.5 seconds ago to it.
|
|
||||||
|
|
||||||
For this, we'll need to keep track of the last 72k samples - exactly 1.5 seconds worth of audio. We can do this using a
|
|
||||||
[Ring Buffer], which helps makes things a little more efficient.
|
|
||||||
|
|
||||||
```lua {data-peripheral=speaker}
|
|
||||||
local dfpwm = require("cc.audio.dfpwm")
|
|
||||||
local speaker = peripheral.find("speaker")
|
|
||||||
|
|
||||||
-- Speakers play at 48kHz, so 1.5 seconds is 72k samples. We first fill our buffer
|
|
||||||
-- with 0s, as there's nothing to echo at the start of the track!
|
|
||||||
local samples_i, samples_n = 1, 48000 * 1.5
|
|
||||||
local samples = {}
|
|
||||||
for i = 1, samples_n do samples[i] = 0 end
|
|
||||||
|
|
||||||
local decoder = dfpwm.make_decoder()
|
|
||||||
for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
|
|
||||||
local buffer = decoder(chunk)
|
|
||||||
|
|
||||||
for i = 1, #buffer do
|
|
||||||
local original_value = buffer[i]
|
|
||||||
|
|
||||||
-- Replace this sample with its current amplitude plus the amplitude from 1.5 seconds ago.
|
|
||||||
-- We scale both to ensure the resulting value is still between -128 and 127.
|
|
||||||
buffer[i] = original_value * 0.6 + samples[samples_i] * 0.4
|
|
||||||
|
|
||||||
-- Now store the current sample, and move the "head" of our ring buffer forward one place.
|
|
||||||
samples[samples_i] = original_value
|
|
||||||
samples_i = samples_i + 1
|
|
||||||
if samples_i > samples_n then samples_i = 1 end
|
|
||||||
end
|
|
||||||
|
|
||||||
while not speaker.playAudio(buffer) do
|
|
||||||
os.pullEvent("speaker_audio_empty")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- The audio processing above can be quite slow and preparing the first batch of audio
|
|
||||||
-- may timeout the computer. We sleep to avoid this.
|
|
||||||
-- There's definitely better ways of handling this - this is just an example!
|
|
||||||
sleep(0.05)
|
|
||||||
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 [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
|
|
||||||
the wave. If you wanted to modify the _frequency_ (for instance, shifting the pitch), things get rather more complex.
|
|
||||||
For this, you'd need to use the [Fast Fourier transform][FFT] to convert the stream of amplitudes to frequencies,
|
|
||||||
process those, and then convert them back to amplitudes.
|
|
||||||
|
|
||||||
This is, I'm afraid, left as an exercise to the reader.
|
|
||||||
|
|
||||||
[Computronics]: https://github.com/Vexatos/Computronics/ "Computronics on GitHub"
|
|
||||||
[FFT]: https://en.wikipedia.org/wiki/Fast_Fourier_transform "Fast Fourier transform - Wikipedia"
|
|
||||||
[PCM]: https://en.wikipedia.org/wiki/Pulse-code_modulation "Pulse-code Modulation - Wikipedia"
|
|
||||||
[Ring Buffer]: https://en.wikipedia.org/wiki/Circular_buffer "Circular buffer - Wikipedia"
|
|
||||||
[Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia"
|
|
||||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
|
||||||
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
|
@@ -1,83 +0,0 @@
|
|||||||
---
|
|
||||||
module: [kind=guide] using_require
|
|
||||||
---
|
|
||||||
|
|
||||||
# Reusing code with require
|
|
||||||
A library is a collection of useful functions and other definitions which is stored separately to your main program. You
|
|
||||||
might want to create a library because you have some functions which are used in multiple programs, or just to split
|
|
||||||
your program into multiple more modular files.
|
|
||||||
|
|
||||||
Let's say we want to create a small library to make working with the @{term|terminal} a little easier. We'll provide two
|
|
||||||
functions: `reset`, which clears the terminal and sets the cursor to (1, 1), and `write_center`, which prints some text
|
|
||||||
in the middle of the screen.
|
|
||||||
|
|
||||||
Start off by creating a file called `more_term.lua`:
|
|
||||||
|
|
||||||
```lua {data-snippet=more_term}
|
|
||||||
local function reset()
|
|
||||||
term.clear()
|
|
||||||
term.setCursorPos(1, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function write_center(text)
|
|
||||||
local x, y = term.getCursorPos()
|
|
||||||
local width, height = term.getSize()
|
|
||||||
term.setCursorPos(math.floor((width - #text) / 2) + 1, y)
|
|
||||||
term.write(text)
|
|
||||||
end
|
|
||||||
|
|
||||||
return { reset = reset, write_center = write_center }
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, what's going on here? We define our two functions as one might expect, and then at the bottom return a table with
|
|
||||||
the two functions. When we require this library, this table is what is returned. With that, we can then call the
|
|
||||||
original functions. Now create a new file, with the following:
|
|
||||||
|
|
||||||
```lua {data-mount=more_term:more_term.lua}
|
|
||||||
local more_term = require("more_term")
|
|
||||||
more_term.reset()
|
|
||||||
more_term.write_center("Hello, world!")
|
|
||||||
```
|
|
||||||
|
|
||||||
When run, this'll clear the screen and print some text in the middle of the first line.
|
|
||||||
|
|
||||||
## require in depth
|
|
||||||
While the previous section is a good introduction to how @{require} operates, there are a couple of remaining points
|
|
||||||
which are worth mentioning for more advanced usage.
|
|
||||||
|
|
||||||
### Libraries can return anything
|
|
||||||
In our above example, we return a table containing the functions we want to expose. However, it's worth pointing out
|
|
||||||
that you can return ''anything'' from your library - a table, a function or even just a string! @{require} treats them
|
|
||||||
all the same, and just returns whatever your library provides.
|
|
||||||
|
|
||||||
### Module resolution and the package path
|
|
||||||
In the above examples, we defined our library in a file, and @{require} read from it. While this is what you'll do most
|
|
||||||
of the time, it is possible to make @{require} look elsewhere for your library, such as downloading from a website or
|
|
||||||
loading from an in-memory library store.
|
|
||||||
|
|
||||||
As a result, the *module name* you pass to @{require} doesn't correspond to a file path. One common mistake is to load
|
|
||||||
code from a sub-directory using `require("folder/library")` or even `require("folder/library.lua")`, neither of which
|
|
||||||
will do quite what you expect.
|
|
||||||
|
|
||||||
When loading libraries (also referred to as *modules*) from files, @{require} searches along the *@{package.path|module
|
|
||||||
path}*. By default, this looks something like:
|
|
||||||
|
|
||||||
* `?.lua`
|
|
||||||
* `?/init.lua`
|
|
||||||
* `/rom/modules/main/?.lua`
|
|
||||||
* etc...
|
|
||||||
|
|
||||||
When you call `require("my_library")`, @{require} replaces the `?` in each element of the path with your module name, and
|
|
||||||
checks if the file exists. In this case, we'd look for `my_library.lua`, `my_library/init.lua`,
|
|
||||||
`/rom/modules/main/my_library.lua` and so on. Note that this works *relative to the current program*, so if your
|
|
||||||
program is actually called `folder/program`, then we'll look for `folder/my_library.lua`, etc...
|
|
||||||
|
|
||||||
One other caveat is loading libraries from sub-directories. For instance, say we have a file
|
|
||||||
`my/fancy/library.lua`. This can be loaded by using `require("my.fancy.library")` - the '.'s are replaced with '/'
|
|
||||||
before we start looking for the library.
|
|
||||||
|
|
||||||
## External links
|
|
||||||
There are several external resources which go into require in a little more detail:
|
|
||||||
|
|
||||||
- The [Lua Module tutorial](http://lua-users.org/wiki/ModulesTutorial) on the Lua wiki.
|
|
||||||
- [Lua's manual section on @{require}](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
|
@@ -1 +0,0 @@
|
|||||||
<meta name="theme-color" content="#c8d87c">
|
|
Binary file not shown.
Before Width: | Height: | Size: 53 KiB |
Binary file not shown.
Before Width: | Height: | Size: 331 KiB |
Binary file not shown.
Before Width: | Height: | Size: 194 KiB |
Binary file not shown.
Before Width: | Height: | Size: 163 KiB |
54
doc/index.md
54
doc/index.md
@@ -1,54 +0,0 @@
|
|||||||
# 
|
|
||||||
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.
|
|
||||||
|
|
||||||
CC: Tweaked can be installed from [CurseForge] or [Modrinth]. It requires the [Minecraft Forge][forge] mod loader, but
|
|
||||||
[versions are available for Fabric][ccrestitched].
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
{.big-image}
|
|
||||||
|
|
||||||
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!
|
|
||||||
|
|
||||||
{.big-image}
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
{.big-image}
|
|
||||||
|
|
||||||
## 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 sidebar and links below provide 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"
|
|
||||||
[curseforge]: https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked from CurseForge"
|
|
||||||
[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"
|
|
||||||
[lua]: https://www.lua.org/ "Lua's main website"
|
|
||||||
[GitHub Discussions]: https://github.com/cc-tweaked/CC-Tweaked/discussions
|
|
||||||
[IRC]: https://webchat.esper.net/?channels=computercraft "#computercraft on EsperNet"
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user