mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-15 22:17:39 +00:00
Compare commits
177 Commits
v1.19.3-1.
...
v1.94.1-be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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
|
||||
insert_final_newline = true
|
||||
|
||||
ij_continuation_indent_size = 4
|
||||
ij_any_do_while_brace_force = if_multiline
|
||||
ij_any_if_brace_force = if_multiline
|
||||
ij_any_for_brace_force = if_multiline
|
||||
ij_any_spaces_within_array_initializer_braces = true
|
||||
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
ij_kotlin_method_parameters_wrap = off
|
||||
ij_kotlin_call_parameters_wrap = off
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.sexp]
|
||||
indent_size = 2
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
[*.properties]
|
||||
insert_final_newline = false
|
||||
|
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
120
.github/workflows/main-ci.yml
vendored
120
.github/workflows/main-ci.yml
vendored
@@ -3,96 +3,48 @@ name: Build
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
- name: Set up Java 8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
||||
- name: Cache gradle dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('gradle.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Disable Gradle daemon
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-daemon || ./gradlew build --no-daemon
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
./gradlew assemble || ./gradlew assemble
|
||||
./gradlew downloadAssets || ./gradlew downloadAssets
|
||||
./gradlew build
|
||||
- name: Upload Jar
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: cc-restiched
|
||||
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: Upload Coverage
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
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: Generate Java documentation stubs
|
||||
run: ./gradlew luaJavadoc --no-daemon
|
||||
|
||||
- name: Upload Jar
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: CC-Tweaked
|
||||
path: ./jars
|
||||
- name: Lint Lua code
|
||||
run: |
|
||||
test -d bin || mkdir bin
|
||||
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
|
||||
chmod +x bin/illuaminate
|
||||
bin/illuaminate lint
|
||||
|
||||
- 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() }}
|
||||
- name: Check whitespace
|
||||
run: python3 tools/check-lines.py
|
||||
|
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
|
||||
/logs
|
||||
/build
|
||||
/projects/*/logs
|
||||
/projects/*/build
|
||||
/buildSrc/build
|
||||
/out
|
||||
/doc/out/
|
||||
/node_modules
|
||||
.jqwik-database
|
||||
|
||||
# Runtime directories
|
||||
/run
|
||||
/projects/*/run
|
||||
/run-*
|
||||
/test-files
|
||||
|
||||
# Autogenerated by IDE
|
||||
/bin
|
||||
/.settings
|
||||
.classpath
|
||||
|
||||
*.ipr
|
||||
*.iws
|
||||
@@ -20,11 +20,4 @@
|
||||
.idea
|
||||
.gradle
|
||||
*.DS_Store
|
||||
|
||||
/.classpath
|
||||
/.project
|
||||
/.settings
|
||||
/.vscode
|
||||
*.launch
|
||||
|
||||
/projects/*/src/generated/resources/.cache
|
||||
.project
|
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
|
||||
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
|
||||
that is explicitly or implicitly required for the mod to be working.
|
||||
|
||||
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
|
||||
|
||||
2. Liability
|
||||
@@ -41,13 +41,13 @@ or misuse of this mod fall on the user.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
5. Distribution of original or modified copy rights
|
||||
@@ -61,10 +61,10 @@ include:
|
||||
- patch to its source or binary 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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
69
README.md
69
README.md
@@ -1,66 +1,5 @@
|
||||
# 
|
||||
[](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status") [][CurseForge]
|
||||
# CC:Restitched Patchwork
|
||||
# This is a Work In Progress Port
|
||||
*it runs and works-ish*
|
||||
|
||||
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].
|
||||
|
||||
## Contributing
|
||||
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you want to get started
|
||||
developing the mod, [check out the instructions here](CONTRIBUTING.md#developing).
|
||||
|
||||
## Community
|
||||
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about
|
||||
ComputerCraft, 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").
|
||||
|
||||
## Using
|
||||
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)
|
||||
dependency in your `mods.toml` file, with the appropriate version bounds, to ensure that API functionality you depend
|
||||
on is present.
|
||||
|
||||
```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"
|
||||
PRs welcome
|
||||
|
122
build.gradle
Normal file
122
build.gradle
Normal file
@@ -0,0 +1,122 @@
|
||||
plugins {
|
||||
id 'fabric-loom' version '0.5-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()
|
||||
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.1.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
|
||||
|
||||
modRuntime "me.shedaniel:RoughlyEnoughItems-api:5.2.10"
|
||||
modRuntime "me.shedaniel:RoughlyEnoughItems:5.2.10"
|
||||
}
|
||||
|
||||
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"
|
BIN
doc/logo.png
BIN
doc/logo.png
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user