mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-03 23:22:59 +00:00 
			
		
		
		
	Compare commits
	
		
			132 Commits
		
	
	
		
			mc-1.18.x
			...
			v1.19.3-1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					cdc91a8e5d | ||
| 
						 | 
					8024017f53 | ||
| 
						 | 
					592ff84aea | ||
| 
						 | 
					4360458416 | ||
| 
						 | 
					717e096b94 | ||
| 
						 | 
					34a31abd9c | ||
| 
						 | 
					bdecb88cca | ||
| 
						 | 
					af15030fa4 | ||
| 
						 | 
					3a883db49e | ||
| 
						 | 
					8ea5b64f64 | ||
| 
						 | 
					7b6caf76e4 | ||
| 
						 | 
					230c7ee904 | ||
| 
						 | 
					aa203802c6 | ||
| 
						 | 
					1259e29f21 | ||
| 
						 | 
					77f62dac94 | ||
| 
						 | 
					7f34aff6bb | ||
| 
						 | 
					3047e3cdf4 | ||
| 
						 | 
					7a83a403f0 | ||
| 
						 | 
					a1d5c76d00 | ||
| 
						 | 
					bcdfa7c5ff | ||
| 
						 | 
					2c59b9122b | ||
| 
						 | 
					d2c7b944ab | ||
| 
						 | 
					e241575329 | ||
| 
						 | 
					86c4c7483d | ||
| 
						 | 
					9010219b9c | ||
| 
						 | 
					172d1824fc | ||
| 
						 | 
					9d394f44d3 | ||
| 
						 | 
					6e5b7243f4 | ||
| 
						 | 
					27b732f835 | ||
| 
						 | 
					4fa7f50534 | ||
| 
						 | 
					eeac86b07c | ||
| 
						 | 
					36ce490566 | ||
| 
						 | 
					e7fe22d4f8 | ||
| 
						 | 
					2b237332ce | ||
| 
						 | 
					1276478deb | ||
| 
						 | 
					551f6ba60c | ||
| 
						 | 
					99a2b26fc5 | ||
| 
						 | 
					0787e17ebe | ||
| 
						 | 
					06163e4f25 | ||
| 
						 | 
					18fbd96c10 | ||
| 
						 | 
					367773e173 | ||
| 
						 | 
					8007a30849 | ||
| 
						 | 
					df38f3e887 | ||
| 
						 | 
					c3fe9f00d4 | ||
| 
						 | 
					3b42f22a4f | ||
| 
						 | 
					9962ce1a5c | ||
| 
						 | 
					9f48395596 | ||
| 
						 | 
					020c5cd2d3 | ||
| 
						 | 
					a9c0b02e3c | ||
| 
						 | 
					fc5f296eeb | ||
| 
						 | 
					c96172e78d | ||
| 
						 | 
					fa122a56cf | ||
| 
						 | 
					87c6d3aef6 | ||
| 
						 | 
					95c57e843d | ||
| 
						 | 
					b13998dd96 | ||
| 
						 | 
					47816805fb | ||
| 
						 | 
					b8fce1eecc | ||
| 
						 | 
					ee2670d53b | ||
| 
						 | 
					3a96aea894 | ||
| 
						 | 
					0fc78acd49 | ||
| 
						 | 
					737d8a2585 | ||
| 
						 | 
					e2447bb0fd | ||
| 
						 | 
					2255d49d16 | ||
| 
						 | 
					3fa39b5f98 | ||
| 
						 | 
					08df68dcc0 | ||
| 
						 | 
					8f92417a2f | ||
| 
						 | 
					b58b9b7df3 | ||
| 
						 | 
					8d2e150f05 | ||
| 
						 | 
					8152f19b6e | ||
| 
						 | 
					b2b58892e3 | ||
| 
						 | 
					8360e8234d | ||
| 
						 | 
					77624fc6fd | ||
| 
						 | 
					1d335f7290 | ||
| 
						 | 
					f04acdc199 | ||
| 
						 | 
					bdf590fa30 | ||
| 
						 | 
					0c4fd2b29e | ||
| 
						 | 
					8a7156785d | ||
| 
						 | 
					4d50b48ea6 | ||
| 
						 | 
					48285404b9 | ||
| 
						 | 
					b36b96e0bc | ||
| 
						 | 
					34c7fcf750 | ||
| 
						 | 
					cc73fcd85d | ||
| 
						 | 
					22729f6f16 | ||
| 
						 | 
					55494b7671 | ||
| 
						 | 
					7d47b219c5 | ||
| 
						 | 
					320007dbc6 | ||
| 
						 | 
					0908acbe9b | ||
| 
						 | 
					e8f9cdd221 | ||
| 
						 | 
					53abe5e56e | ||
| 
						 | 
					564752c8dd | ||
| 
						 | 
					6d665ad841 | ||
| 
						 | 
					9cd728fea9 | ||
| 
						 | 
					1c890e5a5c | ||
| 
						 | 
					955b9c7d28 | ||
| 
						 | 
					76710eec9d | ||
| 
						 | 
					d8e2161f15 | ||
| 
						 | 
					c82f37d3bf | ||
| 
						 | 
					c8c128d335 | ||
| 
						 | 
					acc254a1ef | ||
| 
						 | 
					a17b001950 | ||
| 
						 | 
					e4e528e5bf | ||
| 
						 | 
					6cc86b0ae5 | ||
| 
						 | 
					f478c4ffc4 | ||
| 
						 | 
					7df0412c2d | ||
| 
						 | 
					998efcc950 | ||
| 
						 | 
					45c5de73bb | ||
| 
						 | 
					c919011a7e | ||
| 
						 | 
					0f1f5247ca | ||
| 
						 | 
					c1bf9f0b24 | ||
| 
						 | 
					629abb65e3 | ||
| 
						 | 
					b5056fc3b8 | ||
| 
						 | 
					a9ef874174 | ||
| 
						 | 
					a2911038c5 | ||
| 
						 | 
					c8e15f201c | ||
| 
						 | 
					bc79100a2f | ||
| 
						 | 
					66dff1523b | ||
| 
						 | 
					76f8dd2d14 | ||
| 
						 | 
					34a2e87735 | ||
| 
						 | 
					c43d851e63 | ||
| 
						 | 
					50fe7935a3 | ||
| 
						 | 
					c3615d9c5b | ||
| 
						 | 
					25a44bea6e | ||
| 
						 | 
					4a273ae8e5 | ||
| 
						 | 
					e906f3ebc3 | ||
| 
						 | 
					92c613a7a2 | ||
| 
						 | 
					8fc7820a12 | ||
| 
						 | 
					a2e3d9d9bd | ||
| 
						 | 
					755f8eff93 | ||
| 
						 | 
					a879efc3d0 | ||
| 
						 | 
					a913232e62 | ||
| 
						 | 
					5382d34d29 | ||
| 
						 | 
					8f7719a8dc | 
@@ -8,6 +8,17 @@ charset = utf-8
 | 
			
		||||
trim_trailing_whitespace = true
 | 
			
		||||
insert_final_newline = true
 | 
			
		||||
 | 
			
		||||
ij_continuation_indent_size = 4
 | 
			
		||||
ij_any_do_while_brace_force = if_multiline
 | 
			
		||||
ij_any_if_brace_force = if_multiline
 | 
			
		||||
ij_any_for_brace_force = if_multiline
 | 
			
		||||
ij_any_spaces_within_array_initializer_braces = true
 | 
			
		||||
 | 
			
		||||
ij_kotlin_allow_trailing_comma = true
 | 
			
		||||
ij_kotlin_allow_trailing_comma_on_call_site = true
 | 
			
		||||
ij_kotlin_method_parameters_wrap = off
 | 
			
		||||
ij_kotlin_call_parameters_wrap = off
 | 
			
		||||
 | 
			
		||||
[*.md]
 | 
			
		||||
trim_trailing_whitespace = false
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.git-blame-ignore-revs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.git-blame-ignore-revs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
# Reformat everything
 | 
			
		||||
f478c4ffc4fb9fc2200ec9b0bc751d047057ce81
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
# Ignore changes in generated files
 | 
			
		||||
src/generated/** linguist-generated
 | 
			
		||||
src/testMod/server-files/structures linguist-generated
 | 
			
		||||
projects/*/src/generated/** linguist-generated
 | 
			
		||||
projects/common/src/testMod/resources/data/cctest/structures/* linguist-generated
 | 
			
		||||
 | 
			
		||||
* text=auto
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								.github/workflows/main-ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								.github/workflows/main-ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -33,14 +33,25 @@ jobs:
 | 
			
		||||
        ./gradlew downloadAssets || ./gradlew downloadAssets
 | 
			
		||||
        ./gradlew build
 | 
			
		||||
 | 
			
		||||
    - name: Run client tests
 | 
			
		||||
      run: ./gradlew runGametestClient # Not checkClient, as no point running rendering tests.
 | 
			
		||||
      # These are a little flaky on GH actions: its useful to run them, but don't break the build.
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - name: Prepare Jars
 | 
			
		||||
      run: |
 | 
			
		||||
        # Find the main jar and append the git hash onto it.
 | 
			
		||||
        mkdir -p jars
 | 
			
		||||
        find projects/forge/build/libs projects/fabric/build/libs -type f -regex '.*[0-9.]+\(-SNAPSHOT\)?\.jar$' -exec bash -c 'cp {} "jars/$(basename {} .jar)-$(git rev-parse HEAD).jar"' \;
 | 
			
		||||
 | 
			
		||||
    - name: Upload Jar
 | 
			
		||||
      uses: actions/upload-artifact@v2
 | 
			
		||||
      uses: actions/upload-artifact@v3
 | 
			
		||||
      with:
 | 
			
		||||
        name: CC-Tweaked
 | 
			
		||||
        path: build/libs
 | 
			
		||||
        path: ./jars
 | 
			
		||||
 | 
			
		||||
    - name: Upload coverage
 | 
			
		||||
      uses: codecov/codecov-action@v2
 | 
			
		||||
      uses: codecov/codecov-action@v3
 | 
			
		||||
 | 
			
		||||
    - name: Parse test reports
 | 
			
		||||
      run: ./tools/parse-reports.py
 | 
			
		||||
@@ -48,3 +59,40 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    - name: Run linters
 | 
			
		||||
      uses: pre-commit/action@v3.0.0
 | 
			
		||||
 | 
			
		||||
  build-core:
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        include:
 | 
			
		||||
        - name: Windows
 | 
			
		||||
          uses: windows-latest
 | 
			
		||||
 | 
			
		||||
        - name: macOS
 | 
			
		||||
          uses: macos-latest
 | 
			
		||||
 | 
			
		||||
    name: Test on ${{ matrix.name }}
 | 
			
		||||
    runs-on: ${{ matrix.uses }}
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Clone repository
 | 
			
		||||
      uses: actions/checkout@v3
 | 
			
		||||
 | 
			
		||||
    - name: Set up Java
 | 
			
		||||
      uses: actions/setup-java@v3
 | 
			
		||||
      with:
 | 
			
		||||
        java-version: 17
 | 
			
		||||
        distribution: 'temurin'
 | 
			
		||||
 | 
			
		||||
    - name: Setup Gradle
 | 
			
		||||
      uses: gradle/gradle-build-action@v2
 | 
			
		||||
      with:
 | 
			
		||||
        cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
 | 
			
		||||
 | 
			
		||||
    - name: Run tests
 | 
			
		||||
      run: |
 | 
			
		||||
        ./gradlew --configure-on-demand :core:test
 | 
			
		||||
 | 
			
		||||
    - name: Parse test reports
 | 
			
		||||
      run: python3 ./tools/parse-reports.py
 | 
			
		||||
      if: ${{ failure() }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/make-doc.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/make-doc.sh
									
									
									
									
										vendored
									
									
								
							@@ -12,8 +12,8 @@ chmod 600 "$HOME/.ssh/key"
 | 
			
		||||
 | 
			
		||||
# And upload
 | 
			
		||||
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
 | 
			
		||||
      "$GITHUB_WORKSPACE/build/docs/site/" \
 | 
			
		||||
      "$GITHUB_WORKSPACE/projects/web/build/site/" \
 | 
			
		||||
      "$SSH_USER@$SSH_HOST:/$DEST"
 | 
			
		||||
rsync -avc -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no -p $SSH_PORT" \
 | 
			
		||||
      "$GITHUB_WORKSPACE/build/docs/javadoc/" \
 | 
			
		||||
      "$GITHUB_WORKSPACE/projects/common-api/build/docs/javadoc/" \
 | 
			
		||||
      "$SSH_USER@$SSH_HOST:/$DEST/javadoc"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/make-doc.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/make-doc.yml
									
									
									
									
										vendored
									
									
								
							@@ -3,7 +3,7 @@ name: Build documentation
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
    - mc-1.16.x
 | 
			
		||||
    - mc-1.19.x
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  make_doc:
 | 
			
		||||
@@ -29,7 +29,7 @@ jobs:
 | 
			
		||||
      run: ./gradlew compileJava --no-daemon || ./gradlew compileJava --no-daemon
 | 
			
		||||
 | 
			
		||||
    - name: Generate documentation
 | 
			
		||||
      run: ./gradlew docWebsite javadoc --no-daemon
 | 
			
		||||
      run: ./gradlew docWebsite :common-api:javadoc  --no-daemon
 | 
			
		||||
 | 
			
		||||
    - name: Upload documentation
 | 
			
		||||
      run: .github/workflows/make-doc.sh 2> /dev/null
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -2,15 +2,17 @@
 | 
			
		||||
/classes
 | 
			
		||||
/logs
 | 
			
		||||
/build
 | 
			
		||||
/projects/*/logs
 | 
			
		||||
/projects/*/build
 | 
			
		||||
/buildSrc/build
 | 
			
		||||
/out
 | 
			
		||||
/doc/out/
 | 
			
		||||
/node_modules
 | 
			
		||||
/.jqwik-database
 | 
			
		||||
.jqwik-database
 | 
			
		||||
 | 
			
		||||
# Runtime directories
 | 
			
		||||
/run
 | 
			
		||||
/run-*
 | 
			
		||||
/projects/*/run
 | 
			
		||||
 | 
			
		||||
*.ipr
 | 
			
		||||
*.iws
 | 
			
		||||
@@ -23,8 +25,6 @@
 | 
			
		||||
/.project
 | 
			
		||||
/.settings
 | 
			
		||||
/.vscode
 | 
			
		||||
bin/
 | 
			
		||||
*.launch
 | 
			
		||||
 | 
			
		||||
/src/generated/resources/.cache
 | 
			
		||||
/src/web/mount/*.d.ts
 | 
			
		||||
/projects/*/src/generated/resources/.cache
 | 
			
		||||
 
 | 
			
		||||
@@ -48,9 +48,7 @@ repos:
 | 
			
		||||
 | 
			
		||||
exclude: |
 | 
			
		||||
  (?x)^(
 | 
			
		||||
    src/generated|
 | 
			
		||||
    src/test/resources/test-rom/data/json-parsing/|
 | 
			
		||||
    src/testMod/server-files/|
 | 
			
		||||
    config/idea/|
 | 
			
		||||
    projects/[a-z]+/src/generated|
 | 
			
		||||
    projects/core/src/test/resources/test-rom/data/json-parsing/|
 | 
			
		||||
    .*\.dfpwm
 | 
			
		||||
  )
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										139
									
								
								CONTRIBUTING.md
									
									
									
									
									
								
							
							
						
						
									
										139
									
								
								CONTRIBUTING.md
									
									
									
									
									
								
							@@ -4,96 +4,96 @@ provides an introduction as to how to get started in helping out.
 | 
			
		||||
 | 
			
		||||
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
 | 
			
		||||
 | 
			
		||||
## Table of Contents
 | 
			
		||||
 - [Reporting issues](#reporting-issues)
 | 
			
		||||
 - [Translations](#translations)
 | 
			
		||||
 - [Setting up a development environment](#setting-up-a-development-environment)
 | 
			
		||||
 - [Developing CC: Tweaked](#developing-cc-tweaked)
 | 
			
		||||
 - [Writing documentation](#writing-documentation)
 | 
			
		||||
 | 
			
		||||
## Reporting issues
 | 
			
		||||
If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so,
 | 
			
		||||
do use the issue templates - they provide a useful hint on what information to provide.
 | 
			
		||||
If you have a bug, suggestion, or other feedback, the best thing to do is [file an issue][new-issue]. When doing so, do
 | 
			
		||||
use the issue templates - they provide a useful hint on what information to provide.
 | 
			
		||||
 | 
			
		||||
## Translations
 | 
			
		||||
Translations are managed through [Weblate], an online interface for managing language strings. This is synced
 | 
			
		||||
automatically with GitHub, so please don't submit PRs adding/changing translations!
 | 
			
		||||
 | 
			
		||||
## Developing
 | 
			
		||||
In order to develop CC: Tweaked, you'll need to download the source code and then run it. This is a pretty simple
 | 
			
		||||
process. When building on Windows, Use `gradlew.bat` instead of `./gradlew`.
 | 
			
		||||
## Setting up a development environment
 | 
			
		||||
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
 | 
			
		||||
 | 
			
		||||
 - **Clone the repository:** `git clone https://github.com/cc-tweaked/CC-Tweaked.git && cd CC-Tweaked`
 | 
			
		||||
 - **Setup Forge:** `./gradlew build`
 | 
			
		||||
 - **Run Minecraft:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
 | 
			
		||||
 - **Optionally:** For small PRs (especially those only touching Lua code), it may be easier to use GitPod, which
 | 
			
		||||
   provides a pre-configured environment: [](https://gitpod.io/#https://github.com/cc-tweaked/CC-Tweaked/)
 | 
			
		||||
 - Make sure you've got the following software instealled:
 | 
			
		||||
   - Java Development Kit (JDK) installed. This can be downloaded from [Adoptium].
 | 
			
		||||
   - [Git](https://git-scm.com/).
 | 
			
		||||
   - If you want to work on documentation, [NodeJS][node].
 | 
			
		||||
 | 
			
		||||
   Do note you will need to download the mod after compiling to test.
 | 
			
		||||
 - Download CC: Tweaked's source code:
 | 
			
		||||
   ```
 | 
			
		||||
   git clone https://github.com/cc-tweaked/CC-Tweaked.git
 | 
			
		||||
   cd CC-Tweaked
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
 | 
			
		||||
These commands may take a few minutes to run the first time, as the environment is set up, but should be much faster
 | 
			
		||||
afterwards.
 | 
			
		||||
 - Build CC: Tweaked with `./gradlew build`. This will be very slow the first time it runs, as it needs to download a
 | 
			
		||||
   lot of dependencies (and decompile Minecraft several times). Subsequent runs should be much faster!
 | 
			
		||||
 | 
			
		||||
The following sections describe the more niche sections of CC: Tweaked's build system. Some bits of these are
 | 
			
		||||
quite-complex, and (dare I say) over-engineered, so you may wish to ignore them. Well tested/documented PRs are always
 | 
			
		||||
preferred (and I'd definitely recommend setting up the tooling if you're doing serious development work), but for
 | 
			
		||||
small changes it can be a lot.
 | 
			
		||||
 - You're now ready to start developing CC: Tweaked. Running `./gradlew :forge:runClient` or
 | 
			
		||||
   `./gradle :fabric:runClient` will start Minecraft under Forge and Fabric respectively.
 | 
			
		||||
 | 
			
		||||
### Code linters
 | 
			
		||||
CC: Tweaked uses a couple of "linters" on its source code, to enforce a consistent style across the project. While these
 | 
			
		||||
are run whenever you submit a PR, it's often useful to run this before committing.
 | 
			
		||||
If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble` and copy the `.jar` from
 | 
			
		||||
`projects/forge/build/libs` (for Forge) or `projects/fabric/build/libs` (for Fabric).
 | 
			
		||||
 | 
			
		||||
 - **[Checkstyle]:** Checks Java code to ensure it is consistently formatted. This can be run with `./gradlew build` or
 | 
			
		||||
   `./gradle check`.
 | 
			
		||||
 - **[illuaminate]:** Checks Lua code for semantic and styleistic issues. This can be run with `./gradlew lintLua`.
 | 
			
		||||
## Developing CC: Tweaked
 | 
			
		||||
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
 | 
			
		||||
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
 | 
			
		||||
looking to make your changes. As always, if you're not sure [do ask the community][community]!
 | 
			
		||||
 | 
			
		||||
### Documentation
 | 
			
		||||
### Testing
 | 
			
		||||
When making larger changes, it's may be useful to write a test to make sure your code works as expected.
 | 
			
		||||
 | 
			
		||||
CC: Tweaked has several test suites, each designed to test something different:
 | 
			
		||||
 | 
			
		||||
 - In order to test CraftOS and its builtin APIs, we have a test suite written in Lua located at
 | 
			
		||||
   `projects/core/src/test/resources/test-rom/`. These don't rely on any Minecraft code, which means they can run on
 | 
			
		||||
   emulators, acting as a sort of compliance test.
 | 
			
		||||
 | 
			
		||||
   These tests are written using a test system called "mcfly", heavily inspired by [busted]. Groups of tests go inside
 | 
			
		||||
   `describe` blocks, and a single test goes inside `it`. Assertions are generally written using `expect` (inspired by
 | 
			
		||||
   Hamcrest and the like). For instance, `expect(foo):eq("bar")` asserts that your variable `foo` is equal to the
 | 
			
		||||
   expected value `"bar"`.
 | 
			
		||||
 | 
			
		||||
   These tests can be run with `./gradlew :core:test`.
 | 
			
		||||
 | 
			
		||||
 - In-game functionality, such as the behaviour of blocks and items, is tested using [Minecraft's gametest
 | 
			
		||||
   system][mc-test] (`projects/common/src/testMod`). These tests spin up a server, spawn a structure for each test, and
 | 
			
		||||
   then run some code on the blocks defined in that structure.
 | 
			
		||||
 | 
			
		||||
   These tests can be run with `./gradlew runGametest` (or `./gradle :forge:runGametest`/`./gradlew :fabric:runGametest`
 | 
			
		||||
   for a single loader).
 | 
			
		||||
 | 
			
		||||
For more information, [see the architecture document][architecture].
 | 
			
		||||
 | 
			
		||||
## Writing documentation
 | 
			
		||||
When writing documentation for [CC: Tweaked's documentation website][docs], it may be useful to build the documentation
 | 
			
		||||
and preview it yourself before submitting a PR.
 | 
			
		||||
 | 
			
		||||
Our documentation generation pipeline is rather complex, and involves invoking several external tools. Most of this
 | 
			
		||||
complexity is hidden by Gradle, but you will need to perform some initial setup:
 | 
			
		||||
You'll first need to [set up a development environment as above](#setting-up-a-development-environment).
 | 
			
		||||
 | 
			
		||||
 - Install [Node/npm][node].
 | 
			
		||||
 - Run `npm ci` to install our Node dependencies.
 | 
			
		||||
Once this is set up, you can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code,
 | 
			
		||||
writing the resulting HTML into `./projects/web/build/site`, which can then be opened in a browser. When iterating on
 | 
			
		||||
documentation, you can instead run `./gradlew docWebsite -t`, which will rebuild documentation every time you change a
 | 
			
		||||
file.
 | 
			
		||||
 | 
			
		||||
You can now run `./gradlew docWebsite`. This generates documentation from our Lua and Java code, writing the resulting
 | 
			
		||||
HTML into `./build/docs/site`.
 | 
			
		||||
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
 | 
			
		||||
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
 | 
			
		||||
markdown features - if you can, do check what the documentation looks like locally!
 | 
			
		||||
 | 
			
		||||
#### Writing documentation
 | 
			
		||||
illuaminate's documentation system is not currently documented (somewhat ironic), but is _largely_ the same as
 | 
			
		||||
[ldoc][ldoc]. Documentation comments are written in Markdown,
 | 
			
		||||
 | 
			
		||||
Our markdown engine does _not_ support GitHub flavoured markdown, and so does not support all the features one might
 | 
			
		||||
expect. It is recommended that you build and preview the docs locally first.
 | 
			
		||||
 | 
			
		||||
When iterating on documentation, you can get Gradle to rebuild the website every time a file changes by running
 | 
			
		||||
`./gradlew docWebsite -t`. This will take a couple of seconds to run, but definitely beats running it manually!
 | 
			
		||||
 | 
			
		||||
### Testing
 | 
			
		||||
Thankfully running tests is much simpler than running the documentation generator! `./gradlew check` will run the
 | 
			
		||||
entire test suite (and some additional bits of verification).
 | 
			
		||||
 | 
			
		||||
Before we get into writing tests, it's worth mentioning the various test suites that CC: Tweaked has:
 | 
			
		||||
 - "Core" Java (`./src/test/java`): These test core bits of the mod which don't require any Minecraft interaction.
 | 
			
		||||
   This includes the `@LuaFunction` system, file system code, etc...
 | 
			
		||||
 | 
			
		||||
   These tests are run by `./gradlew test`.
 | 
			
		||||
 | 
			
		||||
 - CraftOS (`./src/test/resources/test-rom/`): These tests are written in Lua, and ensure the Lua environment, libraries
 | 
			
		||||
   and programs work as expected. These are (generally) written to be able to be run on emulators too, to provide some
 | 
			
		||||
   sort of compliance test.
 | 
			
		||||
 | 
			
		||||
   These tests are run by the '"Core" Java' test suite, and so are also run with `./gradlew test`.
 | 
			
		||||
 | 
			
		||||
 - In-game (`./src/testMod/java/dan200/computercraft/ingame/`): These tests are run on an actual Minecraft server, using
 | 
			
		||||
   the same system Mojang do][mc-test]. The aim of these is to test in-game behaviour of blocks and peripherals.
 | 
			
		||||
 | 
			
		||||
   These tests are run with `./gradlew runGametest`.
 | 
			
		||||
 | 
			
		||||
## CraftOS tests
 | 
			
		||||
CraftOS's tests are written using a test system called "mcfly", heavily inspired by [busted] (and thus RSpec). Groups of
 | 
			
		||||
tests go inside `describe` blocks, and a single test goes inside `it`.
 | 
			
		||||
 | 
			
		||||
Assertions are generally written using `expect` (inspired by Hamcrest and the like). For instance, `expect(foo):eq("bar")`
 | 
			
		||||
asserts that your variable `foo` is equal to the expected value `"bar"`.
 | 
			
		||||
When writing long-form documentation (such as the guides in [doc/guides](doc/guides)), I find it useful to tell a
 | 
			
		||||
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept and then talk
 | 
			
		||||
about how you can build on that, until you've covered everything!
 | 
			
		||||
 | 
			
		||||
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
 | 
			
		||||
[community]: README.md#Community "Get in touch with the community."
 | 
			
		||||
[community]: README.md#community "Get in touch with the community."
 | 
			
		||||
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
 | 
			
		||||
[checkstyle]: https://checkstyle.org/
 | 
			
		||||
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
 | 
			
		||||
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
 | 
			
		||||
@@ -102,3 +102,4 @@ asserts that your variable `foo` is equal to the expected value `"bar"`.
 | 
			
		||||
[mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg
 | 
			
		||||
[busted]: https://github.com/Olivine-Labs/busted "busted: Elegant Lua unit testing."
 | 
			
		||||
[node]: https://nodejs.org/en/ "Node.js"
 | 
			
		||||
[architecture]: projects/ARCHITECTURE.md
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							@@ -26,16 +26,26 @@ on is present.
 | 
			
		||||
```groovy
 | 
			
		||||
repositories {
 | 
			
		||||
  maven {
 | 
			
		||||
    url 'https://squiddev.cc/maven/'
 | 
			
		||||
    url "https://squiddev.cc/maven/"
 | 
			
		||||
    content {
 | 
			
		||||
      includeGroup 'org.squiddev'
 | 
			
		||||
      includeGroup("cc.tweaked")
 | 
			
		||||
      includeModule("org.squiddev", "Cobalt")
 | 
			
		||||
      includeModule("fuzs.forgeconfigapiport", "forgeconfigapiport-fabric")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly fg.deobf("org.squiddev:cc-tweaked-${mc_version}:${cct_version}:api")
 | 
			
		||||
  runtimeOnly fg.deobf("org.squiddev:cc-tweaked-${mc_version}:${cct_version}")
 | 
			
		||||
  // Vanilla (i.e. for multi-loader systems)
 | 
			
		||||
  compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
 | 
			
		||||
 | 
			
		||||
  // Forge Gradle
 | 
			
		||||
  compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$cctVersion"))
 | 
			
		||||
  runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$cctVersion"))
 | 
			
		||||
 | 
			
		||||
  // Fabric Loom
 | 
			
		||||
  modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$cctVersion")
 | 
			
		||||
  modRuntimeOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric:$cctVersion")
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										448
									
								
								build.gradle.kts
									
									
									
									
									
								
							
							
						
						
									
										448
									
								
								build.gradle.kts
									
									
									
									
									
								
							@@ -1,400 +1,18 @@
 | 
			
		||||
import cc.tweaked.gradle.*
 | 
			
		||||
import net.darkhax.curseforgegradle.TaskPublishCurseForge
 | 
			
		||||
import net.minecraftforge.gradle.common.util.RunConfig
 | 
			
		||||
import org.jetbrains.gradle.ext.compiler
 | 
			
		||||
import org.jetbrains.gradle.ext.settings
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    // Build
 | 
			
		||||
    alias(libs.plugins.forgeGradle)
 | 
			
		||||
    alias(libs.plugins.mixinGradle)
 | 
			
		||||
    alias(libs.plugins.librarian)
 | 
			
		||||
    alias(libs.plugins.shadow)
 | 
			
		||||
    // Publishing
 | 
			
		||||
    `maven-publish`
 | 
			
		||||
    alias(libs.plugins.curseForgeGradle)
 | 
			
		||||
    alias(libs.plugins.githubRelease)
 | 
			
		||||
    alias(libs.plugins.minotaur)
 | 
			
		||||
    // Utility
 | 
			
		||||
    publishing
 | 
			
		||||
    alias(libs.plugins.taskTree)
 | 
			
		||||
 | 
			
		||||
    id("cc-tweaked.illuaminate")
 | 
			
		||||
    id("cc-tweaked.node")
 | 
			
		||||
    id("cc-tweaked.gametest")
 | 
			
		||||
    alias(libs.plugins.githubRelease)
 | 
			
		||||
    id("org.jetbrains.gradle.plugin.idea-ext")
 | 
			
		||||
    id("cc-tweaked")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val isStable = true
 | 
			
		||||
val isUnstable = project.properties["isUnstable"] == "true"
 | 
			
		||||
val modVersion: String by extra
 | 
			
		||||
val mcVersion: String by extra
 | 
			
		||||
 | 
			
		||||
group = "org.squiddev"
 | 
			
		||||
version = modVersion
 | 
			
		||||
base.archivesName.set("cc-tweaked-$mcVersion")
 | 
			
		||||
 | 
			
		||||
java.registerFeature("extraMods") { usingSourceSet(sourceSets.main.get()) }
 | 
			
		||||
 | 
			
		||||
sourceSets {
 | 
			
		||||
    main {
 | 
			
		||||
        resources.srcDir("src/generated/resources")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
minecraft {
 | 
			
		||||
    runs {
 | 
			
		||||
        // configureEach would be better, but we need to eagerly configure configs or otherwise the run task doesn't
 | 
			
		||||
        // get set up properly.
 | 
			
		||||
        all {
 | 
			
		||||
            lazyToken("minecraft_classpath") {
 | 
			
		||||
                configurations["shade"].copyRecursive().resolve().joinToString(File.pathSeparator) { it.absolutePath }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            property("forge.logging.markers", "REGISTRIES")
 | 
			
		||||
            property("forge.logging.console.level", "debug")
 | 
			
		||||
 | 
			
		||||
            forceExit = false
 | 
			
		||||
 | 
			
		||||
            mods.register("computercraft") { source(sourceSets.main.get()) }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val client by registering {
 | 
			
		||||
            workingDirectory(file("run"))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val server by registering {
 | 
			
		||||
            workingDirectory(file("run/server"))
 | 
			
		||||
            arg("--nogui")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val data by registering {
 | 
			
		||||
            workingDirectory(file("run"))
 | 
			
		||||
            args(
 | 
			
		||||
                "--mod",
 | 
			
		||||
                "computercraft",
 | 
			
		||||
                "--all",
 | 
			
		||||
                "--output",
 | 
			
		||||
                file("src/generated/resources/"),
 | 
			
		||||
                "--existing",
 | 
			
		||||
                file("src/main/resources/"),
 | 
			
		||||
            )
 | 
			
		||||
            property("cct.pretty-json", "true")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun RunConfig.configureForGameTest() {
 | 
			
		||||
            val old = lazyTokens.get("minecraft_classpath")
 | 
			
		||||
            lazyToken("minecraft_classpath") {
 | 
			
		||||
                // We do some terrible hacks here to basically find all things not already on the runtime classpath
 | 
			
		||||
                // and add them. /Except/ for our source sets, as those need to load inside the Minecraft classpath.
 | 
			
		||||
                val testMod = configurations["testModRuntimeClasspath"].resolve()
 | 
			
		||||
                val implementation = configurations.runtimeClasspath.get().resolve()
 | 
			
		||||
                val new = (testMod - implementation)
 | 
			
		||||
                    .asSequence()
 | 
			
		||||
                    .filter { it.isFile && !it.name.endsWith("-test-fixtures.jar") }
 | 
			
		||||
                    .map { it.absolutePath }
 | 
			
		||||
                    .joinToString(File.pathSeparator)
 | 
			
		||||
                if (old == null) new else old.get() + File.pathSeparator + new
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            property("cctest.sources", file("src/testMod/resources/data/cctest").absolutePath)
 | 
			
		||||
 | 
			
		||||
            arg("--mixin.config=computercraft-gametest.mixins.json")
 | 
			
		||||
 | 
			
		||||
            mods.register("cctest") {
 | 
			
		||||
                source(sourceSets["testMod"])
 | 
			
		||||
                source(sourceSets["testFixtures"])
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val testClient by registering {
 | 
			
		||||
            workingDirectory(file("run/testClient"))
 | 
			
		||||
            parent(client.get())
 | 
			
		||||
            configureForGameTest()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val gameTestServer by registering {
 | 
			
		||||
            workingDirectory(file("run/testServer"))
 | 
			
		||||
            configureForGameTest()
 | 
			
		||||
 | 
			
		||||
            property("forge.logging.console.level", "info")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mappings("parchment", "${libs.versions.parchmentMc.get()}-${libs.versions.parchment.get()}-$mcVersion")
 | 
			
		||||
 | 
			
		||||
    accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mixin {
 | 
			
		||||
    add(sourceSets.main.get(), "computercraft.mixins.refmap.json")
 | 
			
		||||
    config("computercraft.mixins.json")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
reobf {
 | 
			
		||||
    register("shadowJar")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
configurations {
 | 
			
		||||
    val shade by registering { isTransitive = false }
 | 
			
		||||
    implementation { extendsFrom(shade.get()) }
 | 
			
		||||
    register("cctJavadoc")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    minecraft("net.minecraftforge:forge:$mcVersion-${libs.versions.forge.get()}")
 | 
			
		||||
    annotationProcessor("org.spongepowered:mixin:0.8.5:processor")
 | 
			
		||||
 | 
			
		||||
    compileOnly(libs.jetbrainsAnnotations)
 | 
			
		||||
    annotationProcessorEverywhere(libs.autoService)
 | 
			
		||||
 | 
			
		||||
    "extraModsCompileOnly"(fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116:api"))
 | 
			
		||||
    "extraModsRuntimeOnly"(fg.deobf("mezz.jei:jei-1.18.2:9.4.1.116"))
 | 
			
		||||
    "extraModsCompileOnly"(fg.deobf("maven.modrinth:oculus:1.18.2-1.2.5"))
 | 
			
		||||
 | 
			
		||||
    "shade"(libs.cobalt)
 | 
			
		||||
 | 
			
		||||
    testFixturesApi(libs.bundles.test)
 | 
			
		||||
    testFixturesApi(libs.bundles.kotlin)
 | 
			
		||||
 | 
			
		||||
    testImplementation(libs.bundles.test)
 | 
			
		||||
    testImplementation(libs.bundles.kotlin)
 | 
			
		||||
    testRuntimeOnly(libs.bundles.testRuntime)
 | 
			
		||||
 | 
			
		||||
    "cctJavadoc"(libs.cctJavadoc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
illuaminate {
 | 
			
		||||
    version.set(libs.versions.illuaminate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Compile tasks
 | 
			
		||||
 | 
			
		||||
tasks.javadoc {
 | 
			
		||||
    include("dan200/computercraft/api/**/*.java")
 | 
			
		||||
    (options as StandardJavadocDocletOptions).links("https://docs.oracle.com/en/java/javase/17/docs/api/")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val apiJar by tasks.registering(Jar::class) {
 | 
			
		||||
    archiveClassifier.set("api")
 | 
			
		||||
    from(sourceSets.main.get().output) {
 | 
			
		||||
        include("dan200/computercraft/api/**/*")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.assemble { dependsOn(apiJar) }
 | 
			
		||||
 | 
			
		||||
val luaJavadoc by tasks.registering(Javadoc::class) {
 | 
			
		||||
    description = "Generates documentation for Java-side Lua functions."
 | 
			
		||||
    group = JavaBasePlugin.DOCUMENTATION_GROUP
 | 
			
		||||
 | 
			
		||||
    source(sourceSets.main.get().java)
 | 
			
		||||
    setDestinationDir(buildDir.resolve("docs/luaJavadoc"))
 | 
			
		||||
    classpath = sourceSets.main.get().compileClasspath
 | 
			
		||||
 | 
			
		||||
    options.docletpath = configurations["cctJavadoc"].files.toList()
 | 
			
		||||
    options.doclet = "cc.tweaked.javadoc.LuaDoclet"
 | 
			
		||||
    (options as StandardJavadocDocletOptions).noTimestamp(false)
 | 
			
		||||
 | 
			
		||||
    javadocTool.set(
 | 
			
		||||
        javaToolchains.javadocToolFor {
 | 
			
		||||
            languageVersion.set(cc.tweaked.gradle.CCTweakedPlugin.JAVA_VERSION)
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.processResources {
 | 
			
		||||
    inputs.property("modVersion", modVersion)
 | 
			
		||||
    inputs.property("forgeVersion", libs.versions.forge.get())
 | 
			
		||||
    inputs.property("gitHash", cct.gitHash)
 | 
			
		||||
 | 
			
		||||
    filesMatching("data/computercraft/lua/rom/help/credits.txt") {
 | 
			
		||||
        expand(mapOf("gitContributors" to cct.gitContributors.get().joinToString("\n")))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    filesMatching("META-INF/mods.toml") {
 | 
			
		||||
        expand(mapOf("forgeVersion" to libs.versions.forge.get(), "file" to mapOf("jarVersion" to modVersion)))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.jar {
 | 
			
		||||
    isReproducibleFileOrder = true
 | 
			
		||||
    isPreserveFileTimestamps = false
 | 
			
		||||
    finalizedBy("reobfJar")
 | 
			
		||||
    archiveClassifier.set("slim")
 | 
			
		||||
 | 
			
		||||
    manifest {
 | 
			
		||||
        attributes(
 | 
			
		||||
            "Specification-Title" to "computercraft",
 | 
			
		||||
            "Specification-Vendor" to "SquidDev",
 | 
			
		||||
            "Specification-Version" to "1",
 | 
			
		||||
            "Implementation-Title" to "cctweaked",
 | 
			
		||||
            "Implementation-Version" to modVersion,
 | 
			
		||||
            "Implementation-Vendor" to "SquidDev",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.shadowJar {
 | 
			
		||||
    finalizedBy("reobfShadowJar")
 | 
			
		||||
 | 
			
		||||
    archiveClassifier.set("")
 | 
			
		||||
    configurations = listOf(project.configurations["shade"])
 | 
			
		||||
    relocate("org.squiddev.cobalt", "cc.tweaked.internal.cobalt")
 | 
			
		||||
    minimize()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.assemble { dependsOn("shadowJar") }
 | 
			
		||||
 | 
			
		||||
// Web tasks
 | 
			
		||||
 | 
			
		||||
val rollup by tasks.registering(NpxExecToDir::class) {
 | 
			
		||||
    group = LifecycleBasePlugin.BUILD_GROUP
 | 
			
		||||
    description = "Bundles JS into rollup"
 | 
			
		||||
 | 
			
		||||
    // Sources
 | 
			
		||||
    inputs.files(fileTree("src/web")).withPropertyName("sources")
 | 
			
		||||
    // Config files
 | 
			
		||||
    inputs.file("tsconfig.json").withPropertyName("Typescript config")
 | 
			
		||||
    inputs.file("rollup.config.js").withPropertyName("Rollup config")
 | 
			
		||||
 | 
			
		||||
    // Output directory. Also defined in illuaminate.sexp and rollup.config.js
 | 
			
		||||
    output.set(buildDir.resolve("rollup"))
 | 
			
		||||
 | 
			
		||||
    args = listOf("rollup", "--config", "rollup.config.js")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val illuaminateDocs by tasks.registering(IlluaminateExecToDir::class) {
 | 
			
		||||
    group = JavaBasePlugin.DOCUMENTATION_GROUP
 | 
			
		||||
    description = "Generates docs using Illuaminate"
 | 
			
		||||
 | 
			
		||||
    // Config files
 | 
			
		||||
    inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
 | 
			
		||||
    // Sources
 | 
			
		||||
    inputs.files(fileTree("doc")).withPropertyName("docs")
 | 
			
		||||
    inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
 | 
			
		||||
    inputs.files(luaJavadoc)
 | 
			
		||||
    // Additional assets
 | 
			
		||||
    inputs.files(rollup)
 | 
			
		||||
    inputs.file("src/web/styles.css").withPropertyName("styles")
 | 
			
		||||
 | 
			
		||||
    // Output directory. Also defined in illuaminate.sexp and transform.tsx
 | 
			
		||||
    output.set(buildDir.resolve("illuaminate"))
 | 
			
		||||
 | 
			
		||||
    args = listOf("doc-gen")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val jsxDocs by tasks.registering(NpxExecToDir::class) {
 | 
			
		||||
    group = JavaBasePlugin.DOCUMENTATION_GROUP
 | 
			
		||||
    description = "Post-processes documentation to statically render some dynamic content."
 | 
			
		||||
 | 
			
		||||
    // Config files
 | 
			
		||||
    inputs.file("tsconfig.json").withPropertyName("Typescript config")
 | 
			
		||||
    // Sources
 | 
			
		||||
    inputs.files(fileTree("src/web")).withPropertyName("sources")
 | 
			
		||||
    inputs.file("src/generated/export/index.json").withPropertyName("export")
 | 
			
		||||
    inputs.files(illuaminateDocs)
 | 
			
		||||
 | 
			
		||||
    // Output directory. Also defined in src/web/transform.tsx
 | 
			
		||||
    output.set(buildDir.resolve("jsxDocs"))
 | 
			
		||||
 | 
			
		||||
    args = listOf("ts-node", "-T", "--esm", "src/web/transform.tsx")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val docWebsite by tasks.registering(Copy::class) {
 | 
			
		||||
    group = JavaBasePlugin.DOCUMENTATION_GROUP
 | 
			
		||||
    description = "Assemble docs and assets together into the documentation website."
 | 
			
		||||
 | 
			
		||||
    from(jsxDocs)
 | 
			
		||||
 | 
			
		||||
    from("doc") {
 | 
			
		||||
        include("logo.png")
 | 
			
		||||
        include("images/**")
 | 
			
		||||
    }
 | 
			
		||||
    from(rollup) { exclude("index.js") }
 | 
			
		||||
    from(illuaminateDocs) { exclude("**/*.html") }
 | 
			
		||||
    from("src/generated/export/items") { into("images/items") }
 | 
			
		||||
 | 
			
		||||
    into(buildDir.resolve("docs/site"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check tasks
 | 
			
		||||
 | 
			
		||||
tasks.test {
 | 
			
		||||
    systemProperty("cct.test-files", buildDir.resolve("tmp/testFiles").absolutePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val lintLua by tasks.registering(IlluaminateExec::class) {
 | 
			
		||||
    group = JavaBasePlugin.VERIFICATION_GROUP
 | 
			
		||||
    description = "Lint Lua (and Lua docs) with illuaminate"
 | 
			
		||||
 | 
			
		||||
    // Config files
 | 
			
		||||
    inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
 | 
			
		||||
    // Sources
 | 
			
		||||
    inputs.files(fileTree("doc")).withPropertyName("docs")
 | 
			
		||||
    inputs.files(fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
 | 
			
		||||
    inputs.files(luaJavadoc)
 | 
			
		||||
 | 
			
		||||
    args = listOf("lint")
 | 
			
		||||
 | 
			
		||||
    doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
 | 
			
		||||
    doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val runGametest by tasks.registering(JavaExec::class) {
 | 
			
		||||
    group = LifecycleBasePlugin.VERIFICATION_GROUP
 | 
			
		||||
    description = "Runs tests on a temporary Minecraft instance."
 | 
			
		||||
    dependsOn("cleanRunGametest")
 | 
			
		||||
 | 
			
		||||
    // Copy from runGameTestServer. We do it in this slightly odd way as runGameTestServer
 | 
			
		||||
    // isn't created until the task is configured (which is no good for us).
 | 
			
		||||
    val exec = tasks.getByName<JavaExec>("runGameTestServer")
 | 
			
		||||
    dependsOn(exec.dependsOn)
 | 
			
		||||
    exec.copyToFull(this)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
cct.jacoco(runGametest)
 | 
			
		||||
 | 
			
		||||
tasks.check { dependsOn(runGametest) }
 | 
			
		||||
 | 
			
		||||
// Upload tasks
 | 
			
		||||
 | 
			
		||||
val checkChangelog by tasks.registering(CheckChangelog::class) {
 | 
			
		||||
    version.set(modVersion)
 | 
			
		||||
    whatsNew.set(file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md"))
 | 
			
		||||
    changelog.set(file("src/main/resources/data/computercraft/lua/rom/help/changelog.md"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.check { dependsOn(checkChangelog) }
 | 
			
		||||
 | 
			
		||||
val publishCurseForge by tasks.registering(TaskPublishCurseForge::class) {
 | 
			
		||||
    group = PublishingPlugin.PUBLISH_TASK_GROUP
 | 
			
		||||
    description = "Upload artifacts to CurseForge"
 | 
			
		||||
 | 
			
		||||
    apiToken = findProperty("curseForgeApiKey") ?: ""
 | 
			
		||||
    enabled = apiToken != ""
 | 
			
		||||
 | 
			
		||||
    val mainFile = upload("282001", tasks.shadowJar.get().archiveFile)
 | 
			
		||||
    dependsOn(tasks.shadowJar) // Ughr.
 | 
			
		||||
    mainFile.changelog = "Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion)."
 | 
			
		||||
    mainFile.changelogType = "markdown"
 | 
			
		||||
    mainFile.releaseType = if (isStable) "release" else "alpha"
 | 
			
		||||
    mainFile.gameVersions.add(mcVersion)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.publish { dependsOn(publishCurseForge) }
 | 
			
		||||
 | 
			
		||||
modrinth {
 | 
			
		||||
    token.set(findProperty("modrinthApiKey") as String? ?: "")
 | 
			
		||||
    projectId.set("gu7yAYhd")
 | 
			
		||||
    versionNumber.set("$mcVersion-$modVersion")
 | 
			
		||||
    versionName.set(modVersion)
 | 
			
		||||
    versionType.set(if (isStable) "release" else "alpha")
 | 
			
		||||
    uploadFile.set(tasks.shadowJar as Any)
 | 
			
		||||
    gameVersions.add(mcVersion)
 | 
			
		||||
    changelog.set("Release notes can be found on the [GitHub repository](https://github.com/cc-tweaked/CC-Tweaked/releases/tag/v$mcVersion-$modVersion).")
 | 
			
		||||
 | 
			
		||||
    syncBodyFrom.set(provider { file("doc/mod-page.md").readText() })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.publish { dependsOn(tasks.modrinth) }
 | 
			
		||||
 | 
			
		||||
githubRelease {
 | 
			
		||||
    token(findProperty("githubApiKey") as String? ?: "")
 | 
			
		||||
    owner.set("cc-tweaked")
 | 
			
		||||
@@ -405,54 +23,30 @@ githubRelease {
 | 
			
		||||
    releaseName.set("[$mcVersion] $modVersion")
 | 
			
		||||
    body.set(
 | 
			
		||||
        provider {
 | 
			
		||||
            "## " + file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
 | 
			
		||||
            "## " + project(":core").file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
 | 
			
		||||
                .readLines()
 | 
			
		||||
                .takeWhile { it != "Type \"help changelog\" to see the full version history." }
 | 
			
		||||
                .joinToString("\n").trim()
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
    prerelease.set(!isStable)
 | 
			
		||||
    prerelease.set(isUnstable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.publish { dependsOn(tasks.githubRelease) }
 | 
			
		||||
 | 
			
		||||
publishing {
 | 
			
		||||
    publications {
 | 
			
		||||
        register<MavenPublication>("maven") {
 | 
			
		||||
            artifactId = base.archivesName.get()
 | 
			
		||||
            from(components["java"])
 | 
			
		||||
            artifact(apiJar)
 | 
			
		||||
            fg.component(this)
 | 
			
		||||
 | 
			
		||||
            pom {
 | 
			
		||||
                name.set("CC: Tweaked")
 | 
			
		||||
                description.set("CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.")
 | 
			
		||||
                url.set("https://github.com/cc-tweaked/CC-Tweaked")
 | 
			
		||||
 | 
			
		||||
                scm {
 | 
			
		||||
                    url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                issueManagement {
 | 
			
		||||
                    system.set("github")
 | 
			
		||||
                    url.set("https://github.com/cc-tweaked/CC-Tweaked/issues")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                licenses {
 | 
			
		||||
                    license {
 | 
			
		||||
                        name.set("ComputerCraft Public License, Version 1.0")
 | 
			
		||||
                        url.set("https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
idea.project.settings.compiler.javac {
 | 
			
		||||
    // We want ErrorProne to be present when compiling via IntelliJ, as it offers some helpful warnings
 | 
			
		||||
    // and errors. Loop through our source sets and find the appropriate flags.
 | 
			
		||||
    moduleJavacAdditionalOptions = subprojects
 | 
			
		||||
        .asSequence()
 | 
			
		||||
        .map { evaluationDependsOn(it.path) }
 | 
			
		||||
        .flatMap { project ->
 | 
			
		||||
            val sourceSets = project.extensions.findByType(SourceSetContainer::class) ?: return@flatMap sequenceOf()
 | 
			
		||||
            sourceSets.asSequence().map { sourceSet ->
 | 
			
		||||
                val name = "${idea.project.name}.${project.name}.${sourceSet.name}"
 | 
			
		||||
                val compile = project.tasks.named(sourceSet.compileJavaTaskName, JavaCompile::class).get()
 | 
			
		||||
                name to compile.options.allCompilerArgs.joinToString(" ") { if (it.contains(" ")) "\"$it\"" else it }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    repositories {
 | 
			
		||||
        maven("https://squiddev.cc/maven") {
 | 
			
		||||
            name = "SquidDev"
 | 
			
		||||
 | 
			
		||||
            credentials(PasswordCredentials::class)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        .toMap()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,14 +3,51 @@ plugins {
 | 
			
		||||
    `kotlin-dsl`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Duplicated in settings.gradle.kts
 | 
			
		||||
repositories {
 | 
			
		||||
    mavenCentral()
 | 
			
		||||
    gradlePluginPortal()
 | 
			
		||||
 | 
			
		||||
    maven("https://maven.minecraftforge.net") {
 | 
			
		||||
        name = "Forge"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("net.minecraftforge")
 | 
			
		||||
            includeGroup("net.minecraftforge.gradle")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maven("https://maven.parchmentmc.org") {
 | 
			
		||||
        name = "Librarian"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroupByRegex("^org\\.parchmentmc.*")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maven("https://repo.spongepowered.org/repository/maven-public/") {
 | 
			
		||||
        name = "Sponge"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("org.spongepowered")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maven("https://maven.fabricmc.net/") {
 | 
			
		||||
        name = "Fabric"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("net.fabricmc")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation(libs.errorProne.plugin)
 | 
			
		||||
    implementation(libs.kotlin.plugin)
 | 
			
		||||
    implementation(libs.spotless)
 | 
			
		||||
 | 
			
		||||
    implementation(libs.fabric.loom)
 | 
			
		||||
    implementation(libs.forgeGradle)
 | 
			
		||||
    implementation(libs.librarian)
 | 
			
		||||
    implementation(libs.quiltflower)
 | 
			
		||||
    implementation(libs.vanillaGradle)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gradlePlugin {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								buildSrc/src/main/kotlin/cc-tweaked.fabric.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								buildSrc/src/main/kotlin/cc-tweaked.fabric.gradle.kts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
/** Default configuration for Fabric projects. */
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedExtension
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedPlugin
 | 
			
		||||
import cc.tweaked.gradle.IdeaRunConfigurations
 | 
			
		||||
import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    `java-library`
 | 
			
		||||
    id("fabric-loom")
 | 
			
		||||
    id("io.github.juuxel.loom-quiltflower")
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins.apply(CCTweakedPlugin::class.java)
 | 
			
		||||
 | 
			
		||||
val mcVersion: String by extra
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
    maven("https://maven.parchmentmc.org/") {
 | 
			
		||||
        name = "Parchment"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("org.parchmentmc.data")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
loom {
 | 
			
		||||
    splitEnvironmentSourceSets()
 | 
			
		||||
    splitModDependencies.set(true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MinecraftConfigurations.setup(project)
 | 
			
		||||
 | 
			
		||||
extensions.configure(CCTweakedExtension::class.java) {
 | 
			
		||||
    linters(minecraft = true, loader = "fabric")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
 | 
			
		||||
    minecraft("com.mojang:minecraft:$mcVersion")
 | 
			
		||||
    mappings(
 | 
			
		||||
        loom.layered {
 | 
			
		||||
            officialMojangMappings()
 | 
			
		||||
            parchment(
 | 
			
		||||
                project.dependencies.create(
 | 
			
		||||
                    group = "org.parchmentmc.data",
 | 
			
		||||
                    name = "parchment-${libs.findVersion("parchmentMc").get()}",
 | 
			
		||||
                    version = libs.findVersion("parchment").get().toString(),
 | 
			
		||||
                    ext = "zip",
 | 
			
		||||
                ),
 | 
			
		||||
            )
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    modImplementation(libs.findLibrary("fabric-loader").get())
 | 
			
		||||
    modImplementation(libs.findLibrary("fabric-api").get())
 | 
			
		||||
 | 
			
		||||
    // Depend on error prone annotations to silence a lot of compile warnings.
 | 
			
		||||
    compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.ideaSyncTask {
 | 
			
		||||
    doLast { IdeaRunConfigurations(project).patch() }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								buildSrc/src/main/kotlin/cc-tweaked.forge.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								buildSrc/src/main/kotlin/cc-tweaked.forge.gradle.kts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
/** Default configuration for Forge projects. */
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedExtension
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedPlugin
 | 
			
		||||
import cc.tweaked.gradle.IdeaRunConfigurations
 | 
			
		||||
import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
    id("net.minecraftforge.gradle")
 | 
			
		||||
    id("org.parchmentmc.librarian.forgegradle")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins.apply(CCTweakedPlugin::class.java)
 | 
			
		||||
 | 
			
		||||
val mcVersion: String by extra
 | 
			
		||||
 | 
			
		||||
minecraft {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
 | 
			
		||||
 | 
			
		||||
    accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MinecraftConfigurations.setup(project)
 | 
			
		||||
 | 
			
		||||
extensions.configure(CCTweakedExtension::class.java) {
 | 
			
		||||
    linters(minecraft = true, loader = "forge")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    "minecraft"("net.minecraftforge:forge:$mcVersion-${libs.findVersion("forge").get()}")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.configureEach {
 | 
			
		||||
    // genIntellijRuns isn't registered until much later, so we need this silly hijinks.
 | 
			
		||||
    if (name == "genIntellijRuns") doLast { IdeaRunConfigurations(project).patch() }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 | 
			
		||||
import cc.tweaked.gradle.clientClasses
 | 
			
		||||
import cc.tweaked.gradle.commonClasses
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sets up the configurations for writing game tests.
 | 
			
		||||
@@ -11,12 +12,13 @@ plugins {
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val main = sourceSets.main.get()
 | 
			
		||||
val main = sourceSets["main"]
 | 
			
		||||
val client = sourceSets["client"]
 | 
			
		||||
 | 
			
		||||
// Both testMod and testFixtures inherit from the main classpath, just so we have access to Minecraft classes.
 | 
			
		||||
// Both testMod and testFixtures inherit from the main and client classpath, just so we have access to Minecraft classes.
 | 
			
		||||
val testMod by sourceSets.creating {
 | 
			
		||||
    compileClasspath += main.compileClasspath
 | 
			
		||||
    runtimeClasspath += main.runtimeClasspath
 | 
			
		||||
    compileClasspath += main.compileClasspath + client.compileClasspath
 | 
			
		||||
    runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
configurations {
 | 
			
		||||
@@ -32,12 +34,13 @@ configurations {
 | 
			
		||||
// Like the main test configurations, we're safe to depend on source set outputs.
 | 
			
		||||
dependencies {
 | 
			
		||||
    add(testMod.implementationConfigurationName, main.output)
 | 
			
		||||
    add(testMod.implementationConfigurationName, client.output)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Similar to java-test-fixtures, but tries to avoid putting the obfuscated jar on the classpath.
 | 
			
		||||
 | 
			
		||||
val testFixtures by sourceSets.creating {
 | 
			
		||||
    compileClasspath += main.compileClasspath
 | 
			
		||||
    compileClasspath += main.compileClasspath + client.compileClasspath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
java.registerFeature("testFixtures") {
 | 
			
		||||
@@ -46,8 +49,12 @@ java.registerFeature("testFixtures") {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    add(testFixtures.implementationConfigurationName, main.output)
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    add(testFixtures.apiConfigurationName, libs.findBundle("test").get())
 | 
			
		||||
    // Consumers of this project already have the common and client classes on the classpath, so it's fine for these
 | 
			
		||||
    // to be compile-only.
 | 
			
		||||
    add(testFixtures.compileOnlyApiConfigurationName, commonClasses(project))
 | 
			
		||||
    add(testFixtures.compileOnlyApiConfigurationName, clientClasses(project))
 | 
			
		||||
 | 
			
		||||
    testImplementation(testFixtures(project))
 | 
			
		||||
    add(testMod.implementationConfigurationName, testFixtures(project))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,35 @@
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedExtension
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedPlugin
 | 
			
		||||
import cc.tweaked.gradle.LicenseHeader
 | 
			
		||||
import com.diffplug.gradle.spotless.FormatExtension
 | 
			
		||||
import com.diffplug.spotless.LineEnding
 | 
			
		||||
import net.ltgt.gradle.errorprone.CheckSeverity
 | 
			
		||||
import net.ltgt.gradle.errorprone.errorprone
 | 
			
		||||
import java.nio.charset.StandardCharsets
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    `java-library`
 | 
			
		||||
    idea
 | 
			
		||||
    jacoco
 | 
			
		||||
    checkstyle
 | 
			
		||||
    id("com.diffplug.spotless")
 | 
			
		||||
    id("net.ltgt.errorprone")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val modVersion: String by extra
 | 
			
		||||
val mcVersion: String by extra
 | 
			
		||||
 | 
			
		||||
group = "cc.tweaked"
 | 
			
		||||
version = modVersion
 | 
			
		||||
 | 
			
		||||
base.archivesName.convention("cc-tweaked-$mcVersion-${project.name}")
 | 
			
		||||
 | 
			
		||||
java {
 | 
			
		||||
    toolchain {
 | 
			
		||||
        languageVersion.set(CCTweakedPlugin.JAVA_VERSION)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    withSourcesJar()
 | 
			
		||||
    withJavadocJar()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
@@ -28,10 +40,15 @@ repositories {
 | 
			
		||||
            includeGroup("org.squiddev")
 | 
			
		||||
            includeGroup("cc.tweaked")
 | 
			
		||||
            // Things we mirror
 | 
			
		||||
            includeGroup("com.blamejared.crafttweaker")
 | 
			
		||||
            includeGroup("commoble.morered")
 | 
			
		||||
            includeGroup("dev.architectury")
 | 
			
		||||
            includeGroup("maven.modrinth")
 | 
			
		||||
            includeGroup("me.shedaniel")
 | 
			
		||||
            includeGroup("me.shedaniel.cloth")
 | 
			
		||||
            includeGroup("mezz.jei")
 | 
			
		||||
            includeModule("com.terraformersmc", "modmenu")
 | 
			
		||||
            includeModule("fuzs.forgeconfigapiport", "forgeconfigapiport-fabric")
 | 
			
		||||
            // Until https://github.com/SpongePowered/Mixin/pull/593 is merged
 | 
			
		||||
            includeModule("org.spongepowered", "mixin")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -39,6 +56,9 @@ repositories {
 | 
			
		||||
dependencies {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
    checkstyle(libs.findLibrary("checkstyle").get())
 | 
			
		||||
 | 
			
		||||
    errorprone(libs.findLibrary("errorProne-core").get())
 | 
			
		||||
    errorprone(libs.findLibrary("nullAway").get())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Configure default JavaCompile tasks with our arguments.
 | 
			
		||||
@@ -46,13 +66,68 @@ sourceSets.all {
 | 
			
		||||
    tasks.named(compileJavaTaskName, JavaCompile::class.java) {
 | 
			
		||||
        // Processing just gives us "No processor claimed any of these annotations", so skip that!
 | 
			
		||||
        options.compilerArgs.addAll(listOf("-Xlint", "-Xlint:-processing"))
 | 
			
		||||
 | 
			
		||||
        options.errorprone {
 | 
			
		||||
            check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
 | 
			
		||||
            check("InvalidParam", CheckSeverity.OFF) // Broken by records.
 | 
			
		||||
            check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally
 | 
			
		||||
            // Too many false positives right now. Maybe we need an indirection for it later on.
 | 
			
		||||
            check("ReferenceEquality", CheckSeverity.OFF)
 | 
			
		||||
            check("UnusedVariable", CheckSeverity.OFF) // Too many false positives with records.
 | 
			
		||||
            check("OperatorPrecedence", CheckSeverity.OFF) // For now.
 | 
			
		||||
            check("AlreadyChecked", CheckSeverity.OFF) // Seems to be broken?
 | 
			
		||||
            check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
 | 
			
		||||
            check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
 | 
			
		||||
 | 
			
		||||
            check("NullAway", CheckSeverity.ERROR)
 | 
			
		||||
            option("NullAway:AnnotatedPackages", listOf("dan200.computercraft", "net.fabricmc.fabric.api").joinToString(","))
 | 
			
		||||
            option("NullAway:ExcludedFieldAnnotations", listOf("org.spongepowered.asm.mixin.Shadow").joinToString(","))
 | 
			
		||||
            option("NullAway:CastToNonNullMethod", "dan200.computercraft.core.util.Nullability.assertNonNull")
 | 
			
		||||
            option("NullAway:CheckOptionalEmptiness")
 | 
			
		||||
            option("NullAway:AcknowledgeRestrictiveAnnotations")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.compileTestJava {
 | 
			
		||||
    options.errorprone {
 | 
			
		||||
        check("NullAway", CheckSeverity.OFF)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
tasks.withType(JavaCompile::class.java).configureEach {
 | 
			
		||||
    options.encoding = "UTF-8"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.withType(AbstractArchiveTask::class.java).configureEach {
 | 
			
		||||
    isPreserveFileTimestamps = false
 | 
			
		||||
    isReproducibleFileOrder = true
 | 
			
		||||
    dirMode = Integer.valueOf("755", 8)
 | 
			
		||||
    fileMode = Integer.valueOf("664", 8)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.jar {
 | 
			
		||||
    manifest {
 | 
			
		||||
        attributes(
 | 
			
		||||
            "Specification-Title" to "computercraft",
 | 
			
		||||
            "Specification-Vendor" to "SquidDev",
 | 
			
		||||
            "Specification-Version" to "1",
 | 
			
		||||
            "Implementation-Title" to "cctweaked-${project.name}",
 | 
			
		||||
            "Implementation-Version" to modVersion,
 | 
			
		||||
            "Implementation-Vendor" to "SquidDev",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.javadoc {
 | 
			
		||||
    options {
 | 
			
		||||
        val stdOptions = this as StandardJavadocDocletOptions
 | 
			
		||||
        stdOptions.addBooleanOption("Xdoclint:all,-missing", true)
 | 
			
		||||
        stdOptions.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.test {
 | 
			
		||||
    finalizedBy("jacocoTestReport")
 | 
			
		||||
 | 
			
		||||
@@ -67,6 +142,14 @@ tasks.withType(JacocoReport::class.java).configureEach {
 | 
			
		||||
    reports.html.required.set(true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
project.plugins.withType(CCTweakedPlugin::class.java) {
 | 
			
		||||
    // Set up jacoco to read from /all/ our source directories.
 | 
			
		||||
    val cct = project.extensions.getByType<CCTweakedExtension>()
 | 
			
		||||
    project.tasks.named("jacocoTestReport", JacocoReport::class.java) {
 | 
			
		||||
        for (ref in cct.sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
spotless {
 | 
			
		||||
    encoding = StandardCharsets.UTF_8
 | 
			
		||||
    lineEndings = LineEnding.UNIX
 | 
			
		||||
@@ -78,8 +161,8 @@ spotless {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val licenser = LicenseHeader.create(
 | 
			
		||||
        api = file("config/license/api.txt"),
 | 
			
		||||
        main = file("config/license/main.txt"),
 | 
			
		||||
        api = rootProject.file("config/license/api.txt"),
 | 
			
		||||
        main = rootProject.file("config/license/main.txt"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    java {
 | 
			
		||||
@@ -104,3 +187,12 @@ spotless {
 | 
			
		||||
        ktlint().editorConfigOverride(ktlintConfig)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
idea.module {
 | 
			
		||||
    excludeDirs.addAll(project.files("run", "out", "logs").files)
 | 
			
		||||
 | 
			
		||||
    // Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes.
 | 
			
		||||
    // This is required for Loom, and we patch Forge's run configurations to work there.
 | 
			
		||||
    // TODO: Submit a patch to Forge to support ProjectRootManager.
 | 
			
		||||
    inheritOutputDirs = true
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								buildSrc/src/main/kotlin/cc-tweaked.publishing.gradle.kts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import org.gradle.kotlin.dsl.`maven-publish`
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    `java-library`
 | 
			
		||||
    `maven-publish`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
publishing {
 | 
			
		||||
    publications {
 | 
			
		||||
        register<MavenPublication>("maven") {
 | 
			
		||||
            artifactId = base.archivesName.get()
 | 
			
		||||
            from(components["java"])
 | 
			
		||||
 | 
			
		||||
            pom {
 | 
			
		||||
                name.set("CC: Tweaked")
 | 
			
		||||
                description.set("CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles and more to Minecraft.")
 | 
			
		||||
                url.set("https://github.com/cc-tweaked/CC-Tweaked")
 | 
			
		||||
 | 
			
		||||
                scm {
 | 
			
		||||
                    url.set("https://github.com/cc-tweaked/CC-Tweaked.git")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                issueManagement {
 | 
			
		||||
                    system.set("github")
 | 
			
		||||
                    url.set("https://github.com/cc-tweaked/CC-Tweaked/issues")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                licenses {
 | 
			
		||||
                    license {
 | 
			
		||||
                        name.set("ComputerCraft Public License, Version 1.0")
 | 
			
		||||
                        url.set("https://github.com/cc-tweaked/CC-Tweaked/blob/HEAD/LICENSE")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    repositories {
 | 
			
		||||
        maven("https://squiddev.cc/maven") {
 | 
			
		||||
            name = "SquidDev"
 | 
			
		||||
 | 
			
		||||
            credentials(PasswordCredentials::class)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								buildSrc/src/main/kotlin/cc-tweaked.vanilla.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								buildSrc/src/main/kotlin/cc-tweaked.vanilla.gradle.kts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
/** Default configuration for non-modloader-specific Minecraft projects. */
 | 
			
		||||
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedExtension
 | 
			
		||||
import cc.tweaked.gradle.CCTweakedPlugin
 | 
			
		||||
import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
    id("org.spongepowered.gradle.vanilla")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
plugins.apply(CCTweakedPlugin::class.java)
 | 
			
		||||
 | 
			
		||||
val mcVersion: String by extra
 | 
			
		||||
 | 
			
		||||
minecraft {
 | 
			
		||||
    version(mcVersion)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
 | 
			
		||||
 | 
			
		||||
    // Depend on error prone annotations to silence a lot of compile warnings.
 | 
			
		||||
    compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MinecraftConfigurations.setup(project)
 | 
			
		||||
 | 
			
		||||
extensions.configure(CCTweakedExtension::class.java) {
 | 
			
		||||
    linters(minecraft = true, loader = null)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +1,35 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import net.ltgt.gradle.errorprone.CheckSeverity
 | 
			
		||||
import net.ltgt.gradle.errorprone.errorprone
 | 
			
		||||
import org.gradle.api.GradleException
 | 
			
		||||
import org.gradle.api.NamedDomainObjectProvider
 | 
			
		||||
import org.gradle.api.Project
 | 
			
		||||
import org.gradle.api.Task
 | 
			
		||||
import org.gradle.api.attributes.TestSuiteType
 | 
			
		||||
import org.gradle.api.file.FileSystemOperations
 | 
			
		||||
import org.gradle.api.plugins.JavaPluginExtension
 | 
			
		||||
import org.gradle.api.provider.Provider
 | 
			
		||||
import org.gradle.api.provider.SetProperty
 | 
			
		||||
import org.gradle.api.reporting.ReportingExtension
 | 
			
		||||
import org.gradle.api.tasks.JavaExec
 | 
			
		||||
import org.gradle.api.tasks.SourceSetContainer
 | 
			
		||||
import org.gradle.api.tasks.SourceSet
 | 
			
		||||
import org.gradle.api.tasks.bundling.Jar
 | 
			
		||||
import org.gradle.api.tasks.compile.JavaCompile
 | 
			
		||||
import org.gradle.api.tasks.javadoc.Javadoc
 | 
			
		||||
import org.gradle.configurationcache.extensions.capitalized
 | 
			
		||||
import org.gradle.kotlin.dsl.get
 | 
			
		||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
 | 
			
		||||
import org.gradle.language.jvm.tasks.ProcessResources
 | 
			
		||||
import org.gradle.process.JavaForkOptions
 | 
			
		||||
import org.gradle.testing.jacoco.plugins.JacocoCoverageReport
 | 
			
		||||
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
 | 
			
		||||
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
 | 
			
		||||
import org.gradle.testing.jacoco.tasks.JacocoReport
 | 
			
		||||
import java.io.BufferedWriter
 | 
			
		||||
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
 | 
			
		||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import java.io.OutputStreamWriter
 | 
			
		||||
import java.net.URI
 | 
			
		||||
import java.net.URL
 | 
			
		||||
import java.util.regex.Pattern
 | 
			
		||||
 | 
			
		||||
abstract class CCTweakedExtension(
 | 
			
		||||
@@ -26,45 +38,137 @@ abstract class CCTweakedExtension(
 | 
			
		||||
) {
 | 
			
		||||
    /** Get the hash of the latest git commit. */
 | 
			
		||||
    val gitHash: Provider<String> = gitProvider(project, "<no git hash>") {
 | 
			
		||||
        ProcessHelpers.captureOut("git", "-C", project.projectDir.absolutePath, "rev-parse", "HEAD").trim()
 | 
			
		||||
        ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "HEAD").trim()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Get the current git branch. */
 | 
			
		||||
    val gitBranch: Provider<String> = gitProvider(project, "<no git branch>") {
 | 
			
		||||
        ProcessHelpers.captureOut("git", "-C", project.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD").trim()
 | 
			
		||||
        ProcessHelpers.captureOut("git", "-C", project.rootProject.projectDir.absolutePath, "rev-parse", "--abbrev-ref", "HEAD")
 | 
			
		||||
            .trim()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** Get a list of all contributors to the project. */
 | 
			
		||||
    val gitContributors: Provider<List<String>> = gitProvider(project, listOf()) {
 | 
			
		||||
        val authors: Set<String> = HashSet(
 | 
			
		||||
            ProcessHelpers.captureLines(
 | 
			
		||||
                "git", "-C", project.projectDir.absolutePath, "log",
 | 
			
		||||
                "--format=tformat:%an <%ae>%n%cn <%ce>%n%(trailers:key=Co-authored-by,valueonly)",
 | 
			
		||||
            ),
 | 
			
		||||
        ProcessHelpers.captureLines(
 | 
			
		||||
            "git", "-C", project.rootProject.projectDir.absolutePath, "shortlog", "-ns",
 | 
			
		||||
            "--group=author", "--group=trailer:co-authored-by", "HEAD",
 | 
			
		||||
        )
 | 
			
		||||
        val process = ProcessHelpers.startProcess("git", "check-mailmap", "--stdin")
 | 
			
		||||
        BufferedWriter(OutputStreamWriter(process.outputStream)).use { writer ->
 | 
			
		||||
            for (authorName in authors) {
 | 
			
		||||
                var author = authorName
 | 
			
		||||
 | 
			
		||||
                if (author.isEmpty()) continue
 | 
			
		||||
                if (!author.endsWith(">")) author += ">" // Some commits have broken Co-Authored-By lines!
 | 
			
		||||
                writer.write(author)
 | 
			
		||||
                writer.newLine()
 | 
			
		||||
            .asSequence()
 | 
			
		||||
            .map {
 | 
			
		||||
                val matcher = COMMIT_COUNTS.matcher(it)
 | 
			
		||||
                matcher.find()
 | 
			
		||||
                matcher.group(1)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        val contributors: MutableSet<String> = HashSet()
 | 
			
		||||
        for (authorLine in ProcessHelpers.captureLines(process)) {
 | 
			
		||||
            val matcher = EMAIL.matcher(authorLine)
 | 
			
		||||
            matcher.find()
 | 
			
		||||
            val name = matcher.group(1)
 | 
			
		||||
            if (!IGNORED_USERS.contains(name)) contributors.add(name)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        contributors.sortedWith(String.CASE_INSENSITIVE_ORDER)
 | 
			
		||||
            .filter { !IGNORED_USERS.contains(it) }
 | 
			
		||||
            .toList()
 | 
			
		||||
            .sortedWith(String.CASE_INSENSITIVE_ORDER)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun jacoco(task: NamedDomainObjectProvider<JavaExec>) {
 | 
			
		||||
    /**
 | 
			
		||||
     * References to other sources
 | 
			
		||||
     */
 | 
			
		||||
    val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
 | 
			
		||||
 | 
			
		||||
    /** All source sets referenced by this project. */
 | 
			
		||||
    val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        sourceDirectories.finalizeValueOnRead()
 | 
			
		||||
        project.afterEvaluate { sourceDirectories.disallowChanges() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Mark this project as consuming another project. Its [sourceDirectories] are added, allowing easier configuration
 | 
			
		||||
     * of run configurations and other tasks which consume sources/classes.
 | 
			
		||||
     */
 | 
			
		||||
    fun externalSources(project: Project) {
 | 
			
		||||
        val otherCct = project.extensions.getByType(CCTweakedExtension::class.java)
 | 
			
		||||
        for (sourceSet in otherCct.sourceDirectories.get()) {
 | 
			
		||||
            sourceDirectories.add(SourceSetReference(sourceSet.sourceSet, classes = sourceSet.classes, external = true))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a dependency on another project such that its sources and compiles are processed with this one.
 | 
			
		||||
     *
 | 
			
		||||
     * This is used when importing a common library into a loader-specific one, as we want to compile sources using
 | 
			
		||||
     * the loader-specific sources.
 | 
			
		||||
     */
 | 
			
		||||
    fun inlineProject(path: String) {
 | 
			
		||||
        val otherProject = project.evaluationDependsOn(path)
 | 
			
		||||
        val otherJava = otherProject.extensions.getByType(JavaPluginExtension::class.java)
 | 
			
		||||
        val main = otherJava.sourceSets.getByName("main")
 | 
			
		||||
        val client = otherJava.sourceSets.getByName("client")
 | 
			
		||||
        val testMod = otherJava.sourceSets.findByName("testMod")
 | 
			
		||||
        val testFixtures = otherJava.sourceSets.findByName("testFixtures")
 | 
			
		||||
 | 
			
		||||
        // Pull in sources from the other project.
 | 
			
		||||
        extendSourceSet(otherProject, main)
 | 
			
		||||
        extendSourceSet(otherProject, client)
 | 
			
		||||
        if (testMod != null) extendSourceSet(otherProject, testMod)
 | 
			
		||||
        if (testFixtures != null) extendSourceSet(otherProject, testFixtures)
 | 
			
		||||
 | 
			
		||||
        // The extra source-processing tasks should include these files too.
 | 
			
		||||
        project.tasks.named(main.javadocTaskName, Javadoc::class.java) { source(main.allJava, client.allJava) }
 | 
			
		||||
        project.tasks.named(main.sourcesJarTaskName, Jar::class.java) { from(main.allSource, client.allSource) }
 | 
			
		||||
        sourceDirectories.addAll(SourceSetReference.inline(main), SourceSetReference.inline(client))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Extend a source set with files from another project.
 | 
			
		||||
     *
 | 
			
		||||
     * This actually extends the original compile tasks, as extending the source sets does not play well with IDEs.
 | 
			
		||||
     */
 | 
			
		||||
    private fun extendSourceSet(otherProject: Project, sourceSet: SourceSet) {
 | 
			
		||||
        project.tasks.named(sourceSet.compileJavaTaskName, JavaCompile::class.java) {
 | 
			
		||||
            dependsOn(otherProject.tasks.named(sourceSet.compileJavaTaskName)) // Avoid duplicate compile errors
 | 
			
		||||
            source(sourceSet.allJava)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        project.tasks.named(sourceSet.processResourcesTaskName, ProcessResources::class.java) {
 | 
			
		||||
            from(sourceSet.resources)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Also try to depend on Kotlin if it exists
 | 
			
		||||
        val kotlin = otherProject.extensions.findByType(KotlinProjectExtension::class.java)
 | 
			
		||||
        if (kotlin != null) {
 | 
			
		||||
            val compileKotlin = sourceSet.getCompileTaskName("kotlin")
 | 
			
		||||
            project.tasks.named(compileKotlin, KotlinCompile::class.java) {
 | 
			
		||||
                dependsOn(otherProject.tasks.named(compileKotlin))
 | 
			
		||||
                source(kotlin.sourceSets.getByName(sourceSet.name).kotlin)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If we're doing an IDE sync, add a fake dependency to ensure it's on the classpath.
 | 
			
		||||
        if (isIdeSync) project.dependencies.add(sourceSet.apiConfigurationName, sourceSet.output)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun linters(@Suppress("UNUSED_PARAMETER") vararg unused: UseNamedArgs, minecraft: Boolean, loader: String?) {
 | 
			
		||||
        val java = project.extensions.getByType(JavaPluginExtension::class.java)
 | 
			
		||||
        val sourceSets = java.sourceSets
 | 
			
		||||
 | 
			
		||||
        project.dependencies.run { add("errorprone", project(mapOf("path" to ":lints"))) }
 | 
			
		||||
        sourceSets.all {
 | 
			
		||||
            val name = name
 | 
			
		||||
            project.tasks.named(compileJavaTaskName, JavaCompile::class.java) {
 | 
			
		||||
                options.errorprone {
 | 
			
		||||
                    // Only the main source set should run the side checker
 | 
			
		||||
                    check("SideChecker", if (minecraft && name == "main") CheckSeverity.DEFAULT else CheckSeverity.OFF)
 | 
			
		||||
 | 
			
		||||
                    // The MissingLoaderOverride check superseeds the MissingOverride one, so disable that.
 | 
			
		||||
                    if (loader != null) {
 | 
			
		||||
                        check("MissingOverride", CheckSeverity.OFF)
 | 
			
		||||
                        option("ModLoader", loader)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        check("LoaderOverride", CheckSeverity.OFF)
 | 
			
		||||
                        check("MissingLoaderOverride", CheckSeverity.OFF)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun <T> jacoco(task: NamedDomainObjectProvider<T>) where T : Task, T : JavaForkOptions {
 | 
			
		||||
        val classDump = project.buildDir.resolve("jacocoClassDump/${task.name}")
 | 
			
		||||
        val reportTaskName = "jacoco${task.name.capitalized()}Report"
 | 
			
		||||
 | 
			
		||||
@@ -93,8 +197,7 @@ abstract class CCTweakedExtension(
 | 
			
		||||
            classDirectories.from(classDump)
 | 
			
		||||
 | 
			
		||||
            // Don't want to use sourceSets(...) here as we have a custom class directory.
 | 
			
		||||
            val sourceSets = project.extensions.getByType(SourceSetContainer::class.java)
 | 
			
		||||
            sourceDirectories.from(sourceSets["main"].allSource.sourceDirectories)
 | 
			
		||||
            for (ref in sourceSets.get()) sourceDirectories.from(ref.allSource.sourceDirectories)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        project.extensions.configure(ReportingExtension::class.java) {
 | 
			
		||||
@@ -104,8 +207,43 @@ abstract class CCTweakedExtension(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download a file by creating a dummy Ivy repository.
 | 
			
		||||
     *
 | 
			
		||||
     * This should only be used for one-off downloads. Using a more conventional Ivy or Maven repository is preferred
 | 
			
		||||
     * where possible.
 | 
			
		||||
     */
 | 
			
		||||
    fun downloadFile(label: String, url: String): File {
 | 
			
		||||
        val url = URL(url)
 | 
			
		||||
        val path = File(url.path)
 | 
			
		||||
 | 
			
		||||
        project.repositories.ivy {
 | 
			
		||||
            name = label
 | 
			
		||||
            setUrl(URI(url.protocol, url.userInfo, url.host, url.port, path.parent, null, null))
 | 
			
		||||
            patternLayout {
 | 
			
		||||
                artifact("[artifact].[ext]")
 | 
			
		||||
            }
 | 
			
		||||
            metadataSources {
 | 
			
		||||
                artifact()
 | 
			
		||||
            }
 | 
			
		||||
            content {
 | 
			
		||||
                includeModule("cc.tweaked.internal", path.nameWithoutExtension)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return project.configurations.detachedConfiguration(
 | 
			
		||||
            project.dependencies.create(
 | 
			
		||||
                mapOf(
 | 
			
		||||
                    "group" to "cc.tweaked.internal",
 | 
			
		||||
                    "name" to path.nameWithoutExtension,
 | 
			
		||||
                    "ext" to path.extension,
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
        ).resolve().single()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val EMAIL = Pattern.compile("^([^<]+) <.+>$")
 | 
			
		||||
        private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
 | 
			
		||||
        private val IGNORED_USERS = setOf(
 | 
			
		||||
            "GitHub", "Daniel Ratcliffe", "Weblate",
 | 
			
		||||
        )
 | 
			
		||||
@@ -117,8 +255,14 @@ abstract class CCTweakedExtension(
 | 
			
		||||
                } catch (e: IOException) {
 | 
			
		||||
                    project.logger.error("Cannot read Git repository: ${e.message}")
 | 
			
		||||
                    default
 | 
			
		||||
                } catch (e: GradleException) {
 | 
			
		||||
                    project.logger.error("Cannot read Git repository: ${e.message}")
 | 
			
		||||
                    default
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private val isIdeSync: Boolean
 | 
			
		||||
            get() = java.lang.Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@ package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.Plugin
 | 
			
		||||
import org.gradle.api.Project
 | 
			
		||||
import org.gradle.api.plugins.JavaPlugin
 | 
			
		||||
import org.gradle.api.plugins.JavaPluginExtension
 | 
			
		||||
import org.gradle.jvm.toolchain.JavaLanguageVersion
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -9,7 +11,12 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion
 | 
			
		||||
 */
 | 
			
		||||
class CCTweakedPlugin : Plugin<Project> {
 | 
			
		||||
    override fun apply(project: Project) {
 | 
			
		||||
        project.extensions.create("cct", CCTweakedExtension::class.java)
 | 
			
		||||
        val cct = project.extensions.create("cct", CCTweakedExtension::class.java)
 | 
			
		||||
 | 
			
		||||
        project.plugins.withType(JavaPlugin::class.java) {
 | 
			
		||||
            val sourceSets = project.extensions.getByType(JavaPluginExtension::class.java).sourceSets
 | 
			
		||||
            cct.sourceDirectories.add(SourceSetReference.internal(sourceSets.getByName("main")))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@ import org.gradle.api.file.RegularFileProperty
 | 
			
		||||
import org.gradle.api.provider.Property
 | 
			
		||||
import org.gradle.api.tasks.*
 | 
			
		||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
 | 
			
		||||
import java.nio.charset.StandardCharsets
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks the `changelog.md` and `whatsnew.md` files are well-formed.
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ import com.diffplug.spotless.FormatterStep
 | 
			
		||||
import com.diffplug.spotless.generic.LicenseHeaderStep
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.Serializable
 | 
			
		||||
import java.nio.charset.StandardCharsets
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Similar to [LicenseHeaderStep], but supports multiple licenses.
 | 
			
		||||
 
 | 
			
		||||
@@ -2,19 +2,116 @@ package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.artifacts.dsl.DependencyHandler
 | 
			
		||||
import org.gradle.api.tasks.JavaExec
 | 
			
		||||
import org.gradle.process.BaseExecSpec
 | 
			
		||||
import org.gradle.process.JavaExecSpec
 | 
			
		||||
import org.gradle.process.ProcessForkOptions
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add an annotation processor to all source sets.
 | 
			
		||||
 */
 | 
			
		||||
fun DependencyHandler.annotationProcessorEverywhere(dep: Any) {
 | 
			
		||||
    add("compileOnly", dep)
 | 
			
		||||
    add("annotationProcessor", dep)
 | 
			
		||||
 | 
			
		||||
    add("clientCompileOnly", dep)
 | 
			
		||||
    add("clientAnnotationProcessor", dep)
 | 
			
		||||
 | 
			
		||||
    add("testCompileOnly", dep)
 | 
			
		||||
    add("testAnnotationProcessor", dep)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A version of [JavaExecSpec.copyTo] which copies *all* properties.
 | 
			
		||||
 */
 | 
			
		||||
fun JavaExec.copyToFull(spec: JavaExec) {
 | 
			
		||||
    copyTo(spec)
 | 
			
		||||
    spec.classpath = classpath
 | 
			
		||||
    spec.mainClass.set(mainClass)
 | 
			
		||||
    spec.javaLauncher.set(javaLauncher)
 | 
			
		||||
 | 
			
		||||
    // Additional Java options
 | 
			
		||||
    spec.jvmArgs = jvmArgs // Fabric overrides getJvmArgs so copyTo doesn't do the right thing.
 | 
			
		||||
    spec.args = args
 | 
			
		||||
    spec.argumentProviders.addAll(argumentProviders)
 | 
			
		||||
    spec.mainClass.set(mainClass)
 | 
			
		||||
    spec.classpath = classpath
 | 
			
		||||
    spec.javaLauncher.set(javaLauncher)
 | 
			
		||||
    if (executable != null) spec.setExecutable(executable!!)
 | 
			
		||||
 | 
			
		||||
    // Additional ExecSpec options
 | 
			
		||||
    copyToExec(spec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Copy additional [BaseExecSpec] options which aren't handled by [ProcessForkOptions.copyTo].
 | 
			
		||||
 */
 | 
			
		||||
fun BaseExecSpec.copyToExec(spec: BaseExecSpec) {
 | 
			
		||||
    spec.isIgnoreExitValue = isIgnoreExitValue
 | 
			
		||||
    if (standardInput != null) spec.standardInput = standardInput
 | 
			
		||||
    if (standardOutput != null) spec.standardOutput = standardOutput
 | 
			
		||||
    if (errorOutput != null) spec.errorOutput = errorOutput
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An alternative to [Nothing] with a more descriptive name. Use to enforce calling a function with named arguments:
 | 
			
		||||
 *
 | 
			
		||||
 * ```kotlin
 | 
			
		||||
 * fun f(vararg unused: UseNamedArgs, arg1: Int, arg2: Int) {
 | 
			
		||||
 *   // ...
 | 
			
		||||
 * }
 | 
			
		||||
 * ```
 | 
			
		||||
 */
 | 
			
		||||
class UseNamedArgs private constructor()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An [AutoCloseable] implementation which can be used to combine other [AutoCloseable] instances.
 | 
			
		||||
 *
 | 
			
		||||
 * Values which implement [AutoCloseable] can be dynamically registered with [CloseScope.add]. When the scope is closed,
 | 
			
		||||
 * each value is closed in the opposite order.
 | 
			
		||||
 *
 | 
			
		||||
 * This is largely intended for cases where it's not appropriate to nest [AutoCloseable.use], for instance when nested
 | 
			
		||||
 * would be too deep.
 | 
			
		||||
 */
 | 
			
		||||
class CloseScope : AutoCloseable {
 | 
			
		||||
    private val toClose = ArrayDeque<AutoCloseable>()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a value to be closed when this scope is closed.
 | 
			
		||||
     */
 | 
			
		||||
    public fun add(value: AutoCloseable) {
 | 
			
		||||
        toClose.addLast(value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun close() {
 | 
			
		||||
        close(null)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PublishedApi
 | 
			
		||||
    internal fun close(baseException: Throwable?) {
 | 
			
		||||
        var exception = baseException
 | 
			
		||||
 | 
			
		||||
        while (true) {
 | 
			
		||||
            var toClose = toClose.removeLastOrNull() ?: break
 | 
			
		||||
            try {
 | 
			
		||||
                toClose.close()
 | 
			
		||||
            } catch (e: Throwable) {
 | 
			
		||||
                if (exception == null) {
 | 
			
		||||
                    exception = e
 | 
			
		||||
                } else {
 | 
			
		||||
                    exception.addSuppressed(e)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (exception != null) throw exception
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inline fun <R> use(block: (CloseScope) -> R): R {
 | 
			
		||||
        var exception: Throwable? = null
 | 
			
		||||
        try {
 | 
			
		||||
            return block(this)
 | 
			
		||||
        } catch (e: Throwable) {
 | 
			
		||||
            exception = e
 | 
			
		||||
            throw e
 | 
			
		||||
        } finally {
 | 
			
		||||
            close(exception)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,170 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.Project
 | 
			
		||||
import org.gradle.api.logging.Logging
 | 
			
		||||
import org.w3c.dom.Attr
 | 
			
		||||
import org.w3c.dom.Document
 | 
			
		||||
import org.w3c.dom.Node
 | 
			
		||||
import org.xml.sax.InputSource
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
import java.nio.file.Path
 | 
			
		||||
import javax.xml.parsers.DocumentBuilderFactory
 | 
			
		||||
import javax.xml.transform.TransformerFactory
 | 
			
		||||
import javax.xml.transform.dom.DOMSource
 | 
			
		||||
import javax.xml.transform.stream.StreamResult
 | 
			
		||||
import javax.xml.xpath.XPathConstants
 | 
			
		||||
import javax.xml.xpath.XPathFactory
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Patches up run configurations from ForgeGradle and Loom.
 | 
			
		||||
 *
 | 
			
		||||
 * Would be good to PR some (or all) of these changes upstream at some point.
 | 
			
		||||
 *
 | 
			
		||||
 * @see net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask
 | 
			
		||||
 * @see net.minecraftforge.gradle.common.util.runs.IntellijRunGenerator
 | 
			
		||||
 */
 | 
			
		||||
internal class IdeaRunConfigurations(project: Project) {
 | 
			
		||||
    private val rootProject = project.rootProject
 | 
			
		||||
 | 
			
		||||
    private val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
 | 
			
		||||
    private val xpath = XPathFactory.newInstance().newXPath()
 | 
			
		||||
    private val writer = TransformerFactory.newInstance().newTransformer()
 | 
			
		||||
 | 
			
		||||
    private val ideaDir = rootProject.file(".idea/")
 | 
			
		||||
    private val buildDir: Lazy<String?> = lazy {
 | 
			
		||||
        val ideaMisc = ideaDir.resolve("misc.xml")
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            val doc = Files.newBufferedReader(ideaMisc.toPath()).use {
 | 
			
		||||
                documentBuilder.parse(InputSource(it))
 | 
			
		||||
            }
 | 
			
		||||
            val node =
 | 
			
		||||
                xpath.evaluate("//component[@name=\"ProjectRootManager\"]/output", doc, XPathConstants.NODE) as Node
 | 
			
		||||
            val attr = node.attributes.getNamedItem("url") as Attr
 | 
			
		||||
            attr.value.removePrefix("file://")
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            LOGGER.error("Failed to find root directory", e)
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun patch() = synchronized(LOCK) {
 | 
			
		||||
        val runConfigDir = ideaDir.resolve("runConfigurations")
 | 
			
		||||
        if (!runConfigDir.isDirectory) return
 | 
			
		||||
 | 
			
		||||
        Files.list(runConfigDir.toPath()).use {
 | 
			
		||||
            for (configuration in it) {
 | 
			
		||||
                val filename = configuration.fileName.toString();
 | 
			
		||||
                when {
 | 
			
		||||
                    filename.endsWith("_fabric.xml") -> patchFabric(configuration)
 | 
			
		||||
                    filename.startsWith("forge_") && filename.endsWith(".xml") -> patchForge(configuration)
 | 
			
		||||
                    else -> {}
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun patchFabric(path: Path) = withXml(path) {
 | 
			
		||||
        setXml("//configuration", "folderName") { "Fabric" }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun patchForge(path: Path) = withXml(path) {
 | 
			
		||||
        val configId = path.fileName.toString().removePrefix("forge_").removeSuffix(".xml")
 | 
			
		||||
        val sourceSet = forgeConfigs[configId]
 | 
			
		||||
        if (sourceSet == null) {
 | 
			
		||||
            LOGGER.error("[{}] Cannot map run configuration to a known source set", path)
 | 
			
		||||
            return@withXml
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setXml("//configuration", "folderName") { "Forge" }
 | 
			
		||||
        setXml("//configuration/module", "name") { "${rootProject.name}.forge.$sourceSet" }
 | 
			
		||||
 | 
			
		||||
        if (buildDir.value == null) return@withXml
 | 
			
		||||
        setXml("//configuration/envs/env[@name=\"MOD_CLASSES\"]", "value") { classpath ->
 | 
			
		||||
            val classes = classpath!!.split(':')
 | 
			
		||||
            val newClasses = mutableListOf<String>()
 | 
			
		||||
            fun appendUnique(x: String) {
 | 
			
		||||
                if (!newClasses.contains(x)) newClasses.add(x)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (entry in classes) {
 | 
			
		||||
                if (!entry.contains("/out/")) {
 | 
			
		||||
                    appendUnique(entry)
 | 
			
		||||
                    continue
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                val match = CLASSPATH_ENTRY.matchEntire(entry)
 | 
			
		||||
                if (match != null) {
 | 
			
		||||
                    val modId = match.groups["modId"]!!.value
 | 
			
		||||
                    val proj = match.groups["proj"]!!.value
 | 
			
		||||
                    var component = match.groups["component"]!!.value
 | 
			
		||||
                    if (component == "production") component = "main"
 | 
			
		||||
 | 
			
		||||
                    appendUnique(forgeModEntry(modId, proj, component))
 | 
			
		||||
                } else {
 | 
			
		||||
                    LOGGER.warn("[{}] Unknown classpath entry {}", path, entry)
 | 
			
		||||
                    appendUnique(entry)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Ensure common code is on the classpath
 | 
			
		||||
            for (proj in listOf("common", "common-api")) {
 | 
			
		||||
                for (component in listOf("main", "client")) {
 | 
			
		||||
                    appendUnique(forgeModEntry("computercraft", proj, component))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (newClasses.any { it.startsWith("cctest%%") }) {
 | 
			
		||||
                appendUnique(forgeModEntry("cctest", "core", "testFixtures"))
 | 
			
		||||
                appendUnique(forgeModEntry("cctest", "common", "testFixtures"))
 | 
			
		||||
                appendUnique(forgeModEntry("cctest", "common", "testMod"))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            newClasses.joinToString(":")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun forgeModEntry(mod: String, project: String, component: String) =
 | 
			
		||||
        "$mod%%${buildDir.value}/production/${rootProject.name}.$project.$component"
 | 
			
		||||
 | 
			
		||||
    private fun LocatedDocument.setXml(xpath: String, attribute: String, value: (String?) -> String) {
 | 
			
		||||
        val node = this@IdeaRunConfigurations.xpath.evaluate(xpath, document, XPathConstants.NODE) as Node?
 | 
			
		||||
        if (node == null) {
 | 
			
		||||
            LOGGER.error("[{}] Cannot find {}", path.fileName, xpath)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val attr = node.attributes.getNamedItem(attribute) as Attr? ?: document.createAttribute(attribute)
 | 
			
		||||
        val oldValue = attr.value
 | 
			
		||||
        attr.value = value(attr.value)
 | 
			
		||||
        node.attributes.setNamedItem(attr)
 | 
			
		||||
 | 
			
		||||
        if (oldValue != attr.value) {
 | 
			
		||||
            LOGGER.info("[{}] Setting {}@{}:\n  Old: {}\n  New: {}", path.fileName, xpath, attribute, oldValue, attr.value)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun withXml(path: Path, run: LocatedDocument.() -> Unit) {
 | 
			
		||||
        val doc = Files.newBufferedReader(path).use { documentBuilder.parse(InputSource(it)) }
 | 
			
		||||
        run(LocatedDocument(path, doc))
 | 
			
		||||
        Files.newBufferedWriter(path).use { writer.transform(DOMSource(doc), StreamResult(it)) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private class LocatedDocument(val path: Path, val document: Document)
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val LOGGER = Logging.getLogger(IdeaRunConfigurations::class.java)
 | 
			
		||||
        private val LOCK = Any()
 | 
			
		||||
 | 
			
		||||
        private val CLASSPATH_ENTRY =
 | 
			
		||||
            Regex("(?<modId>[a-z]+)%%\\\$PROJECT_DIR\\\$/projects/(?<proj>[a-z-]+)/out/(?<component>\\w+)/(?<type>[a-z]+)\$")
 | 
			
		||||
 | 
			
		||||
        private val forgeConfigs = mapOf(
 | 
			
		||||
            "runClient" to "client",
 | 
			
		||||
            "runData" to "main",
 | 
			
		||||
            "runGameTestServer" to "testMod",
 | 
			
		||||
            "runServer" to "main",
 | 
			
		||||
            "runTestClient" to "testMod",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -66,9 +66,11 @@ class IlluaminatePlugin : Plugin<Project> {
 | 
			
		||||
 | 
			
		||||
        val osArch = System.getProperty("os.arch").toLowerCase()
 | 
			
		||||
        val arch = when {
 | 
			
		||||
            // On macOS the x86_64 binary will work for both ARM and Intel Macs through Rosetta.
 | 
			
		||||
            os == "macos" -> "x86_64"
 | 
			
		||||
            osArch == "arm" || osArch.startsWith("aarch") -> error("Unsupported architecture '$osArch' for illuaminate")
 | 
			
		||||
            osArch.contains("64") -> "x86_64"
 | 
			
		||||
            else -> error("Unsupported architecture $osArch for illuaminate")
 | 
			
		||||
            else -> error("Unsupported architecture '$osArch' for illuaminate")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return project.dependencies.create(
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,62 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.artifacts.Dependency
 | 
			
		||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency
 | 
			
		||||
import org.gradle.api.publish.maven.MavenPublication
 | 
			
		||||
import org.gradle.api.specs.Spec
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A dependency in a POM file.
 | 
			
		||||
 */
 | 
			
		||||
data class MavenDependency(val groupId: String?, val artifactId: String?, val version: String?, val scope: String?)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A spec specifying which dependencies to include/exclude.
 | 
			
		||||
 */
 | 
			
		||||
class MavenDependencySpec {
 | 
			
		||||
    private val excludeSpecs = mutableListOf<Spec<MavenDependency>>()
 | 
			
		||||
 | 
			
		||||
    fun exclude(spec: Spec<MavenDependency>) {
 | 
			
		||||
        excludeSpecs.add(spec)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun exclude(dep: Dependency) {
 | 
			
		||||
        exclude {
 | 
			
		||||
            (dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
 | 
			
		||||
                (dep.name.isNullOrEmpty() || dep.name == it.artifactId) &&
 | 
			
		||||
                (dep.version.isNullOrEmpty() || dep.version == it.version)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun exclude(dep: MinimalExternalModuleDependency) {
 | 
			
		||||
        exclude {
 | 
			
		||||
            dep.module.group == it.groupId && dep.module.name == it.artifactId
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun isIncluded(dep: MavenDependency) = !excludeSpecs.any { it.isSatisfiedBy(dep) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Configure dependencies present in this publication's POM file.
 | 
			
		||||
 *
 | 
			
		||||
 * While this approach is very ugly, it's the easiest way to handle it!
 | 
			
		||||
 */
 | 
			
		||||
fun MavenPublication.mavenDependencies(action: MavenDependencySpec.() -> Unit) {
 | 
			
		||||
    val spec = MavenDependencySpec()
 | 
			
		||||
    action(spec)
 | 
			
		||||
 | 
			
		||||
    pom.withXml {
 | 
			
		||||
        val dependencies = XmlUtil.findChild(asNode(), "dependencies") ?: return@withXml
 | 
			
		||||
        dependencies.children().map { it as groovy.util.Node }.forEach {
 | 
			
		||||
            val dep = MavenDependency(
 | 
			
		||||
                groupId = XmlUtil.findChild(it, "groupId")?.text(),
 | 
			
		||||
                artifactId = XmlUtil.findChild(it, "artifactId")?.text(),
 | 
			
		||||
                version = XmlUtil.findChild(it, "version")?.text(),
 | 
			
		||||
                scope = XmlUtil.findChild(it, "scope")?.text(),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            if (!spec.isIncluded(dep)) it.parent().remove(it)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,189 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.Project
 | 
			
		||||
import org.gradle.api.artifacts.Configuration
 | 
			
		||||
import org.gradle.api.artifacts.ModuleDependency
 | 
			
		||||
import org.gradle.api.artifacts.dsl.DependencyHandler
 | 
			
		||||
import org.gradle.api.attributes.Bundling
 | 
			
		||||
import org.gradle.api.attributes.Category
 | 
			
		||||
import org.gradle.api.attributes.LibraryElements
 | 
			
		||||
import org.gradle.api.attributes.Usage
 | 
			
		||||
import org.gradle.api.attributes.java.TargetJvmVersion
 | 
			
		||||
import org.gradle.api.capabilities.Capability
 | 
			
		||||
import org.gradle.api.plugins.BasePlugin
 | 
			
		||||
import org.gradle.api.plugins.JavaPluginExtension
 | 
			
		||||
import org.gradle.api.tasks.SourceSet
 | 
			
		||||
import org.gradle.api.tasks.bundling.Jar
 | 
			
		||||
import org.gradle.api.tasks.javadoc.Javadoc
 | 
			
		||||
import org.gradle.kotlin.dsl.get
 | 
			
		||||
import org.gradle.kotlin.dsl.named
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This sets up a separate client-only source set, and extends that and the main/common source set with additional
 | 
			
		||||
 * metadata, to make it easier to consume jars downstream.
 | 
			
		||||
 */
 | 
			
		||||
class MinecraftConfigurations private constructor(private val project: Project) {
 | 
			
		||||
    private val java = project.extensions.getByType(JavaPluginExtension::class.java)
 | 
			
		||||
    private val sourceSets = java.sourceSets
 | 
			
		||||
    private val configurations = project.configurations
 | 
			
		||||
    private val objects = project.objects
 | 
			
		||||
 | 
			
		||||
    private val main = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
 | 
			
		||||
    private val test = sourceSets[SourceSet.TEST_SOURCE_SET_NAME]
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs the initial setup of our configurations.
 | 
			
		||||
     */
 | 
			
		||||
    private fun setup() {
 | 
			
		||||
        // Define a client source set.
 | 
			
		||||
        val client = sourceSets.maybeCreate("client")
 | 
			
		||||
 | 
			
		||||
        // Ensure the client classpaths behave the same as the main ones.
 | 
			
		||||
        configurations.named(client.compileClasspathConfigurationName) {
 | 
			
		||||
            shouldResolveConsistentlyWith(configurations[main.compileClasspathConfigurationName])
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        configurations.named(client.runtimeClasspathConfigurationName) {
 | 
			
		||||
            shouldResolveConsistentlyWith(configurations[main.runtimeClasspathConfigurationName])
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set up an API configuration for clients (to ensure it's consistent with the main source set).
 | 
			
		||||
        val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply {
 | 
			
		||||
            isVisible = false
 | 
			
		||||
            isCanBeConsumed = false
 | 
			
		||||
            isCanBeResolved = false
 | 
			
		||||
        }
 | 
			
		||||
        configurations.named(client.implementationConfigurationName) { extendsFrom(clientApi) }
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
          Now add outgoing variants for the main and common source sets that we can consume downstream. This is possibly
 | 
			
		||||
          the worst way to do things, but unfortunately the alternatives don't actually work very well:
 | 
			
		||||
 | 
			
		||||
           - Just using source set outputs: This means dependencies don't propagate, which means when :fabric depends
 | 
			
		||||
             on :fabric-api, we don't inherit the fake :common-api in IDEA.
 | 
			
		||||
 | 
			
		||||
           - Having separate common/main jars: Nice in principle, but unfortunately Forge needs a separate deobf jar
 | 
			
		||||
             task (as the original jar is obfuscated), and IDEA is not able to map its output back to a source set.
 | 
			
		||||
 | 
			
		||||
          This works for now, but is incredibly brittle. It's part of the reason we can't use testFixtures inside our
 | 
			
		||||
          MC projects, as that adds a project(self) -> test dependency, which would pull in the jar instead.
 | 
			
		||||
 | 
			
		||||
          Note we register a fake client jar here. It's not actually needed, but is there to make sure IDEA has
 | 
			
		||||
          a way to tell that client classes are needed at runtime.
 | 
			
		||||
 | 
			
		||||
          I'm so sorry, deeply aware how cursed this is.
 | 
			
		||||
        */
 | 
			
		||||
        setupOutgoing(main, "CommonOnly")
 | 
			
		||||
        project.tasks.register(client.jarTaskName, Jar::class.java) {
 | 
			
		||||
            description = "An empty jar standing in for the client classes."
 | 
			
		||||
            group = BasePlugin.BUILD_GROUP
 | 
			
		||||
            archiveClassifier.set("client")
 | 
			
		||||
        }
 | 
			
		||||
        setupOutgoing(client)
 | 
			
		||||
 | 
			
		||||
        // Reset the client classpath (Loom configures it slightly differently to this) and add a main -> client
 | 
			
		||||
        // dependency. Here we /can/ use source set outputs as we add transitive deps by patching the classpath. Nasty,
 | 
			
		||||
        // but avoids accidentally pulling in Forge's obfuscated jar.
 | 
			
		||||
        client.compileClasspath = client.compileClasspath + main.compileClasspath
 | 
			
		||||
        client.runtimeClasspath = client.runtimeClasspath + main.runtimeClasspath
 | 
			
		||||
        project.dependencies.add(client.apiConfigurationName, main.output)
 | 
			
		||||
 | 
			
		||||
        // Also add client classes to the test classpath. We do the same nasty tricks as needed for main -> client.
 | 
			
		||||
        test.compileClasspath += client.compileClasspath
 | 
			
		||||
        test.runtimeClasspath += client.runtimeClasspath
 | 
			
		||||
        project.dependencies.add(test.implementationConfigurationName, client.output)
 | 
			
		||||
 | 
			
		||||
        // Configure some tasks to include our additional files.
 | 
			
		||||
        project.tasks.named("javadoc", Javadoc::class.java) {
 | 
			
		||||
            source(client.allJava)
 | 
			
		||||
            classpath = main.compileClasspath + main.output + client.compileClasspath + client.output
 | 
			
		||||
        }
 | 
			
		||||
        // This are already done by Fabric, but we need it for Forge and vanilla. It shouldn't conflict at all.
 | 
			
		||||
        project.tasks.named("jar", Jar::class.java) { from(client.output) }
 | 
			
		||||
        project.tasks.named("sourcesJar", Jar::class.java) { from(client.allSource) }
 | 
			
		||||
 | 
			
		||||
        project.extensions.configure(CCTweakedExtension::class.java) {
 | 
			
		||||
            sourceDirectories.add(SourceSetReference.internal(client))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {
 | 
			
		||||
        setupOutgoing("${sourceSet.apiElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_API)) {
 | 
			
		||||
            description = "API elements for ${sourceSet.name}"
 | 
			
		||||
            extendsFrom(configurations[sourceSet.apiConfigurationName])
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setupOutgoing("${sourceSet.runtimeElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_RUNTIME)) {
 | 
			
		||||
            description = "Runtime elements for ${sourceSet.name}"
 | 
			
		||||
            extendsFrom(configurations[sourceSet.implementationConfigurationName], configurations[sourceSet.runtimeOnlyConfigurationName])
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set up an outgoing configuration for a specific source set. We set an additional "main" or "client" capability
 | 
			
		||||
     * (depending on the source set name) which allows downstream projects to consume them separately (see
 | 
			
		||||
     * [DependencyHandler.commonClasses] and [DependencyHandler.clientClasses]).
 | 
			
		||||
     */
 | 
			
		||||
    private fun setupOutgoing(name: String, sourceSet: SourceSet, usage: Usage, configure: Configuration.() -> Unit) {
 | 
			
		||||
        configurations.register(name) {
 | 
			
		||||
            isVisible = false
 | 
			
		||||
            isCanBeConsumed = true
 | 
			
		||||
            isCanBeResolved = false
 | 
			
		||||
 | 
			
		||||
            configure(this)
 | 
			
		||||
 | 
			
		||||
            attributes {
 | 
			
		||||
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
 | 
			
		||||
                attribute(Usage.USAGE_ATTRIBUTE, usage)
 | 
			
		||||
                attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
 | 
			
		||||
                attributeProvider(
 | 
			
		||||
                    TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,
 | 
			
		||||
                    java.toolchain.languageVersion.map { it.asInt() },
 | 
			
		||||
                )
 | 
			
		||||
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            outgoing {
 | 
			
		||||
                capability(BasicOutgoingCapability(project, sourceSet.name))
 | 
			
		||||
 | 
			
		||||
                // We have two outgoing variants here: the original jar and the classes.
 | 
			
		||||
                artifact(project.tasks.named(sourceSet.jarTaskName))
 | 
			
		||||
 | 
			
		||||
                variants.create("classes") {
 | 
			
		||||
                    attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.CLASSES))
 | 
			
		||||
                    sourceSet.output.classesDirs.forEach { artifact(it) { builtBy(sourceSet.output) } }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        fun setup(project: Project) {
 | 
			
		||||
            MinecraftConfigurations(project).setup()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private class BasicIncomingCapability(private val module: ModuleDependency, private val name: String) : Capability {
 | 
			
		||||
    override fun getGroup(): String = module.group!!
 | 
			
		||||
    override fun getName(): String = "${module.name}-$name"
 | 
			
		||||
    override fun getVersion(): String? = null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private class BasicOutgoingCapability(private val project: Project, private val name: String) : Capability {
 | 
			
		||||
    override fun getGroup(): String = project.group.toString()
 | 
			
		||||
    override fun getName(): String = "${project.name}-$name"
 | 
			
		||||
    override fun getVersion(): String = project.version.toString()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency {
 | 
			
		||||
    val dep = create(notation) as ModuleDependency
 | 
			
		||||
    dep.capabilities { requireCapability(BasicIncomingCapability(dep, "client")) }
 | 
			
		||||
    return dep
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency {
 | 
			
		||||
    val dep = create(notation) as ModuleDependency
 | 
			
		||||
    dep.capabilities { requireCapability(BasicIncomingCapability(dep, "main")) }
 | 
			
		||||
    return dep
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										191
									
								
								buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftExec.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftExec.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,191 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.GradleException
 | 
			
		||||
import org.gradle.api.file.FileSystemOperations
 | 
			
		||||
import org.gradle.api.invocation.Gradle
 | 
			
		||||
import org.gradle.api.provider.Provider
 | 
			
		||||
import org.gradle.api.services.BuildService
 | 
			
		||||
import org.gradle.api.services.BuildServiceParameters
 | 
			
		||||
import org.gradle.api.tasks.*
 | 
			
		||||
import org.gradle.kotlin.dsl.getByName
 | 
			
		||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
import java.util.function.Supplier
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
import kotlin.random.Random
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A [JavaExec] task for client-tests. This sets some common setup, and uses [MinecraftRunnerService] to ensure only one
 | 
			
		||||
 * test runs at once.
 | 
			
		||||
 */
 | 
			
		||||
abstract class ClientJavaExec : JavaExec() {
 | 
			
		||||
    private val clientRunner: Provider<MinecraftRunnerService> = MinecraftRunnerService.get(project.gradle)
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        group = LifecycleBasePlugin.VERIFICATION_GROUP
 | 
			
		||||
        usesService(clientRunner)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When [false], tests will not be run automatically, allowing the user to debug rendering.
 | 
			
		||||
     */
 | 
			
		||||
    @get:Input
 | 
			
		||||
    val clientDebug get() = project.hasProperty("clientDebug")
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When [false], tests will not run under a framebuffer.
 | 
			
		||||
     */
 | 
			
		||||
    @get:Input
 | 
			
		||||
    val useFramebuffer get() = !clientDebug && !project.hasProperty("clientNoFramebuffer")
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The path test results are written to.
 | 
			
		||||
     */
 | 
			
		||||
    @get:OutputFile
 | 
			
		||||
    val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Copy configuration from a task with the given name.
 | 
			
		||||
     */
 | 
			
		||||
    fun copyFrom(path: String) = copyFrom(project.tasks.getByName(path, JavaExec::class))
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Copy configuration from an existing [JavaExec] task.
 | 
			
		||||
     */
 | 
			
		||||
    fun copyFrom(task: JavaExec) {
 | 
			
		||||
        for (dep in task.dependsOn) dependsOn(dep)
 | 
			
		||||
        task.copyToFull(this)
 | 
			
		||||
 | 
			
		||||
        if (!clientDebug) systemProperty("cctest.client", "")
 | 
			
		||||
        systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
 | 
			
		||||
        workingDir(project.buildDir.resolve("gametest").resolve(name))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Only run tests with the given tags.
 | 
			
		||||
     */
 | 
			
		||||
    fun tags(vararg tags: String) {
 | 
			
		||||
        systemProperty("cctest.tags", tags.joinToString(","))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Write a file with the given contents before starting Minecraft. This may be useful for writing config files.
 | 
			
		||||
     */
 | 
			
		||||
    fun withFileContents(path: Any, contents: Supplier<String>) {
 | 
			
		||||
        val file = project.file(path).toPath()
 | 
			
		||||
        doFirst {
 | 
			
		||||
            Files.createDirectories(file.parent)
 | 
			
		||||
            Files.writeString(file, contents.get())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Copy a file to the provided path before starting Minecraft. This copy only occurs if the file does not already
 | 
			
		||||
     * exist.
 | 
			
		||||
     */
 | 
			
		||||
    fun withFileFrom(path: Any, source: Supplier<File>) {
 | 
			
		||||
        val file = project.file(path).toPath()
 | 
			
		||||
        doFirst {
 | 
			
		||||
            Files.createDirectories(file.parent)
 | 
			
		||||
            if (!Files.exists(file)) Files.copy(source.get().toPath(), file)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @TaskAction
 | 
			
		||||
    override fun exec() {
 | 
			
		||||
        Files.createDirectories(workingDir.toPath())
 | 
			
		||||
        fsOperations.delete { delete(workingDir.resolve("screenshots")) }
 | 
			
		||||
 | 
			
		||||
        if (useFramebuffer) {
 | 
			
		||||
            clientRunner.get().wrapClient(this) { super.exec() }
 | 
			
		||||
        } else {
 | 
			
		||||
            super.exec()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @get:Inject
 | 
			
		||||
    protected abstract val fsOperations: FileSystemOperations
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A service for [JavaExec] tasks which start Minecraft.
 | 
			
		||||
 *
 | 
			
		||||
 * Tasks may run `usesService(MinecraftRunnerService.get(gradle))` to ensure that only one Minecraft-related task runs
 | 
			
		||||
 * at once.
 | 
			
		||||
 */
 | 
			
		||||
abstract class MinecraftRunnerService : BuildService<BuildServiceParameters.None> {
 | 
			
		||||
    private val hasXvfb = lazy {
 | 
			
		||||
        System.getProperty("os.name", "").equals("linux", ignoreCase = true) && ProcessHelpers.onPath("xvfb-run")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal fun wrapClient(exec: JavaExec, run: () -> Unit) = when {
 | 
			
		||||
        hasXvfb.value -> runXvfb(exec, run)
 | 
			
		||||
        else -> run()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Run a program under Xvfb, preventing it spawning a window.
 | 
			
		||||
     */
 | 
			
		||||
    private fun runXvfb(exec: JavaExec, run: () -> Unit) {
 | 
			
		||||
        fun ProcessBuilder.startVerbose(): Process {
 | 
			
		||||
            exec.logger.info("Running ${this.command()}")
 | 
			
		||||
            return start()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CloseScope().use { scope ->
 | 
			
		||||
            val dir = Files.createTempDirectory("cctweaked").toAbsolutePath()
 | 
			
		||||
            scope.add { fsOperations.delete { delete(dir) } }
 | 
			
		||||
 | 
			
		||||
            val authFile = Files.createTempFile(dir, "Xauthority", "").toAbsolutePath()
 | 
			
		||||
 | 
			
		||||
            val cookie = StringBuilder().also {
 | 
			
		||||
                for (i in 0..31) it.append("0123456789abcdef"[Random.nextInt(16)])
 | 
			
		||||
            }.toString()
 | 
			
		||||
 | 
			
		||||
            val xvfb =
 | 
			
		||||
                ProcessBuilder("Xvfb", "-displayfd", "1", "-screen", "0", "640x480x24", "-nolisten", "tcp").also {
 | 
			
		||||
                    it.inheritIO()
 | 
			
		||||
                    it.environment()["XAUTHORITY"] = authFile.toString()
 | 
			
		||||
                    it.redirectOutput(ProcessBuilder.Redirect.PIPE)
 | 
			
		||||
                }.startVerbose()
 | 
			
		||||
            scope.add { xvfb.destroyForcibly().waitFor() }
 | 
			
		||||
 | 
			
		||||
            val server = xvfb.inputReader().use { it.readLine().trim() }
 | 
			
		||||
            exec.logger.info("Running at :$server (XAUTHORITY=$authFile.toA")
 | 
			
		||||
 | 
			
		||||
            ProcessBuilder("xauth", "add", ":$server", ".", cookie).also {
 | 
			
		||||
                it.inheritIO()
 | 
			
		||||
                it.environment()["XAUTHORITY"] = authFile.toString()
 | 
			
		||||
            }.startVerbose().waitForOrThrow("Failed to setup XAuthority file")
 | 
			
		||||
 | 
			
		||||
            scope.add {
 | 
			
		||||
                ProcessBuilder("xauth", "remove", ":$server").also {
 | 
			
		||||
                    it.inheritIO()
 | 
			
		||||
                    it.environment()["XAUTHORITY"] = authFile.toString()
 | 
			
		||||
                }.startVerbose().waitFor()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Wait a few seconds for Xvfb to start. Ugly, but identical to xvfb-run.
 | 
			
		||||
            if (xvfb.waitFor(3, TimeUnit.SECONDS)) {
 | 
			
		||||
                throw GradleException("Xvfb unexpectedly exited (with status code ${xvfb.exitValue()})")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            exec.environment("XAUTHORITY", authFile.toString())
 | 
			
		||||
            exec.environment("DISPLAY", ":$server")
 | 
			
		||||
 | 
			
		||||
            run()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @get:Inject
 | 
			
		||||
    protected abstract val fsOperations: FileSystemOperations
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        fun get(gradle: Gradle): Provider<MinecraftRunnerService> =
 | 
			
		||||
            gradle.sharedServices.registerIfAbsent("cc.tweaked.gradle.ClientJavaExec", MinecraftRunnerService::class.java) {
 | 
			
		||||
                maxParallelUsages.set(1)
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,35 +1,50 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.codehaus.groovy.runtime.ProcessGroovyMethods
 | 
			
		||||
import org.gradle.api.GradleException
 | 
			
		||||
import java.io.BufferedReader
 | 
			
		||||
import java.io.IOException
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.io.InputStreamReader
 | 
			
		||||
import java.util.stream.Collectors
 | 
			
		||||
 | 
			
		||||
internal object ProcessHelpers {
 | 
			
		||||
    fun startProcess(vararg command: String): Process {
 | 
			
		||||
        // Something randomly passes in "GIT_DIR=" as an environment variable which clobbers everything else. Don't
 | 
			
		||||
        // inherit the environment array!
 | 
			
		||||
        return Runtime.getRuntime().exec(command, arrayOfNulls(0))
 | 
			
		||||
        return ProcessBuilder()
 | 
			
		||||
            .command(*command)
 | 
			
		||||
            .redirectError(ProcessBuilder.Redirect.INHERIT)
 | 
			
		||||
            .also { it.environment().clear() }
 | 
			
		||||
            .start()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun captureOut(vararg command: String): String {
 | 
			
		||||
        val process = startProcess(*command)
 | 
			
		||||
        process.outputStream.close()
 | 
			
		||||
 | 
			
		||||
        val result = ProcessGroovyMethods.getText(process)
 | 
			
		||||
        if (process.waitFor() != 0) throw IOException("Command exited with a non-0 status")
 | 
			
		||||
        process.waitForOrThrow("Failed to run command")
 | 
			
		||||
        return result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun captureLines(vararg command: String): List<String> {
 | 
			
		||||
        return captureLines(startProcess(*command))
 | 
			
		||||
    }
 | 
			
		||||
        val process = startProcess(*command)
 | 
			
		||||
        process.outputStream.close()
 | 
			
		||||
 | 
			
		||||
    fun captureLines(process: Process): List<String> {
 | 
			
		||||
        val out = BufferedReader(InputStreamReader(process.inputStream)).use { reader ->
 | 
			
		||||
            reader.lines().filter { it.isNotEmpty() }.collect(Collectors.toList())
 | 
			
		||||
            reader.lines().filter { it.isNotEmpty() }.toList()
 | 
			
		||||
        }
 | 
			
		||||
        ProcessGroovyMethods.closeStreams(process)
 | 
			
		||||
        if (process.waitFor() != 0) throw IOException("Command exited with a non-0 status")
 | 
			
		||||
        process.waitForOrThrow("Failed to run command")
 | 
			
		||||
        return out
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun onPath(name: String): Boolean {
 | 
			
		||||
        val path = System.getenv("PATH") ?: return false
 | 
			
		||||
        return path.splitToSequence(File.pathSeparator).any { File(it, name).exists() }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun Process.waitForOrThrow(message: String) {
 | 
			
		||||
    val ret = waitFor()
 | 
			
		||||
    if (ret != 0) throw GradleException("$message (exited with $ret)")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.tasks.SourceSet
 | 
			
		||||
 | 
			
		||||
data class SourceSetReference(
 | 
			
		||||
    val sourceSet: SourceSet,
 | 
			
		||||
    val classes: Boolean,
 | 
			
		||||
    val external: Boolean,
 | 
			
		||||
) {
 | 
			
		||||
    companion object {
 | 
			
		||||
        /** A source set in the current project. */
 | 
			
		||||
        fun internal(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = false)
 | 
			
		||||
 | 
			
		||||
        /** A source set from another project. */
 | 
			
		||||
        fun external(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = true, external = true)
 | 
			
		||||
 | 
			
		||||
        /** A source set which is inlined into the current project. */
 | 
			
		||||
        fun inline(sourceSet: SourceSet) = SourceSetReference(sourceSet, classes = false, external = false)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								buildSrc/src/main/kotlin/cc/tweaked/gradle/XmlUtil.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								buildSrc/src/main/kotlin/cc/tweaked/gradle/XmlUtil.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import groovy.util.Node
 | 
			
		||||
import groovy.util.NodeList
 | 
			
		||||
 | 
			
		||||
object XmlUtil {
 | 
			
		||||
    fun findChild(node: Node, name: String): Node? = when (val child = node.get(name)) {
 | 
			
		||||
        is Node -> child
 | 
			
		||||
        is NodeList -> child.singleOrNull() as Node?
 | 
			
		||||
        else -> null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -17,7 +17,10 @@
 | 
			
		||||
    <module name="TreeWalker">
 | 
			
		||||
        <!-- Annotations -->
 | 
			
		||||
        <module name="AnnotationLocation" />
 | 
			
		||||
        <module name="AnnotationUseStyle" />
 | 
			
		||||
        <module name="AnnotationUseStyle">
 | 
			
		||||
            <!-- We want trailing commas on multiline arrays. -->
 | 
			
		||||
            <property name="trailingArrayComma" value="ignore" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="MissingDeprecated" />
 | 
			
		||||
        <module name="MissingOverride" />
 | 
			
		||||
 | 
			
		||||
@@ -26,17 +29,11 @@
 | 
			
		||||
        <module name="EmptyCatchBlock">
 | 
			
		||||
            <property name="exceptionVariableName" value="ignored" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="LeftCurly">
 | 
			
		||||
            <property name="option" value="nl" />
 | 
			
		||||
            <!-- The defaults, minus lambdas. -->
 | 
			
		||||
            <property name="tokens" value="ANNOTATION_DEF,CLASS_DEF,CTOR_DEF,ENUM_CONSTANT_DEF,ENUM_DEF,INTERFACE_DEF,LITERAL_CASE,LITERAL_CATCH,LITERAL_DEFAULT,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,METHOD_DEF,OBJBLOCK,STATIC_INIT" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="LeftCurly" />
 | 
			
		||||
        <module name="NeedBraces">
 | 
			
		||||
            <property name="allowSingleLineStatement" value="true"/>
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="RightCurly">
 | 
			
		||||
            <property name="option" value="alone" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="RightCurly" />
 | 
			
		||||
 | 
			
		||||
        <!-- Class design. As if we've ever followed good practice here. -->
 | 
			
		||||
        <module name="FinalClass" />
 | 
			
		||||
@@ -114,7 +111,7 @@
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="MethodTypeParameterName" />
 | 
			
		||||
        <module name="PackageName">
 | 
			
		||||
            <property name="format" value="^dan200\.computercraft(\.[a-z][a-z0-9]*)*" />
 | 
			
		||||
            <property name="format" value="^(dan200\.computercraft|cc\.tweaked)(\.[a-z][a-z0-9]*)*" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="ParameterName" />
 | 
			
		||||
        <module name="StaticVariableName">
 | 
			
		||||
@@ -131,18 +128,11 @@
 | 
			
		||||
        <module name="MethodParamPad" />
 | 
			
		||||
        <module name="NoLineWrap" />
 | 
			
		||||
        <module name="NoWhitespaceAfter">
 | 
			
		||||
            <property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP" />
 | 
			
		||||
            <property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP,METHOD_REF" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="NoWhitespaceBefore" />
 | 
			
		||||
        <!-- TODO: Decide on an OperatorWrap style. -->
 | 
			
		||||
        <module name="ParenPad">
 | 
			
		||||
            <property name="option" value="space" />
 | 
			
		||||
            <property name="tokens" value="ANNOTATION,ANNOTATION_FIELD_DEF,CTOR_CALL,CTOR_DEF,ENUM_CONSTANT_DEF,LITERAL_CATCH,LITERAL_DO,LITERAL_FOR,LITERAL_IF,LITERAL_NEW,LITERAL_SWITCH,LITERAL_SYNCHRONIZED,LITERAL_WHILE,METHOD_CALL,METHOD_DEF,RESOURCE_SPECIFICATION,SUPER_CTOR_CALL,LAMBDA" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="ParenPad">
 | 
			
		||||
            <property name="option" value="nospace" />
 | 
			
		||||
            <property name="tokens" value="DOT,EXPR,QUESTION" />
 | 
			
		||||
        </module>
 | 
			
		||||
        <module name="ParenPad" />
 | 
			
		||||
        <module name="SeparatorWrap">
 | 
			
		||||
            <property name="option" value="eol" />
 | 
			
		||||
            <property name="tokens" value="COMMA,SEMI,ELLIPSIS,ARRAY_DECLARATOR,RBRACK,METHOD_REF" />
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,61 +0,0 @@
 | 
			
		||||
<code_scheme name="Project" version="173">
 | 
			
		||||
  <JSON>
 | 
			
		||||
    <option name="OBJECT_WRAPPING" value="1" />
 | 
			
		||||
    <option name="ARRAY_WRAPPING" value="1" />
 | 
			
		||||
  </JSON>
 | 
			
		||||
  <JavaCodeStyleSettings>
 | 
			
		||||
    <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
 | 
			
		||||
      <value />
 | 
			
		||||
    </option>
 | 
			
		||||
    <option name="JD_P_AT_EMPTY_LINES" value="false" />
 | 
			
		||||
    <option name="JD_PRESERVE_LINE_FEEDS" value="true" />
 | 
			
		||||
  </JavaCodeStyleSettings>
 | 
			
		||||
  <codeStyleSettings language="JAVA">
 | 
			
		||||
    <option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
 | 
			
		||||
    <option name="BRACE_STYLE" value="2" />
 | 
			
		||||
    <option name="CLASS_BRACE_STYLE" value="2" />
 | 
			
		||||
    <option name="METHOD_BRACE_STYLE" value="2" />
 | 
			
		||||
    <option name="LAMBDA_BRACE_STYLE" value="5" />
 | 
			
		||||
    <option name="ELSE_ON_NEW_LINE" value="true" />
 | 
			
		||||
    <option name="CATCH_ON_NEW_LINE" value="true" />
 | 
			
		||||
    <option name="FINALLY_ON_NEW_LINE" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_METHOD_CALL_PARENTHESES" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_METHOD_PARENTHESES" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_IF_PARENTHESES" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_WHILE_PARENTHESES" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_FOR_PARENTHESES" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_TRY_PARENTHESES" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_CATCH_PARENTHESES" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_SWITCH_PARENTHESES" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_SYNCHRONIZED_PARENTHESES" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
 | 
			
		||||
    <option name="SPACE_BEFORE_IF_PARENTHESES" value="false" />
 | 
			
		||||
    <option name="SPACE_BEFORE_WHILE_PARENTHESES" value="false" />
 | 
			
		||||
    <option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
 | 
			
		||||
    <option name="SPACE_BEFORE_TRY_PARENTHESES" value="false" />
 | 
			
		||||
    <option name="SPACE_BEFORE_CATCH_PARENTHESES" value="false" />
 | 
			
		||||
    <option name="SPACE_BEFORE_SWITCH_PARENTHESES" value="false" />
 | 
			
		||||
    <option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
 | 
			
		||||
    <option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
 | 
			
		||||
    <option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
 | 
			
		||||
    <option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
 | 
			
		||||
    <option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
 | 
			
		||||
    <option name="IF_BRACE_FORCE" value="1" />
 | 
			
		||||
    <option name="DOWHILE_BRACE_FORCE" value="1" />
 | 
			
		||||
    <option name="WHILE_BRACE_FORCE" value="1" />
 | 
			
		||||
    <option name="FOR_BRACE_FORCE" value="1" />
 | 
			
		||||
    <option name="SPACE_WITHIN_ANNOTATION_PARENTHESES" value="true" />
 | 
			
		||||
    <indentOptions>
 | 
			
		||||
      <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
			
		||||
    </indentOptions>
 | 
			
		||||
  </codeStyleSettings>
 | 
			
		||||
  <codeStyleSettings language="JSON">
 | 
			
		||||
    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
 | 
			
		||||
    <option name="SPACE_WITHIN_BRACKETS" value="true" />
 | 
			
		||||
    <option name="SPACE_WITHIN_BRACES" value="true" />
 | 
			
		||||
    <indentOptions>
 | 
			
		||||
      <option name="INDENT_SIZE" value="4" />
 | 
			
		||||
      <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
			
		||||
    </indentOptions>
 | 
			
		||||
  </codeStyleSettings>
 | 
			
		||||
</code_scheme>
 | 
			
		||||
@@ -1,64 +0,0 @@
 | 
			
		||||
--- @module fs
 | 
			
		||||
 | 
			
		||||
--- Returns true if a path is mounted to the parent filesystem.
 | 
			
		||||
--
 | 
			
		||||
-- The root filesystem "/" is considered a mount, along with disk folders and
 | 
			
		||||
-- the rom folder. Other programs (such as network shares) can exstend this to
 | 
			
		||||
-- make other mount types by correctly assigning their return value for getDrive.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam string path The path to check.
 | 
			
		||||
-- @treturn boolean If the path is mounted, rather than a normal file/folder.
 | 
			
		||||
-- @throws If the path does not exist.
 | 
			
		||||
-- @see getDrive
 | 
			
		||||
-- @since 1.87.0
 | 
			
		||||
function isDriveRoot(path) end
 | 
			
		||||
 | 
			
		||||
--[[- Provides completion for a file or directory name, suitable for use with
 | 
			
		||||
@{_G.read}.
 | 
			
		||||
 | 
			
		||||
When a directory is a possible candidate for completion, two entries are
 | 
			
		||||
included - one with a trailing slash (indicating that entries within this
 | 
			
		||||
directory exist) and one without it (meaning this entry is an immediate
 | 
			
		||||
completion candidate). `include_dirs` can be set to @{false} to only include
 | 
			
		||||
those with a trailing slash.
 | 
			
		||||
 | 
			
		||||
@tparam[1] string path The path to complete.
 | 
			
		||||
@tparam[1] string location The location where paths are resolved from.
 | 
			
		||||
@tparam[1,opt=true] boolean include_files When @{false}, only directories will
 | 
			
		||||
be included in the returned list.
 | 
			
		||||
@tparam[1,opt=true] boolean include_dirs When @{false}, "raw" directories will
 | 
			
		||||
not be included in the returned list.
 | 
			
		||||
 | 
			
		||||
@tparam[2] string path The path to complete.
 | 
			
		||||
@tparam[2] string location The location where paths are resolved from.
 | 
			
		||||
@tparam[2] {
 | 
			
		||||
    include_dirs? = boolean, include_files? = boolean,
 | 
			
		||||
    include_hidden? = boolean
 | 
			
		||||
} options
 | 
			
		||||
This table form is an expanded version of the previous syntax. The
 | 
			
		||||
`include_files` and `include_dirs` arguments from above are passed in as fields.
 | 
			
		||||
 | 
			
		||||
This table also accepts the following options:
 | 
			
		||||
 - `include_hidden`: Whether to include hidden files (those starting with `.`)
 | 
			
		||||
   by default. They will still be shown when typing a `.`.
 | 
			
		||||
 | 
			
		||||
@treturn { string... } A list of possible completion candidates.
 | 
			
		||||
@since 1.74
 | 
			
		||||
@changed 1.101.0
 | 
			
		||||
@usage Complete files in the root directory.
 | 
			
		||||
 | 
			
		||||
    read(nil, nil, function(str)
 | 
			
		||||
        return fs.complete(str, "", true, false)
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
@usage Complete files in the root directory, hiding hidden files by default.
 | 
			
		||||
 | 
			
		||||
    read(nil, nil, function(str)
 | 
			
		||||
        return fs.complete(str, "", {
 | 
			
		||||
            include_files = true,
 | 
			
		||||
            include_dirs = false,
 | 
			
		||||
            included_hidden = false,
 | 
			
		||||
        })
 | 
			
		||||
    end)
 | 
			
		||||
]]
 | 
			
		||||
function complete(path, location, include_files, include_dirs) end
 | 
			
		||||
@@ -1,177 +0,0 @@
 | 
			
		||||
--- Make HTTP requests, sending and receiving data to a remote web server.
 | 
			
		||||
--
 | 
			
		||||
-- @module http
 | 
			
		||||
-- @since 1.1
 | 
			
		||||
-- @see local_ips To allow accessing servers running on your local network.
 | 
			
		||||
 | 
			
		||||
--- Asynchronously make a HTTP request to the given url.
 | 
			
		||||
--
 | 
			
		||||
-- This returns immediately, a @{http_success} or @{http_failure} will be queued
 | 
			
		||||
-- once the request has completed.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam      string url   The url to request
 | 
			
		||||
-- @tparam[opt] string body  An optional string containing the body of the
 | 
			
		||||
-- request. If specified, a `POST` request will be made instead.
 | 
			
		||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
 | 
			
		||||
-- of this request.
 | 
			
		||||
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
 | 
			
		||||
-- the body will not be UTF-8 encoded, and the received response will not be
 | 
			
		||||
-- decoded.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam[2] {
 | 
			
		||||
--   url = string, body? = string, headers? = { [string] = string },
 | 
			
		||||
--   binary? = boolean, method? = string, redirect? = boolean,
 | 
			
		||||
-- } request Options for the request.
 | 
			
		||||
--
 | 
			
		||||
-- This table form is an expanded version of the previous syntax. All arguments
 | 
			
		||||
-- from above are passed in as fields instead (for instance,
 | 
			
		||||
-- `http.request("https://example.com")` becomes `http.request { url =
 | 
			
		||||
-- "https://example.com" }`).
 | 
			
		||||
--
 | 
			
		||||
-- This table also accepts several additional options:
 | 
			
		||||
--
 | 
			
		||||
--  - `method`: Which HTTP method to use, for instance `"PATCH"` or `"DELETE"`.
 | 
			
		||||
--  - `redirect`: Whether to follow HTTP redirects. Defaults to true.
 | 
			
		||||
--
 | 
			
		||||
-- @see http.get  For a synchronous way to make GET requests.
 | 
			
		||||
-- @see http.post For a synchronous way to make POST requests.
 | 
			
		||||
--
 | 
			
		||||
-- @changed 1.63 Added argument for headers.
 | 
			
		||||
-- @changed 1.80pr1 Added argument for binary handles.
 | 
			
		||||
-- @changed 1.80pr1.6 Added support for table argument.
 | 
			
		||||
-- @changed 1.86.0 Added PATCH and TRACE methods.
 | 
			
		||||
function request(...) end
 | 
			
		||||
 | 
			
		||||
--- Make a HTTP GET request to the given url.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam string url   The url to request
 | 
			
		||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
 | 
			
		||||
-- of this request.
 | 
			
		||||
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
 | 
			
		||||
-- the body will not be UTF-8 encoded, and the received response will not be
 | 
			
		||||
-- decoded.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam[2] {
 | 
			
		||||
--   url = string, headers? = { [string] = string },
 | 
			
		||||
--   binary? = boolean, method? = string, redirect? = boolean,
 | 
			
		||||
-- } request Options for the request. See @{http.request} for details on how
 | 
			
		||||
-- these options behave.
 | 
			
		||||
--
 | 
			
		||||
-- @treturn Response The resulting http response, which can be read from.
 | 
			
		||||
-- @treturn[2] nil When the http request failed, such as in the event of a 404
 | 
			
		||||
-- error or connection timeout.
 | 
			
		||||
-- @treturn string A message detailing why the request failed.
 | 
			
		||||
-- @treturn Response|nil The failing http response, if available.
 | 
			
		||||
--
 | 
			
		||||
-- @changed 1.63 Added argument for headers.
 | 
			
		||||
-- @changed 1.80pr1 Response handles are now returned on error if available.
 | 
			
		||||
-- @changed 1.80pr1 Added argument for binary handles.
 | 
			
		||||
-- @changed 1.80pr1.6 Added support for table argument.
 | 
			
		||||
-- @changed 1.86.0 Added PATCH and TRACE methods.
 | 
			
		||||
--
 | 
			
		||||
-- @usage Make a request to [example.tweaked.cc](https://example.tweaked.cc),
 | 
			
		||||
-- and print the returned page.
 | 
			
		||||
-- ```lua
 | 
			
		||||
-- local request = http.get("https://example.tweaked.cc")
 | 
			
		||||
-- print(request.readAll())
 | 
			
		||||
-- -- => HTTP is working!
 | 
			
		||||
-- request.close()
 | 
			
		||||
-- ```
 | 
			
		||||
function get(...) end
 | 
			
		||||
 | 
			
		||||
--- Make a HTTP POST request to the given url.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam string url   The url to request
 | 
			
		||||
-- @tparam string body  The body of the POST request.
 | 
			
		||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
 | 
			
		||||
-- of this request.
 | 
			
		||||
-- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true,
 | 
			
		||||
-- the body will not be UTF-8 encoded, and the received response will not be
 | 
			
		||||
-- decoded.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam[2] {
 | 
			
		||||
--   url = string, body? = string, headers? = { [string] = string },
 | 
			
		||||
--   binary? = boolean, method? = string, redirect? = boolean,
 | 
			
		||||
-- } request Options for the request. See @{http.request} for details on how
 | 
			
		||||
-- these options behave.
 | 
			
		||||
--
 | 
			
		||||
-- @treturn Response The resulting http response, which can be read from.
 | 
			
		||||
-- @treturn[2] nil When the http request failed, such as in the event of a 404
 | 
			
		||||
-- error or connection timeout.
 | 
			
		||||
-- @treturn string A message detailing why the request failed.
 | 
			
		||||
-- @treturn Response|nil The failing http response, if available.
 | 
			
		||||
--
 | 
			
		||||
-- @since 1.31
 | 
			
		||||
-- @changed 1.63 Added argument for headers.
 | 
			
		||||
-- @changed 1.80pr1 Response handles are now returned on error if available.
 | 
			
		||||
-- @changed 1.80pr1 Added argument for binary handles.
 | 
			
		||||
-- @changed 1.80pr1.6 Added support for table argument.
 | 
			
		||||
-- @changed 1.86.0 Added PATCH and TRACE methods.
 | 
			
		||||
function post(...) end
 | 
			
		||||
 | 
			
		||||
--- Asynchronously determine whether a URL can be requested.
 | 
			
		||||
--
 | 
			
		||||
-- If this returns `true`, one should also listen for @{http_check} which will
 | 
			
		||||
-- container further information about whether the URL is allowed or not.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam string url The URL to check.
 | 
			
		||||
-- @treturn true When this url is not invalid. This does not imply that it is
 | 
			
		||||
-- allowed - see the comment above.
 | 
			
		||||
-- @treturn[2] false When this url is invalid.
 | 
			
		||||
-- @treturn string A reason why this URL is not valid (for instance, if it is
 | 
			
		||||
-- malformed, or blocked).
 | 
			
		||||
--
 | 
			
		||||
-- @see http.checkURL For a synchronous version.
 | 
			
		||||
function checkURLAsync(url) end
 | 
			
		||||
 | 
			
		||||
--- Determine whether a URL can be requested.
 | 
			
		||||
--
 | 
			
		||||
-- If this returns `true`, one should also listen for @{http_check} which will
 | 
			
		||||
-- container further information about whether the URL is allowed or not.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam string url The URL to check.
 | 
			
		||||
-- @treturn true When this url is valid and can be requested via @{http.request}.
 | 
			
		||||
-- @treturn[2] false When this url is invalid.
 | 
			
		||||
-- @treturn string A reason why this URL is not valid (for instance, if it is
 | 
			
		||||
-- malformed, or blocked).
 | 
			
		||||
--
 | 
			
		||||
-- @see http.checkURLAsync For an asynchronous version.
 | 
			
		||||
--
 | 
			
		||||
-- @usage
 | 
			
		||||
-- ```lua
 | 
			
		||||
-- print(http.checkURL("https://example.tweaked.cc/"))
 | 
			
		||||
-- -- => true
 | 
			
		||||
-- print(http.checkURL("http://localhost/"))
 | 
			
		||||
-- -- => false Domain not permitted
 | 
			
		||||
-- print(http.checkURL("not a url"))
 | 
			
		||||
-- -- => false URL malformed
 | 
			
		||||
-- ```
 | 
			
		||||
function checkURL(url) end
 | 
			
		||||
 | 
			
		||||
--- Open a websocket.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam string url The websocket url to connect to. This should have the
 | 
			
		||||
-- `ws://` or `wss://` protocol.
 | 
			
		||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
 | 
			
		||||
-- of the initial websocket connection.
 | 
			
		||||
--
 | 
			
		||||
-- @treturn Websocket The websocket connection.
 | 
			
		||||
-- @treturn[2] false If the websocket connection failed.
 | 
			
		||||
-- @treturn string An error message describing why the connection failed.
 | 
			
		||||
-- @since 1.80pr1.1
 | 
			
		||||
-- @changed 1.80pr1.3 No longer asynchronous.
 | 
			
		||||
-- @changed 1.95.3 Added User-Agent to default headers.
 | 
			
		||||
function websocket(url, headers) end
 | 
			
		||||
 | 
			
		||||
--- Asynchronously open a websocket.
 | 
			
		||||
--
 | 
			
		||||
-- This returns immediately, a @{websocket_success} or @{websocket_failure}
 | 
			
		||||
-- will be queued once the request has completed.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam string url The websocket url to connect to. This should have the
 | 
			
		||||
-- `ws://` or `wss://` protocol.
 | 
			
		||||
-- @tparam[opt] { [string] = string } headers Additional headers to send as part
 | 
			
		||||
-- of the initial websocket connection.
 | 
			
		||||
-- @since 1.80pr1.3
 | 
			
		||||
-- @changed 1.95.3 Added User-Agent to default headers.
 | 
			
		||||
function websocketAsync(url, headers) end
 | 
			
		||||
@@ -5,7 +5,8 @@ kotlin.stdlib.default.dependency=false
 | 
			
		||||
kotlin.jvm.target.validation.mode=error
 | 
			
		||||
 | 
			
		||||
# Mod properties
 | 
			
		||||
modVersion=1.101.0
 | 
			
		||||
isUnstable=true
 | 
			
		||||
modVersion=1.102.1
 | 
			
		||||
 | 
			
		||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
 | 
			
		||||
mcVersion=1.18.2
 | 
			
		||||
mcVersion=1.19.3
 | 
			
		||||
 
 | 
			
		||||
@@ -2,70 +2,152 @@
 | 
			
		||||
 | 
			
		||||
# Minecraft
 | 
			
		||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
 | 
			
		||||
forge = "40.1.0"
 | 
			
		||||
parchment = "2022.03.13"
 | 
			
		||||
parchmentMc = "1.18.2"
 | 
			
		||||
fabric-api = "0.68.1+1.19.3"
 | 
			
		||||
fabric-loader = "0.14.11"
 | 
			
		||||
forge = "44.1.0"
 | 
			
		||||
forgeSpi = "6.0.0"
 | 
			
		||||
mixin = "0.8.5"
 | 
			
		||||
parchment = "2022.11.27"
 | 
			
		||||
parchmentMc = "1.19.2"
 | 
			
		||||
 | 
			
		||||
# Normal dependencies
 | 
			
		||||
asm = "9.3"
 | 
			
		||||
autoService = "1.0.1"
 | 
			
		||||
cobalt = { strictly = "[0.5.8,0.6.0)", prefer = "0.5.8" }
 | 
			
		||||
checkerFramework = "3.12.0"
 | 
			
		||||
cobalt = "0.5.9"
 | 
			
		||||
fastutil = "8.5.9"
 | 
			
		||||
guava = "31.1-jre"
 | 
			
		||||
jetbrainsAnnotations = "23.0.0"
 | 
			
		||||
jsr305 = "3.0.2"
 | 
			
		||||
kotlin = "1.7.10"
 | 
			
		||||
kotlin-coroutines = "1.6.0"
 | 
			
		||||
netty = "4.1.82.Final"
 | 
			
		||||
nightConfig = "3.6.5"
 | 
			
		||||
slf4j = "1.7.36"
 | 
			
		||||
 | 
			
		||||
# Minecraft mods
 | 
			
		||||
forgeConfig = "5.0.4"
 | 
			
		||||
iris = "1.19.3-v1.4.6"
 | 
			
		||||
jei = "11.3.0.262"
 | 
			
		||||
modmenu = "5.0.1"
 | 
			
		||||
oculus = "1.2.5"
 | 
			
		||||
rei = "10.0.578"
 | 
			
		||||
rubidium = "0.6.1"
 | 
			
		||||
sodium = "mc1.19.3-0.4.6"
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
byteBuddy = "1.12.19"
 | 
			
		||||
hamcrest = "2.2"
 | 
			
		||||
jqwik = "1.7.0"
 | 
			
		||||
junit = "5.9.1"
 | 
			
		||||
 | 
			
		||||
# Build tools
 | 
			
		||||
cctJavadoc = "1.5.2"
 | 
			
		||||
cctJavadoc = "1.5.3"
 | 
			
		||||
checkstyle = "10.3.4"
 | 
			
		||||
curseForgeGradle = "1.0.11"
 | 
			
		||||
errorProne-core = "2.14.0"
 | 
			
		||||
errorProne-plugin = "2.0.2"
 | 
			
		||||
fabric-loom = "1.0-SNAPSHOT"
 | 
			
		||||
forgeGradle = "5.1.+"
 | 
			
		||||
githubRelease = "2.2.12"
 | 
			
		||||
illuaminate = "0.1.0-7-g2a5a89c"
 | 
			
		||||
ideaExt = "1.1.6"
 | 
			
		||||
illuaminate = "0.1.0-12-ga03e9cd"
 | 
			
		||||
librarian = "1.+"
 | 
			
		||||
minotaur = "2.+"
 | 
			
		||||
mixinGradle = "0.7.+"
 | 
			
		||||
nullAway = "0.9.9"
 | 
			
		||||
quiltflower = "1.7.3"
 | 
			
		||||
shadow = "7.1.2"
 | 
			
		||||
spotless = "6.8.0"
 | 
			
		||||
taskTree = "2.1.0"
 | 
			
		||||
vanillaGradle = "0.2.1-SNAPSHOT"
 | 
			
		||||
 | 
			
		||||
[libraries]
 | 
			
		||||
# Normal dependencies
 | 
			
		||||
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
 | 
			
		||||
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
 | 
			
		||||
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
 | 
			
		||||
cobalt = { module = "org.squiddev:Cobalt", version.ref = "cobalt" }
 | 
			
		||||
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
 | 
			
		||||
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
 | 
			
		||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
 | 
			
		||||
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
 | 
			
		||||
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
 | 
			
		||||
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
 | 
			
		||||
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
 | 
			
		||||
netty-http = { module = "io.netty:netty-codec-http", version.ref = "netty" }
 | 
			
		||||
nightConfig-core = { module = "com.electronwill.night-config:core", version.ref = "nightConfig" }
 | 
			
		||||
nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref = "nightConfig" }
 | 
			
		||||
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
 | 
			
		||||
 | 
			
		||||
# Minecraft mods
 | 
			
		||||
fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
 | 
			
		||||
fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
 | 
			
		||||
forgeConfig = { module = "fuzs.forgeconfigapiport:forgeconfigapiport-fabric", version.ref = "forgeConfig" }
 | 
			
		||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
 | 
			
		||||
jei-api = { module = "mezz.jei:jei-1.19.2-common-api", version.ref = "jei" }
 | 
			
		||||
jei-fabric = { module = "mezz.jei:jei-1.19.2-fabric", version.ref = "jei" }
 | 
			
		||||
jei-forge = { module = "mezz.jei:jei-1.19.2-forge", version.ref = "jei" }
 | 
			
		||||
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
 | 
			
		||||
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
 | 
			
		||||
oculus = { module = "maven.modrinth:oculus", version.ref = "oculus" }
 | 
			
		||||
rei-api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" }
 | 
			
		||||
rei-builtin = { module = "me.shedaniel:RoughlyEnoughItems-default-plugin", version.ref = "rei" }
 | 
			
		||||
rei-fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
 | 
			
		||||
rubidium = { module = "maven.modrinth:rubidium", version.ref = "rubidium" }
 | 
			
		||||
sodium = { module = "maven.modrinth:sodium", version.ref = "sodium" }
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
byteBuddyAgent = { module ="net.bytebuddy:byte-buddy-agent", version.ref = "byteBuddy" }
 | 
			
		||||
byteBuddy = { module ="net.bytebuddy:byte-buddy", version.ref = "byteBuddy" }
 | 
			
		||||
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
 | 
			
		||||
jqwik-api = { module = "net.jqwik:jqwik-api", version.ref = "jqwik" }
 | 
			
		||||
jqwik-engine = { module = "net.jqwik:jqwik-engine", version.ref = "jqwik" }
 | 
			
		||||
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
 | 
			
		||||
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
 | 
			
		||||
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
 | 
			
		||||
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
 | 
			
		||||
 | 
			
		||||
# Build tools
 | 
			
		||||
cctJavadoc = { module = "cc.tweaked:cct-javadoc", version.ref = "cctJavadoc" }
 | 
			
		||||
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
 | 
			
		||||
errorProne-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "errorProne-core" }
 | 
			
		||||
errorProne-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "errorProne-core" }
 | 
			
		||||
errorProne-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne-core" }
 | 
			
		||||
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
 | 
			
		||||
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
 | 
			
		||||
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
 | 
			
		||||
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
 | 
			
		||||
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
 | 
			
		||||
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
 | 
			
		||||
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
 | 
			
		||||
quiltflower = { module = "io.github.juuxel:loom-quiltflower", version.ref = "quiltflower" }
 | 
			
		||||
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
 | 
			
		||||
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
 | 
			
		||||
 | 
			
		||||
[plugins]
 | 
			
		||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
 | 
			
		||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
 | 
			
		||||
curseForgeGradle = { id = "net.darkhax.curseforgegradle", version.ref = "curseForgeGradle" }
 | 
			
		||||
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
 | 
			
		||||
minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" }
 | 
			
		||||
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
 | 
			
		||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
 | 
			
		||||
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
 | 
			
		||||
ideaExt = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaExt" }
 | 
			
		||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
 | 
			
		||||
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
 | 
			
		||||
minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" }
 | 
			
		||||
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
 | 
			
		||||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
 | 
			
		||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
 | 
			
		||||
 | 
			
		||||
[bundles]
 | 
			
		||||
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
 | 
			
		||||
 | 
			
		||||
# Minecraft
 | 
			
		||||
externalMods-common = ["jei-api", "forgeConfig", "nightConfig-core", "nightConfig-toml"]
 | 
			
		||||
externalMods-forge-compile = ["oculus", "jei-api"]
 | 
			
		||||
externalMods-forge-runtime = []
 | 
			
		||||
externalMods-fabric = ["fabric-loader", "fabric-api", "forgeConfig", "nightConfig-core", "nightConfig-toml"]
 | 
			
		||||
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"]
 | 
			
		||||
externalMods-fabric-runtime = ["modmenu"]
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
 | 
			
		||||
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
 | 
			
		||||
 
 | 
			
		||||
@@ -2,25 +2,26 @@
 | 
			
		||||
 | 
			
		||||
(sources
 | 
			
		||||
  /doc/
 | 
			
		||||
  /build/docs/luaJavadoc/
 | 
			
		||||
  /src/main/resources/*/computercraft/lua/bios.lua
 | 
			
		||||
  /src/main/resources/*/computercraft/lua/rom/
 | 
			
		||||
  /src/test/resources/test-rom
 | 
			
		||||
  /src/web/mount)
 | 
			
		||||
  /projects/forge/build/docs/luaJavadoc/
 | 
			
		||||
  /projects/core/src/main/resources/data/computercraft/lua/bios.lua
 | 
			
		||||
  /projects/core/src/main/resources/data/computercraft/lua/rom/
 | 
			
		||||
  /projects/core/src/test/resources/test-rom
 | 
			
		||||
  /projects/web/src/mount)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
(doc
 | 
			
		||||
  (destination build/illuaminate)
 | 
			
		||||
  ; Also defined in projects/web/build.gradle.kts
 | 
			
		||||
  (destination /projects/web/build/illuaminate)
 | 
			
		||||
  (index doc/index.md)
 | 
			
		||||
 | 
			
		||||
  (site
 | 
			
		||||
    (title "CC: Tweaked")
 | 
			
		||||
    (logo src/main/resources/pack.png)
 | 
			
		||||
    (logo projects/common/src/main/resources/pack.png)
 | 
			
		||||
    (url https://tweaked.cc/)
 | 
			
		||||
    (source-link https://github.com/cc-tweaked/CC-Tweaked/blob/${commit}/${path}#L${line})
 | 
			
		||||
 | 
			
		||||
    (styles src/web/styles.css)
 | 
			
		||||
    (scripts build/rollup/index.js)
 | 
			
		||||
    (styles /projects/web/src/styles.css)
 | 
			
		||||
    (scripts /projects/web/build/rollup/index.js)
 | 
			
		||||
    (head doc/head.html))
 | 
			
		||||
 | 
			
		||||
  (module-kinds
 | 
			
		||||
@@ -32,15 +33,15 @@
 | 
			
		||||
 | 
			
		||||
  (library-path
 | 
			
		||||
    /doc/stub/
 | 
			
		||||
    /build/docs/luaJavadoc/
 | 
			
		||||
    /projects/forge/build/docs/luaJavadoc/
 | 
			
		||||
 | 
			
		||||
    /src/main/resources/*/computercraft/lua/rom/apis/
 | 
			
		||||
    /src/main/resources/*/computercraft/lua/rom/apis/command/
 | 
			
		||||
    /src/main/resources/*/computercraft/lua/rom/apis/turtle/
 | 
			
		||||
    /projects/core/src/main/resources/data/computercraft/lua/rom/apis/
 | 
			
		||||
    /projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
 | 
			
		||||
    /projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/
 | 
			
		||||
 | 
			
		||||
    /src/main/resources/*/computercraft/lua/rom/modules/main/
 | 
			
		||||
    /src/main/resources/*/computercraft/lua/rom/modules/command/
 | 
			
		||||
    /src/main/resources/*/computercraft/lua/rom/modules/turtle/))
 | 
			
		||||
    /projects/core/src/main/resources/data/computercraft/lua/rom/modules/main/
 | 
			
		||||
    /projects/core/src/main/resources/data/computercraft/lua/rom/modules/command/
 | 
			
		||||
    /projects/core/src/main/resources/data/computercraft/lua/rom/modules/turtle/))
 | 
			
		||||
 | 
			
		||||
(at /
 | 
			
		||||
  (linters
 | 
			
		||||
@@ -80,37 +81,37 @@
 | 
			
		||||
;; We disable the unused global linter in bios.lua and the APIs. In the future
 | 
			
		||||
;; hopefully we'll get illuaminate to handle this.
 | 
			
		||||
(at
 | 
			
		||||
  (/src/main/resources/*/computercraft/lua/bios.lua
 | 
			
		||||
   /src/main/resources/*/computercraft/lua/rom/apis/)
 | 
			
		||||
  (/projects/core/src/main/resources/data/computercraft/lua/bios.lua
 | 
			
		||||
   /projects/core/src/main/resources/data/computercraft/lua/rom/apis/)
 | 
			
		||||
  (linters -var:unused-global)
 | 
			
		||||
  (lint (allow-toplevel-global true)))
 | 
			
		||||
 | 
			
		||||
;; Silence some variable warnings in documentation stubs.
 | 
			
		||||
(at (/doc/stub/ /build/docs/luaJavadoc/)
 | 
			
		||||
(at (/doc/stub/ /projects/forge/build/docs/luaJavadoc/)
 | 
			
		||||
  (linters -var:unused-global)
 | 
			
		||||
  (lint (allow-toplevel-global true)))
 | 
			
		||||
 | 
			
		||||
;; Suppress warnings for currently undocumented modules.
 | 
			
		||||
(at
 | 
			
		||||
  (; Lua APIs
 | 
			
		||||
   /src/main/resources/*/computercraft/lua/rom/apis/io.lua
 | 
			
		||||
   /src/main/resources/*/computercraft/lua/rom/apis/window.lua)
 | 
			
		||||
   /projects/core/src/main/resources/data/computercraft/lua/rom/apis/io.lua
 | 
			
		||||
   /projects/core/src/main/resources/data/computercraft/lua/rom/apis/window.lua)
 | 
			
		||||
 | 
			
		||||
  (linters -doc:undocumented -doc:undocumented-arg -doc:undocumented-return))
 | 
			
		||||
 | 
			
		||||
;; Suppress warnings for various APIs using its own deprecated members.
 | 
			
		||||
(at
 | 
			
		||||
  (/src/main/resources/*/computercraft/lua/bios.lua
 | 
			
		||||
   /src/main/resources/*/computercraft/lua/rom/apis/turtle/turtle.lua)
 | 
			
		||||
  (/projects/core/src/main/resources/data/computercraft/lua/bios.lua
 | 
			
		||||
   /projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua)
 | 
			
		||||
  (linters -var:deprecated))
 | 
			
		||||
 | 
			
		||||
(at /src/test/resources/test-rom
 | 
			
		||||
(at /projects/core/src/test/resources/test-rom
 | 
			
		||||
  ; We should still be able to test deprecated members.
 | 
			
		||||
  (linters -var:deprecated)
 | 
			
		||||
 | 
			
		||||
  (lint
 | 
			
		||||
    (globals
 | 
			
		||||
      :max sleep write
 | 
			
		||||
      cct_test describe expect howlci fail it pending stub)))
 | 
			
		||||
      cct_test describe expect howlci fail it pending stub before_each)))
 | 
			
		||||
 | 
			
		||||
(at /src/web/mount/expr_template.lua (lint (globals :max __expr__)))
 | 
			
		||||
(at /projects/web/src/mount/expr_template.lua (lint (globals :max __expr__)))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										159
									
								
								projects/ARCHITECTURE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								projects/ARCHITECTURE.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
			
		||||
# Architecture
 | 
			
		||||
CC: Tweaked has a rather complex project layout, as there's several use-cases we want to support (multiple mod loaders,
 | 
			
		||||
usable outside of Minecraft). As such, it can be tricky to understand how the code is structured and how the various
 | 
			
		||||
sub-projects interact. This document provides a high-level overview of the entire mod.
 | 
			
		||||
 | 
			
		||||
## Project Outline
 | 
			
		||||
CC: Tweaked is split into 4 primary modules (`core`, `common`, `fabric`, `forge`). These themselves are then split into
 | 
			
		||||
a public API (i.e `core-api`) and the actual implementation (i.e. `core`).
 | 
			
		||||
 | 
			
		||||
 - `core`: This contains the core "computer" part of ComputerCraft, such as the Lua VM, filesystem and builtin APIs.
 | 
			
		||||
   This is also where the Lua ROM is located (`projects/core/src/main/resources/data/computercraft/lua`). Notably this
 | 
			
		||||
   project does _not_ depend on Minecraft, making it possible to use it in emulators and other tooling.
 | 
			
		||||
 | 
			
		||||
 - `common`: This contains all non mod-loader-specific Minecraft code. This is where computers, turtles and peripherals
 | 
			
		||||
   are defined (and everything else Minecraft-related!).
 | 
			
		||||
 | 
			
		||||
   This project is separates client code into its own separate source set (suitably named `client`). This helps us
 | 
			
		||||
   ensure that server code can never reference client-only code (such as LWJGL).
 | 
			
		||||
 | 
			
		||||
 - `forge` and `fabric`: These contain any mod-loader specific code.
 | 
			
		||||
 | 
			
		||||
When we need to call loader-specific code from our own code (for instance, sending network messages or firing
 | 
			
		||||
loader-specific events), we use a `PlatformHelper` interface (defined in
 | 
			
		||||
`projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java`). This abstracts over most
 | 
			
		||||
loader-specific code we need to use, and is then implemented by each mod-loader-specific project. The concrete
 | 
			
		||||
implementation is then loaded with Java's [`ServiceLoader`][ServiceLoader], in a design based on [jaredlll08's
 | 
			
		||||
multi-loader template][MultiLoader-Template]. We use a similar system for communicating between the API and its
 | 
			
		||||
implementation.
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
subgraph Common
 | 
			
		||||
    platform(PlatformHelper)
 | 
			
		||||
    impl[AbstractComputerCraftAPI]
 | 
			
		||||
end
 | 
			
		||||
subgraph API
 | 
			
		||||
    api(ComputerCraft API) --> impl
 | 
			
		||||
end
 | 
			
		||||
subgraph Forge[Forge]
 | 
			
		||||
    platform --> forgePlatform[PlatformHelperImpl]
 | 
			
		||||
    impl -.-> forgeImpl[ComputerCraftAPIImpl]
 | 
			
		||||
end
 | 
			
		||||
subgraph Fabric
 | 
			
		||||
    platform --> fabricPlatform[PlatformHelperImpl]
 | 
			
		||||
    impl -.-> fabricImpl[ComputerCraftAPIImpl]
 | 
			
		||||
end
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Note the `PlatformHelper` is only used when calling from our code into loader-specific code. While we use this to _fire_
 | 
			
		||||
events, we do not use it to _subscribe_ to events. For that we just subscribe to the events in the loader-specific
 | 
			
		||||
project, and then dispatch to the common `CommonHooks` (for shared code) and `ClientHooks` (for client-specific code).
 | 
			
		||||
 | 
			
		||||
You may notice there's a couple of other, smaller modules in the codebase. These you can probably ignore, but are worth
 | 
			
		||||
mentioning:
 | 
			
		||||
 | 
			
		||||
 - `lints`: This defines an [ErrorProne] plugin which adds a couple of compile-time checks to our code. This is what
 | 
			
		||||
   enforces that no client-specific code is used inside the `main` source set (and a couple of other things!).
 | 
			
		||||
 | 
			
		||||
 - `web`: This contains the additional tooling for building [the documentation website][tweaked.cc], such as support for
 | 
			
		||||
   rendering recipes
 | 
			
		||||
 | 
			
		||||
 - `buildSrc` (in the base directory, not in `projects/`): This contains any build logic shared between modules. For
 | 
			
		||||
   instance, `cc-tweaked.java-convention.gradle.kts` sets up the defaults for Java that we use across the whole project.
 | 
			
		||||
 | 
			
		||||
> **Note**
 | 
			
		||||
> The Forge and Fabric modules (and their API counterparts) depend on the common modules. However, in order to correctly
 | 
			
		||||
> process mixins we need to compile the common code along with the Forge/Fabric code. This leads to a slightly strange
 | 
			
		||||
> build process:
 | 
			
		||||
>
 | 
			
		||||
>  - In your IDE, Forge/Fabric depend on the common as normal.
 | 
			
		||||
>  - When building via Gradle, the common code is compiled alongside Forge/Fabric.
 | 
			
		||||
>
 | 
			
		||||
> You shouldn't need to worry about this - it should all be set up automatically - but hopefully explains a little bit
 | 
			
		||||
> why our Gradle scripts are slightly odd!
 | 
			
		||||
 | 
			
		||||
## Testing
 | 
			
		||||
CC: Tweaked has a small (though growing!) test suite to ensure various features behave correctly. Most tests are written
 | 
			
		||||
in Java using [JUnit], though we also make use of [jqwik] for property testing.
 | 
			
		||||
 | 
			
		||||
### Test Fixtures
 | 
			
		||||
Some projects define an additional `testFixtures` folder alongside their main `test` code (i.e.
 | 
			
		||||
`projects/core/src/testFixtures`). This source set contains test-related code which might be consumed in dependent
 | 
			
		||||
projects. For instance, core's test fixtures defines additional [Hamcrest] matchers, which are used in both `core` and
 | 
			
		||||
`common`'s test suite.
 | 
			
		||||
 | 
			
		||||
Test fixtures may also define [Test Interfaces]. This is a pattern for writing tests to ensure that an implementation
 | 
			
		||||
obeys its interface's contract. For instance, we might have a `ListContract` test, which asserts an abstract list
 | 
			
		||||
behaves as expected:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
interface ListContract<T extends List<Integer>> {
 | 
			
		||||
    T newList();
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    default void testAddInsert() {
 | 
			
		||||
        var list = newList();
 | 
			
		||||
        assertTrue(list.add(123));
 | 
			
		||||
        assertTrue(list.contains(123));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
We can then use this interface to create tests for a specific implementation:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
class ArrayListTest implements ListContract<ArrayList<Integer>> {
 | 
			
		||||
    @Override public ArrayList<Integer> newList() { return new ArrayList<>(); }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This is especially useful when testing `PlatformHelper` and other mod loader abstractions.
 | 
			
		||||
 | 
			
		||||
### Lua tests
 | 
			
		||||
While the majority of CC: Tweaked is written in Java, a significant portion of the code is written in Lua. As such, it's
 | 
			
		||||
also useful to test that.
 | 
			
		||||
 | 
			
		||||
This is done by starting a Lua VM with all of ComputerCraft's APIs loaded, then starting a custom test framework
 | 
			
		||||
(`mcfly.lua`). This test framework discovers tests and sends them back to the Java side. These are turned into JUnit
 | 
			
		||||
tests which are then in turn run on the computer again. This allows the tests to integrate with existing Java testing
 | 
			
		||||
tooling (for instance, XML test reports and IDE integration).
 | 
			
		||||
 | 
			
		||||
There's a slightly more detailed description of the process at `ComputerTestDelegate.java`.
 | 
			
		||||
 | 
			
		||||
### Game tests
 | 
			
		||||
CC: Tweaked also runs several tests in-game using Minecraft's [gametest framework][mc-test]. These work by starting
 | 
			
		||||
a Minecraft server and then, for each test, spawning a structure and then interacting with the blocks inside the
 | 
			
		||||
structure, asserting they behave as expected.
 | 
			
		||||
 | 
			
		||||
Unlike most of our other tests, these are written in Kotlin. We make extensive use of [extension methods] to augment
 | 
			
		||||
vanilla's own test classes, which helps give a more consistent feel to the API.
 | 
			
		||||
 | 
			
		||||
Each test works by defining a sequence of steps. Each step can either run an action (`thenExecute`), sleep for a period
 | 
			
		||||
(`thenIdle`) or sleep until a condition is met (`thenWaitUntil`).
 | 
			
		||||
 | 
			
		||||
```kotlin
 | 
			
		||||
fun Some_test(context: GameTestHelper) = context.sequence {
 | 
			
		||||
    thenExecute { context.setBlock(BlockPos(2, 2, 2), Blocks.AIR) }
 | 
			
		||||
    thenIdle(4)
 | 
			
		||||
    thenExecute { context.assertBlockHas(lamp, RedstoneLampBlock.LIT, false, "Lamp should not be lit") }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Some tests need to use Lua APIs from a computer, such as when testing `turtle.dig`. In order to do this, we install
 | 
			
		||||
a custom "Lua" runtime (see `ManagedComputers.kt`) which actually runs Java functions. Tests can then enqueue a function
 | 
			
		||||
to run on a particular computer and then wait for it to finish.
 | 
			
		||||
 | 
			
		||||
While the internals of this is quite complex, it ends up being a much nicer workflow than writing parts of the test in
 | 
			
		||||
Lua. It also ends up being much more efficient, which is important when running a dozen tests at once!
 | 
			
		||||
 | 
			
		||||
[MultiLoader-Template]: https://github.com/jaredlll08/MultiLoader-Template/ "MultiLoader-Template - A template for a Forge + Fabric project setup using a Common source set."
 | 
			
		||||
[ServiceLoader]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html "ServiceLoader (Java SE 17 and JDK 17)"
 | 
			
		||||
[ErrorProne]: https://errorprone.info/ "ErrorProne"
 | 
			
		||||
[tweaked.cc]: https://tweaked.cc "CC: Tweaked"
 | 
			
		||||
[JUnit]: https://junit.org/junit5/ "JUnit 5"
 | 
			
		||||
[jqwik]: https://jqwik.net/
 | 
			
		||||
[Hamcrest]: https://hamcrest.org/JavaHamcrest/ "Java Hamcrest"
 | 
			
		||||
[Test Interfaces]: https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-interfaces-and-default-methods
 | 
			
		||||
[mc-test]: https://www.youtube.com/watch?v=vXaWOJTCYNg "Testing Minecraft in Minecraft on YouTube"
 | 
			
		||||
[extension methods]: https://kotlinlang.org/docs/extensions.html "Extensions | Kotlin"
 | 
			
		||||
							
								
								
									
										20
									
								
								projects/common-api/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								projects/common-api/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
    id("cc-tweaked.publishing")
 | 
			
		||||
    id("cc-tweaked.vanilla")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
java {
 | 
			
		||||
    withJavadocJar()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    api(project(":core-api"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.javadoc {
 | 
			
		||||
    include("dan200/computercraft/api/**/*.java")
 | 
			
		||||
 | 
			
		||||
    // Include the core-api in our javadoc export. This is wrong, but it means we can export a single javadoc dump.
 | 
			
		||||
    source(project(":core-api").sourceSets.main.map { it.allJava })
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,35 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
public final class ComputerCraftAPIClient {
 | 
			
		||||
    private ComputerCraftAPIClient() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This may be called at any point after registry creation, though it is recommended to call it within your client
 | 
			
		||||
     * setup step.
 | 
			
		||||
     *
 | 
			
		||||
     * @param serialiser The turtle upgrade serialiser.
 | 
			
		||||
     * @param modeller   The upgrade modeller.
 | 
			
		||||
     * @param <T>        The type of the turtle upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    public static <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
 | 
			
		||||
        getInstance().registerTurtleUpgradeModeller(serialiser, modeller);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ComputerCraftAPIClientService getInstance() {
 | 
			
		||||
        return ComputerCraftAPIClientService.get();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,57 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.client;
 | 
			
		||||
 | 
			
		||||
import com.mojang.math.Transformation;
 | 
			
		||||
import dan200.computercraft.impl.client.ClientPlatformHelper;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A model to render, combined with a transformation matrix to apply.
 | 
			
		||||
 */
 | 
			
		||||
public final class TransformedModel {
 | 
			
		||||
    private final BakedModel model;
 | 
			
		||||
    private final Transformation matrix;
 | 
			
		||||
 | 
			
		||||
    public TransformedModel(BakedModel model, Transformation matrix) {
 | 
			
		||||
        this.model = Objects.requireNonNull(model);
 | 
			
		||||
        this.matrix = Objects.requireNonNull(matrix);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TransformedModel(BakedModel model) {
 | 
			
		||||
        this.model = Objects.requireNonNull(model);
 | 
			
		||||
        matrix = Transformation.identity();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static TransformedModel of(ModelResourceLocation location) {
 | 
			
		||||
        var modelManager = Minecraft.getInstance().getModelManager();
 | 
			
		||||
        return new TransformedModel(modelManager.getModel(location));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static TransformedModel of(ResourceLocation location) {
 | 
			
		||||
        var modelManager = Minecraft.getInstance().getModelManager();
 | 
			
		||||
        return new TransformedModel(ClientPlatformHelper.get().getModel(modelManager, location));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static TransformedModel of(ItemStack item, Transformation transform) {
 | 
			
		||||
        var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(item);
 | 
			
		||||
        return new TransformedModel(model, transform);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BakedModel getModel() {
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Transformation getMatrix() {
 | 
			
		||||
        return matrix;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,76 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.client.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.client.ComputerCraftAPIClient;
 | 
			
		||||
import dan200.computercraft.api.client.TransformedModel;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides models for a {@link ITurtleUpgrade}.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of turtle upgrade this modeller applies to.
 | 
			
		||||
 * @see ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller) To register a modeller.
 | 
			
		||||
 */
 | 
			
		||||
public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Obtain the model to be used when rendering a turtle peripheral.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * When the current turtle is {@literal null}, this function should be constant for a given upgrade and side.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade The upgrade that you're getting the model for.
 | 
			
		||||
     * @param turtle  Access to the turtle that the upgrade resides on. This will be null when getting item models!
 | 
			
		||||
     * @param side    Which side of the turtle (left or right) the upgrade resides on.
 | 
			
		||||
     * @return The model that you wish to be used to render your upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getCraftingItem()
 | 
			
		||||
     * crafting item}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This uses appropriate transformations for "flat" items, namely those extending the {@literal minecraft:item/generated}
 | 
			
		||||
     * model type. It will not appear correct for 3D models with additional depth, such as blocks.
 | 
			
		||||
     *
 | 
			
		||||
     * @param <T> The type of the turtle upgrade.
 | 
			
		||||
     * @return The constructed modeller.
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
 | 
			
		||||
        return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.FLAT_ITEM;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
 | 
			
		||||
     *
 | 
			
		||||
     * @param left  The model to use on the left.
 | 
			
		||||
     * @param right The model to use on the right.
 | 
			
		||||
     * @param <T>   The type of the turtle upgrade.
 | 
			
		||||
     * @return The constructed modeller.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelResourceLocation left, ModelResourceLocation right) {
 | 
			
		||||
        return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
 | 
			
		||||
     *
 | 
			
		||||
     * @param left  The model to use on the left.
 | 
			
		||||
     * @param right The model to use on the right.
 | 
			
		||||
     * @param <T>   The type of the turtle upgrade.
 | 
			
		||||
     * @return The constructed modeller.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
 | 
			
		||||
        return (upgrade, turtle, side) -> TransformedModel.of(side == TurtleSide.LEFT ? left : right);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.client.turtle;
 | 
			
		||||
 | 
			
		||||
import com.mojang.math.Transformation;
 | 
			
		||||
import dan200.computercraft.api.client.TransformedModel;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
 | 
			
		||||
class TurtleUpgradeModellers {
 | 
			
		||||
    private static final Transformation leftTransform = getMatrixFor(-0.40625f);
 | 
			
		||||
    private static final Transformation rightTransform = getMatrixFor(0.40625f);
 | 
			
		||||
 | 
			
		||||
    private static Transformation getMatrixFor(float offset) {
 | 
			
		||||
        var matrix = new Matrix4f();
 | 
			
		||||
        matrix.set(new float[]{
 | 
			
		||||
            0.0f, 0.0f, -1.0f, 1.0f + offset,
 | 
			
		||||
            1.0f, 0.0f, 0.0f, 0.0f,
 | 
			
		||||
            0.0f, -1.0f, 0.0f, 1.0f,
 | 
			
		||||
            0.0f, 0.0f, 0.0f, 1.0f,
 | 
			
		||||
        });
 | 
			
		||||
        matrix.transpose();
 | 
			
		||||
        return new Transformation(matrix);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->
 | 
			
		||||
        TransformedModel.of(upgrade.getCraftingItem(), side == TurtleSide.LEFT ? leftTransform : rightTransform);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.impl.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.impl.Services;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelManager;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public interface ClientPlatformHelper {
 | 
			
		||||
    /**
 | 
			
		||||
     * Equivalent to {@link ModelManager#getModel(ModelResourceLocation)} but for arbitrary {@link ResourceLocation}s.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manager  The model manager.
 | 
			
		||||
     * @param location The model location.
 | 
			
		||||
     * @return The baked model.
 | 
			
		||||
     */
 | 
			
		||||
    BakedModel getModel(ModelManager manager, ResourceLocation location);
 | 
			
		||||
 | 
			
		||||
    static ClientPlatformHelper get() {
 | 
			
		||||
        var instance = Instance.INSTANCE;
 | 
			
		||||
        return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final class Instance {
 | 
			
		||||
        static final @Nullable ClientPlatformHelper INSTANCE;
 | 
			
		||||
        static final @Nullable Throwable ERROR;
 | 
			
		||||
 | 
			
		||||
        static {
 | 
			
		||||
            var helper = Services.tryLoad(ClientPlatformHelper.class);
 | 
			
		||||
            INSTANCE = helper.instance();
 | 
			
		||||
            ERROR = helper.error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Instance() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.impl.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.client.ComputerCraftAPIClient;
 | 
			
		||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.Services;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Backing interface for {@link ComputerCraftAPIClient}
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public interface ComputerCraftAPIClientService {
 | 
			
		||||
    static ComputerCraftAPIClientService get() {
 | 
			
		||||
        var instance = Instance.INSTANCE;
 | 
			
		||||
        return instance == null ? Services.raise(ComputerCraftAPIClientService.class, Instance.ERROR) : instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller);
 | 
			
		||||
 | 
			
		||||
    final class Instance {
 | 
			
		||||
        static final @Nullable ComputerCraftAPIClientService INSTANCE;
 | 
			
		||||
        static final @Nullable Throwable ERROR;
 | 
			
		||||
 | 
			
		||||
        static {
 | 
			
		||||
            var helper = Services.tryLoad(ComputerCraftAPIClientService.class);
 | 
			
		||||
            INSTANCE = helper.instance();
 | 
			
		||||
            ERROR = helper.error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Instance() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,203 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.filesystem.Mount;
 | 
			
		||||
import dan200.computercraft.api.filesystem.WritableMount;
 | 
			
		||||
import dan200.computercraft.api.lua.GenericSource;
 | 
			
		||||
import dan200.computercraft.api.lua.ILuaAPI;
 | 
			
		||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
 | 
			
		||||
import dan200.computercraft.api.media.IMedia;
 | 
			
		||||
import dan200.computercraft.api.media.MediaProvider;
 | 
			
		||||
import dan200.computercraft.api.network.PacketNetwork;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredElement;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNode;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IComputerAccess;
 | 
			
		||||
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.server.MinecraftServer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The static entry point to the ComputerCraft API.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Members in this class must be called after ComputerCraft has been initialised, but may be called before it is
 | 
			
		||||
 * fully loaded.
 | 
			
		||||
 */
 | 
			
		||||
public final class ComputerCraftAPI {
 | 
			
		||||
    public static final String MOD_ID = "computercraft";
 | 
			
		||||
 | 
			
		||||
    public static String getInstalledVersion() {
 | 
			
		||||
        return getInstance().getInstalledVersion();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a numbered directory in a subfolder of the save directory for a given world, and returns that number.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Use in conjunction with createSaveDirMount() to create a unique place for your peripherals or media items to store files.
 | 
			
		||||
     *
 | 
			
		||||
     * @param server        The server for which the save dir should be created.
 | 
			
		||||
     * @param parentSubPath The folder path within the save directory where the new directory should be created. eg: "computercraft/disk"
 | 
			
		||||
     * @return The numerical value of the name of the new folder, or -1 if the folder could not be created for some reason.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * eg: if createUniqueNumberedSaveDir( world, "computer/disk" ) was called returns 42, then "computer/disk/42" is now
 | 
			
		||||
     * available for writing.
 | 
			
		||||
     * @see #createSaveDirMount(MinecraftServer, String, long)
 | 
			
		||||
     */
 | 
			
		||||
    public static int createUniqueNumberedSaveDir(MinecraftServer server, String parentSubPath) {
 | 
			
		||||
        return getInstance().createUniqueNumberedSaveDir(server, parentSubPath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a file system mount that maps to a subfolder of the save directory for a given world, and returns it.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Use in conjunction with Use {@link IComputerAccess#mount(String, Mount)} or {@link IComputerAccess#mountWritable(String, WritableMount)}
 | 
			
		||||
     * to mount this on a computer's file system.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * If the same folder may be mounted on multiple computers at once (for instance, if you provide a network file share),
 | 
			
		||||
     * the same mount instance should be used for all computers. You should NOT have multiple mount instances for the
 | 
			
		||||
     * same folder.
 | 
			
		||||
     *
 | 
			
		||||
     * @param server   The server which the save dir can be found.
 | 
			
		||||
     * @param subPath  The folder path within the save directory that the mount should map to. eg: "disk/42".
 | 
			
		||||
     *                 Use {@link #createUniqueNumberedSaveDir(MinecraftServer, String)} to create a new numbered folder
 | 
			
		||||
     *                 to use.
 | 
			
		||||
     * @param capacity The amount of data that can be stored in the directory before it fills up, in bytes.
 | 
			
		||||
     * @return The newly created mount.
 | 
			
		||||
     * @see #createUniqueNumberedSaveDir(MinecraftServer, String)
 | 
			
		||||
     * @see IComputerAccess#mount(String, Mount)
 | 
			
		||||
     * @see IComputerAccess#mountWritable(String, WritableMount)
 | 
			
		||||
     * @see Mount
 | 
			
		||||
     * @see WritableMount
 | 
			
		||||
     */
 | 
			
		||||
    public static WritableMount createSaveDirMount(MinecraftServer server, String subPath, long capacity) {
 | 
			
		||||
        return getInstance().createSaveDirMount(server, subPath, capacity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a file system mount to a resource folder, and returns it.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Use in conjunction with {@link IComputerAccess#mount} or {@link IComputerAccess#mountWritable} to mount a
 | 
			
		||||
     * resource folder onto a computer's file system.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * The files in this mount will be a combination of files in all mod jar, and data packs that contain
 | 
			
		||||
     * resources with the same domain and path. For instance, ComputerCraft's resources are stored in
 | 
			
		||||
     * "/data/computercraft/lua/rom". We construct a mount for that with
 | 
			
		||||
     * {@code createResourceMount("computercraft", "lua/rom")}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param server  The current Minecraft server, from which to read resources from.
 | 
			
		||||
     * @param domain  The domain under which to look for resources. eg: "mymod".
 | 
			
		||||
     * @param subPath The subPath under which to look for resources. eg: "lua/myfiles".
 | 
			
		||||
     * @return The mount, or {@code null} if it could be created for some reason.
 | 
			
		||||
     * @see IComputerAccess#mount(String, Mount)
 | 
			
		||||
     * @see IComputerAccess#mountWritable(String, WritableMount)
 | 
			
		||||
     * @see Mount
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public static Mount createResourceMount(MinecraftServer server, String domain, String subPath) {
 | 
			
		||||
        return getInstance().createResourceMount(server, domain, subPath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers a method source for generic peripherals.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source The method source to register.
 | 
			
		||||
     * @see GenericSource
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerGenericSource(GenericSource source) {
 | 
			
		||||
        getInstance().registerGenericSource(source);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers a bundled redstone provider to provide bundled redstone output for blocks.
 | 
			
		||||
     *
 | 
			
		||||
     * @param provider The bundled redstone provider to register.
 | 
			
		||||
     * @see BundledRedstoneProvider
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerBundledRedstoneProvider(BundledRedstoneProvider provider) {
 | 
			
		||||
        getInstance().registerBundledRedstoneProvider(provider);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If there is a Computer or Turtle at a certain position in the world, get it's bundled redstone output.
 | 
			
		||||
     *
 | 
			
		||||
     * @param world The world this block is in.
 | 
			
		||||
     * @param pos   The position this block is at.
 | 
			
		||||
     * @param side  The side to extract the bundled redstone output from.
 | 
			
		||||
     * @return If there is a block capable of emitting bundled redstone at the location, it's signal (0-65535) will be returned.
 | 
			
		||||
     * If there is no block capable of emitting bundled redstone at the location, -1 will be returned.
 | 
			
		||||
     * @see BundledRedstoneProvider
 | 
			
		||||
     */
 | 
			
		||||
    public static int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side) {
 | 
			
		||||
        return getInstance().getBundledRedstoneOutput(world, pos, side);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers a media provider to provide {@link IMedia} implementations for Items.
 | 
			
		||||
     *
 | 
			
		||||
     * @param provider The media provider to register.
 | 
			
		||||
     * @see MediaProvider
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerMediaProvider(MediaProvider provider) {
 | 
			
		||||
        getInstance().registerMediaProvider(provider);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempt to get the game-wide wireless network.
 | 
			
		||||
     *
 | 
			
		||||
     * @param server The current Minecraft server.
 | 
			
		||||
     * @return The global wireless network, or {@code null} if it could not be fetched.
 | 
			
		||||
     */
 | 
			
		||||
    public static PacketNetwork getWirelessNetwork(MinecraftServer server) {
 | 
			
		||||
        return getInstance().getWirelessNetwork(server);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a custom {@link ILuaAPI}, which may be added onto all computers without requiring a peripheral.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
 | 
			
		||||
     * to use peripherals to provide functionality to users.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory The factory for your API subclass.
 | 
			
		||||
     * @see ILuaAPIFactory
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerAPIFactory(ILuaAPIFactory factory) {
 | 
			
		||||
        getInstance().registerAPIFactory(factory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Construct a new wired node for a given wired element.
 | 
			
		||||
     *
 | 
			
		||||
     * @param element The element to construct it for
 | 
			
		||||
     * @return The element's node
 | 
			
		||||
     * @see WiredElement#getNode()
 | 
			
		||||
     */
 | 
			
		||||
    public static WiredNode createWiredNodeForElement(WiredElement element) {
 | 
			
		||||
        return getInstance().createWiredNodeForElement(element);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register a refuel handler for turtles. This may be used to provide alternative fuel sources, such as consuming RF
 | 
			
		||||
     * batteries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param handler The turtle refuel handler.
 | 
			
		||||
     * @see TurtleRefuelHandler#refuel(ITurtleAccess, ItemStack, int, int)
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerRefuelHandler(TurtleRefuelHandler handler) {
 | 
			
		||||
        getInstance().registerRefuelHandler(handler);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ComputerCraftAPIService getInstance() {
 | 
			
		||||
        return ComputerCraftAPIService.get();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.core.registries.Registries;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tags provided by ComputerCraft.
 | 
			
		||||
 */
 | 
			
		||||
public class ComputerCraftTags {
 | 
			
		||||
    public static class Items {
 | 
			
		||||
        public static final TagKey<Item> COMPUTER = make("computer");
 | 
			
		||||
        public static final TagKey<Item> TURTLE = make("turtle");
 | 
			
		||||
        public static final TagKey<Item> WIRED_MODEM = make("wired_modem");
 | 
			
		||||
        public static final TagKey<Item> MONITOR = make("monitor");
 | 
			
		||||
 | 
			
		||||
        private static TagKey<Item> make(String name) {
 | 
			
		||||
            return TagKey.create(Registries.ITEM, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class Blocks {
 | 
			
		||||
        public static final TagKey<Block> COMPUTER = make("computer");
 | 
			
		||||
        public static final TagKey<Block> TURTLE = make("turtle");
 | 
			
		||||
        public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
 | 
			
		||||
        public static final TagKey<Block> MONITOR = make("monitor");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Blocks which can be broken by any turtle tool.
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Block> TURTLE_ALWAYS_BREAKABLE = make("turtle_always_breakable");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Blocks which can be broken by the default shovel tool.
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Block> TURTLE_SHOVEL_BREAKABLE = make("turtle_shovel_harvestable");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Blocks which can be broken with the default sword tool.
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Block> TURTLE_SWORD_BREAKABLE = make("turtle_sword_harvestable");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Blocks which can be broken with the default hoe tool.
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
 | 
			
		||||
 | 
			
		||||
        private static TagKey<Block> make(String name) {
 | 
			
		||||
            return TagKey.create(Registries.BLOCK, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.detail;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An item detail provider for {@link ItemStack}'s whose {@link Item} has a specific type.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type the stack's item must have.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemStack> {
 | 
			
		||||
    private final Class<T> itemType;
 | 
			
		||||
    private final @Nullable String namespace;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new item detail provider. Meta will be inserted into a new sub-map named as per {@code namespace}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param itemType  The type the stack's item must have.
 | 
			
		||||
     * @param namespace The namespace to use for this provider.
 | 
			
		||||
     */
 | 
			
		||||
    public BasicItemDetailProvider(@Nullable String namespace, Class<T> itemType) {
 | 
			
		||||
        Objects.requireNonNull(itemType);
 | 
			
		||||
        this.itemType = itemType;
 | 
			
		||||
        this.namespace = namespace;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new item detail provider. Meta will be inserted directly into the results.
 | 
			
		||||
     *
 | 
			
		||||
     * @param itemType The type the stack's item must have.
 | 
			
		||||
     */
 | 
			
		||||
    public BasicItemDetailProvider(Class<T> itemType) {
 | 
			
		||||
        this(null, itemType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provide additional details for the given {@link Item} and {@link ItemStack}. This method is called by
 | 
			
		||||
     * {@code turtle.getItemDetail()}. New properties should be added to the given {@link Map}, {@code data}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This method is always called on the server thread, so it is safe to interact with the world here, but you should
 | 
			
		||||
     * take care to avoid long blocking operations as this will stall the server and other computers.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data  The full details to be returned for this item stack. New properties should be added to this map.
 | 
			
		||||
     * @param stack The item stack to provide details for.
 | 
			
		||||
     * @param item  The item to provide details for.
 | 
			
		||||
     */
 | 
			
		||||
    public abstract void provideDetails(
 | 
			
		||||
        Map<? super String, Object> data, ItemStack stack, T item
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void provideDetails(Map<? super String, Object> data, ItemStack stack) {
 | 
			
		||||
        var item = stack.getItem();
 | 
			
		||||
        if (!itemType.isInstance(item)) return;
 | 
			
		||||
 | 
			
		||||
        // If `namespace` is specified, insert into a new data map instead of the existing one.
 | 
			
		||||
        Map<? super String, Object> child = namespace == null ? data : new HashMap<>();
 | 
			
		||||
 | 
			
		||||
        provideDetails(child, stack, itemType.cast(item));
 | 
			
		||||
 | 
			
		||||
        if (namespace != null) {
 | 
			
		||||
            data.put(namespace, child);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,7 +10,6 @@ import net.minecraft.world.level.Level;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntity;
 | 
			
		||||
import net.minecraft.world.level.block.state.BlockState;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -22,14 +21,12 @@ import javax.annotation.Nullable;
 | 
			
		||||
 * @param blockEntity The block entity at this position, if it exists.
 | 
			
		||||
 */
 | 
			
		||||
public record BlockReference(
 | 
			
		||||
    @Nonnull Level level,
 | 
			
		||||
    @Nonnull BlockPos pos,
 | 
			
		||||
    @Nonnull BlockState state,
 | 
			
		||||
    Level level,
 | 
			
		||||
    BlockPos pos,
 | 
			
		||||
    BlockState state,
 | 
			
		||||
    @Nullable BlockEntity blockEntity
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
    public BlockReference( Level level, BlockPos pos )
 | 
			
		||||
    {
 | 
			
		||||
        this( level, pos, level.getBlockState( pos ), level.getBlockEntity( pos ) );
 | 
			
		||||
) {
 | 
			
		||||
    public BlockReference(Level level, BlockPos pos) {
 | 
			
		||||
        this(level, pos, level.getBlockState(pos), level.getBlockEntity(pos));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.detail;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provide details about a block, fluid, or item.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * When implementing this interface, be careful to only expose information the player can see through normal gameplay.
 | 
			
		||||
 * Computers shouldn't break progression or mechanics of other mods.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of object that this provider can provide details for.
 | 
			
		||||
 * @see DetailRegistry
 | 
			
		||||
 */
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface DetailProvider<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Provide additional details for the given object. This method is called by functions such as
 | 
			
		||||
     * {@code turtle.getItemDetail()} and {@code turtle.inspect()}. New properties should be added to the given
 | 
			
		||||
     * {@link Map}, {@code data}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This method is always called on the server thread, so it is safe to interact with the world here, but you should
 | 
			
		||||
     * take care to avoid long blocking operations as this will stall the server and other computers.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data   The full details to be returned. New properties should be added to this map.
 | 
			
		||||
     * @param object The object to provide details for.
 | 
			
		||||
     */
 | 
			
		||||
    void provideDetails(Map<? super String, Object> data, T object);
 | 
			
		||||
}
 | 
			
		||||
@@ -14,21 +14,20 @@ import java.util.Map;
 | 
			
		||||
 * <p>
 | 
			
		||||
 * These are used by computer methods such as {@code turtle.getItemDetail()} or {@code turtle.inspect()}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Specific instances of this registry are available from {@link DetailRegistries} and loader-specific versions
 | 
			
		||||
 * Specific instances of this registry are available from {@link VanillaDetailRegistries} and loader-specific versions
 | 
			
		||||
 * also in this package.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of object that this registry provides details for.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.NonExtendable
 | 
			
		||||
public interface DetailRegistry<T>
 | 
			
		||||
{
 | 
			
		||||
public interface DetailRegistry<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers a detail provider.
 | 
			
		||||
     *
 | 
			
		||||
     * @param provider The detail provider to register.
 | 
			
		||||
     * @see IDetailProvider
 | 
			
		||||
     * @see DetailProvider
 | 
			
		||||
     */
 | 
			
		||||
    void addProvider( IDetailProvider<T> provider );
 | 
			
		||||
    void addProvider(DetailProvider<T> provider);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable
 | 
			
		||||
@@ -37,7 +36,7 @@ public interface DetailRegistry<T>
 | 
			
		||||
     * @param object The object to get details for.
 | 
			
		||||
     * @return The basic details.
 | 
			
		||||
     */
 | 
			
		||||
    Map<String, Object> getBasicDetails( T object );
 | 
			
		||||
    Map<String, Object> getBasicDetails(T object);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compute all details about an object, using {@link #getBasicDetails(Object)} and any registered providers.
 | 
			
		||||
@@ -45,5 +44,5 @@ public interface DetailRegistry<T>
 | 
			
		||||
     * @param object The object to get details for.
 | 
			
		||||
     * @return The computed details.
 | 
			
		||||
     */
 | 
			
		||||
    Map<String, Object> getDetails( T object );
 | 
			
		||||
    Map<String, Object> getDetails(T object);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.detail;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.impl.ComputerCraftAPIService;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * {@link DetailRegistry}s for built-in Minecraft types.
 | 
			
		||||
 */
 | 
			
		||||
public class VanillaDetailRegistries {
 | 
			
		||||
    /**
 | 
			
		||||
     * Provides details for {@link ItemStack}s.
 | 
			
		||||
     */
 | 
			
		||||
    public static final DetailRegistry<ItemStack> ITEM_STACK = ComputerCraftAPIService.get().getItemStackDetailRegistry();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provides details for {@link BlockReference}, a reference to a {@link Block} in the world.
 | 
			
		||||
     */
 | 
			
		||||
    public static final DetailRegistry<BlockReference> BLOCK_IN_WORLD = ComputerCraftAPIService.get().getBlockInWorldDetailRegistry();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,86 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.media;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.filesystem.Mount;
 | 
			
		||||
import dan200.computercraft.api.filesystem.WritableMount;
 | 
			
		||||
import net.minecraft.server.MinecraftServer;
 | 
			
		||||
import net.minecraft.server.level.ServerLevel;
 | 
			
		||||
import net.minecraft.sounds.SoundEvent;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents an item that can be placed in a disk drive and used by a Computer.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Implement this interface on your {@link Item} class to allow it to be used in the drive. Alternatively, register
 | 
			
		||||
 * a {@link MediaProvider}.
 | 
			
		||||
 */
 | 
			
		||||
public interface IMedia {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a string representing the label of this item. Will be called via {@code disk.getLabel()} in lua.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The {@link ItemStack} to inspect.
 | 
			
		||||
     * @return The label. ie: "Dan's Programs".
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    String getLabel(ItemStack stack);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set a string representing the label of this item. Will be called vi {@code disk.setLabel()} in lua.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The {@link ItemStack} to modify.
 | 
			
		||||
     * @param label The string to set the label to.
 | 
			
		||||
     * @return true if the label was updated, false if the label may not be modified.
 | 
			
		||||
     */
 | 
			
		||||
    default boolean setLabel(ItemStack stack, @Nullable String label) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If this disk represents an item with audio (like a record), get the readable name of the audio track. ie:
 | 
			
		||||
     * "Jonathan Coulton - Still Alive"
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The {@link ItemStack} to modify.
 | 
			
		||||
     * @return The name, or null if this item does not represent an item with audio.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    default String getAudioTitle(ItemStack stack) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If this disk represents an item with audio (like a record), get the resource name of the audio track to play.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The {@link ItemStack} to modify.
 | 
			
		||||
     * @return The name, or null if this item does not represent an item with audio.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    default SoundEvent getAudio(ItemStack stack) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If this disk represents an item with data (like a floppy disk), get a mount representing it's contents. This will
 | 
			
		||||
     * be mounted onto the filesystem of the computer while the media is in the disk drive.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The {@link ItemStack} to modify.
 | 
			
		||||
     * @param level The world in which the item and disk drive reside.
 | 
			
		||||
     * @return The mount, or null if this item does not represent an item with data. If the mount returned also
 | 
			
		||||
     * implements {@link WritableMount}, it will mounted using mountWritable()
 | 
			
		||||
     * @see Mount
 | 
			
		||||
     * @see WritableMount
 | 
			
		||||
     * @see ComputerCraftAPI#createSaveDirMount(MinecraftServer, String, long)
 | 
			
		||||
     * @see ComputerCraftAPI#createResourceMount(MinecraftServer, String, String)
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    default Mount createDataMount(ItemStack stack, ServerLevel level) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.media;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This interface is used to provide {@link IMedia} implementations for {@link ItemStack}.
 | 
			
		||||
 *
 | 
			
		||||
 * @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
 | 
			
		||||
 */
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface MediaProvider {
 | 
			
		||||
    /**
 | 
			
		||||
     * Produce an IMedia implementation from an ItemStack.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The stack from which to extract the media information.
 | 
			
		||||
     * @return An {@link IMedia} implementation, or {@code null} if the item is not something you wish to handle
 | 
			
		||||
     * @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    IMedia getMedia(ItemStack stack);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.network;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a packet which may be sent across a {@link PacketNetwork}.
 | 
			
		||||
 *
 | 
			
		||||
 * @param channel      The channel to send the packet along. Receiving devices should only process packets from on
 | 
			
		||||
 *                     channels they are listening to.
 | 
			
		||||
 * @param replyChannel The channel to reply on.
 | 
			
		||||
 * @param payload      The contents of this packet. This should be a "valid" Lua object, safe for queuing as an
 | 
			
		||||
 *                     event or returning from a peripheral call.
 | 
			
		||||
 * @param sender       The object which sent this packet.
 | 
			
		||||
 * @see PacketSender
 | 
			
		||||
 * @see PacketNetwork#transmitSameDimension(Packet, double)
 | 
			
		||||
 * @see PacketNetwork#transmitInterdimensional(Packet)
 | 
			
		||||
 * @see PacketReceiver#receiveDifferentDimension(Packet)
 | 
			
		||||
 * @see PacketReceiver#receiveSameDimension(Packet, double)
 | 
			
		||||
 */
 | 
			
		||||
public record Packet(
 | 
			
		||||
    int channel,
 | 
			
		||||
    int replyChannel,
 | 
			
		||||
    Object payload,
 | 
			
		||||
    PacketSender sender
 | 
			
		||||
) {
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,57 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.network;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A packet network represents a collection of devices which can send and receive packets.
 | 
			
		||||
 *
 | 
			
		||||
 * @see Packet
 | 
			
		||||
 * @see PacketReceiver
 | 
			
		||||
 */
 | 
			
		||||
public interface PacketNetwork {
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a receiver to the network.
 | 
			
		||||
     *
 | 
			
		||||
     * @param receiver The receiver to register to the network.
 | 
			
		||||
     */
 | 
			
		||||
    void addReceiver(PacketReceiver receiver);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove a receiver from the network.
 | 
			
		||||
     *
 | 
			
		||||
     * @param receiver The device to remove from the network.
 | 
			
		||||
     */
 | 
			
		||||
    void removeReceiver(PacketReceiver receiver);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine whether this network is wireless.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether this network is wireless.
 | 
			
		||||
     */
 | 
			
		||||
    boolean isWireless();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Submit a packet for transmitting across the network. This will route the packet through the network, sending it
 | 
			
		||||
     * to all receivers within range (or any interdimensional ones).
 | 
			
		||||
     *
 | 
			
		||||
     * @param packet The packet to send.
 | 
			
		||||
     * @param range  The maximum distance this packet will be sent.
 | 
			
		||||
     * @see #transmitInterdimensional(Packet)
 | 
			
		||||
     * @see PacketReceiver#receiveSameDimension(Packet, double)
 | 
			
		||||
     */
 | 
			
		||||
    void transmitSameDimension(Packet packet, double range);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Submit a packet for transmitting across the network. This will route the packet through the network, sending it
 | 
			
		||||
     * to all receivers across all dimensions.
 | 
			
		||||
     *
 | 
			
		||||
     * @param packet The packet to send.
 | 
			
		||||
     * @see #transmitSameDimension(Packet, double)
 | 
			
		||||
     * @see PacketReceiver#receiveDifferentDimension(Packet)
 | 
			
		||||
     */
 | 
			
		||||
    void transmitInterdimensional(Packet packet);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,80 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.network;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.minecraft.world.phys.Vec3;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An object on an {@link PacketNetwork}, capable of receiving packets.
 | 
			
		||||
 */
 | 
			
		||||
public interface PacketReceiver {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the world in which this packet receiver exists.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The receivers's world.
 | 
			
		||||
     */
 | 
			
		||||
    Level getLevel();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the position in the world at which this receiver exists.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The receiver's position.
 | 
			
		||||
     */
 | 
			
		||||
    Vec3 getPosition();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the maximum distance this receiver can send and receive messages.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * When determining whether a receiver can receive a message, the largest distance of the packet and receiver is
 | 
			
		||||
     * used - ensuring it is within range. If the packet or receiver is inter-dimensional, then the packet will always
 | 
			
		||||
     * be received.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The maximum distance this device can send and receive messages.
 | 
			
		||||
     * @see #isInterdimensional()
 | 
			
		||||
     * @see #receiveSameDimension(Packet packet, double)
 | 
			
		||||
     * @see PacketNetwork#transmitInterdimensional(Packet)
 | 
			
		||||
     */
 | 
			
		||||
    double getRange();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine whether this receiver can receive packets from other dimensions.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * A device will receive an inter-dimensional packet if either it or the sending device is inter-dimensional.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether this receiver receives packets from other dimensions.
 | 
			
		||||
     * @see #getRange()
 | 
			
		||||
     * @see #receiveDifferentDimension(Packet)
 | 
			
		||||
     * @see PacketNetwork#transmitInterdimensional(Packet)
 | 
			
		||||
     */
 | 
			
		||||
    boolean isInterdimensional();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Receive a network packet from the same dimension.
 | 
			
		||||
     *
 | 
			
		||||
     * @param packet   The packet to receive. Generally you should check that you are listening on the given channel and,
 | 
			
		||||
     *                 if so, queue the appropriate modem event.
 | 
			
		||||
     * @param distance The distance this packet has travelled from the source.
 | 
			
		||||
     * @see Packet
 | 
			
		||||
     * @see #getRange()
 | 
			
		||||
     * @see PacketNetwork#transmitSameDimension(Packet, double)
 | 
			
		||||
     * @see PacketNetwork#transmitInterdimensional(Packet)
 | 
			
		||||
     */
 | 
			
		||||
    void receiveSameDimension(Packet packet, double distance);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Receive a network packet from a different dimension.
 | 
			
		||||
     *
 | 
			
		||||
     * @param packet The packet to receive. Generally you should check that you are listening on the given channel and,
 | 
			
		||||
     *               if so, queue the appropriate modem event.
 | 
			
		||||
     * @see Packet
 | 
			
		||||
     * @see PacketNetwork#transmitInterdimensional(Packet)
 | 
			
		||||
     * @see PacketNetwork#transmitSameDimension(Packet, double)
 | 
			
		||||
     * @see #isInterdimensional()
 | 
			
		||||
     */
 | 
			
		||||
    void receiveDifferentDimension(Packet packet);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.network;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.minecraft.world.phys.Vec3;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An object on a {@link PacketNetwork}, capable of sending packets.
 | 
			
		||||
 */
 | 
			
		||||
public interface PacketSender {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the world in which this packet sender exists.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The sender's world.
 | 
			
		||||
     */
 | 
			
		||||
    Level getLevel();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the position in the world at which this sender exists.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The sender's position.
 | 
			
		||||
     */
 | 
			
		||||
    Vec3 getPosition();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get some sort of identification string for this sender. This does not strictly need to be unique, but you
 | 
			
		||||
     * should be able to extract some identifiable information from it.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This device's id.
 | 
			
		||||
     */
 | 
			
		||||
    String getSenderID();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.network.wired;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An object which may be part of a wired network.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(WiredElement)}. This acts
 | 
			
		||||
 * as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
 | 
			
		||||
 * for its lifespan.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Elements are generally tied to a block or tile entity in world. In such as case, one should provide the
 | 
			
		||||
 * {@link WiredElement} capability for the appropriate sides.
 | 
			
		||||
 */
 | 
			
		||||
public interface WiredElement extends WiredSender {
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when objects on the network change. This may occur when network nodes are added or removed, or when
 | 
			
		||||
     * peripherals change.
 | 
			
		||||
     *
 | 
			
		||||
     * @param change The change which occurred.
 | 
			
		||||
     * @see WiredNetworkChange
 | 
			
		||||
     */
 | 
			
		||||
    default void networkChanged(WiredNetworkChange change) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,83 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.network.wired;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A wired network is composed of one of more {@link WiredNode}s, a set of connections between them, and a series
 | 
			
		||||
 * of peripherals.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
 | 
			
		||||
 * there is some path between two nodes then they must be on the same network. {@link WiredNetwork} will automatically
 | 
			
		||||
 * handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
 | 
			
		||||
 * change.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
 | 
			
		||||
 * it is generally preferred to use the methods provided by {@link WiredNode}.
 | 
			
		||||
 *
 | 
			
		||||
 * @see WiredNode#getNetwork()
 | 
			
		||||
 */
 | 
			
		||||
public interface WiredNetwork {
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a connection between two nodes.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @param left  The first node to connect
 | 
			
		||||
     * @param right The second node to connect
 | 
			
		||||
     * @return {@code true} if a connection was created or {@code false} if the connection already exists.
 | 
			
		||||
     * @throws IllegalStateException    If neither node is on the network.
 | 
			
		||||
     * @throws IllegalArgumentException If {@code left} and {@code right} are equal.
 | 
			
		||||
     * @see WiredNode#connectTo(WiredNode)
 | 
			
		||||
     * @see WiredNetwork#connect(WiredNode, WiredNode)
 | 
			
		||||
     */
 | 
			
		||||
    boolean connect(WiredNode left, WiredNode right);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Destroy a connection between this node and another.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @param left  The first node in the connection.
 | 
			
		||||
     * @param right The second node in the connection.
 | 
			
		||||
     * @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
 | 
			
		||||
     * @throws IllegalArgumentException If either node is not on the network.
 | 
			
		||||
     * @throws IllegalArgumentException If {@code left} and {@code right} are equal.
 | 
			
		||||
     * @see WiredNode#disconnectFrom(WiredNode)
 | 
			
		||||
     * @see WiredNetwork#connect(WiredNode, WiredNode)
 | 
			
		||||
     */
 | 
			
		||||
    boolean disconnect(WiredNode left, WiredNode right);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sever all connections this node has, removing it from this network.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread. You should only call this on nodes
 | 
			
		||||
     * that your network element owns.
 | 
			
		||||
     *
 | 
			
		||||
     * @param node The node to remove
 | 
			
		||||
     * @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
 | 
			
		||||
     * only element.
 | 
			
		||||
     * @throws IllegalArgumentException If the node is not in the network.
 | 
			
		||||
     * @see WiredNode#remove()
 | 
			
		||||
     */
 | 
			
		||||
    boolean remove(WiredNode node);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the peripherals a node provides.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread. You should only call this on nodes
 | 
			
		||||
     * that your network element owns.
 | 
			
		||||
     *
 | 
			
		||||
     * @param node        The node to attach peripherals for.
 | 
			
		||||
     * @param peripherals The new peripherals for this node.
 | 
			
		||||
     * @throws IllegalArgumentException If the node is not in the network.
 | 
			
		||||
     * @see WiredNode#updatePeripherals(Map)
 | 
			
		||||
     */
 | 
			
		||||
    void updatePeripherals(WiredNode node, Map<String, IPeripheral> peripherals);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.network.wired;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a change to the objects on a wired network.
 | 
			
		||||
 *
 | 
			
		||||
 * @see WiredElement#networkChanged(WiredNetworkChange)
 | 
			
		||||
 */
 | 
			
		||||
public interface WiredNetworkChange {
 | 
			
		||||
    /**
 | 
			
		||||
     * A set of peripherals which have been removed. Note that there may be entries with the same name
 | 
			
		||||
     * in the added and removed set, but with a different peripheral.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The set of removed peripherals.
 | 
			
		||||
     */
 | 
			
		||||
    Map<String, IPeripheral> peripheralsRemoved();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A set of peripherals which have been added. Note that there may be entries with the same name
 | 
			
		||||
     * in the added and removed set, but with a different peripheral.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The set of added peripherals.
 | 
			
		||||
     */
 | 
			
		||||
    Map<String, IPeripheral> peripheralsAdded();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,100 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.network.wired;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.network.PacketNetwork;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wired nodes act as a layer between {@link WiredElement}s and {@link WiredNetwork}s.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
 | 
			
		||||
 * methods may be safely used on any thread.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * When sending a packet, the system will attempt to find the shortest path between the two nodes based on their
 | 
			
		||||
 * element's position. Note that packet senders and receivers can have different locations from their associated
 | 
			
		||||
 * element: the distance between the two will be added to the total packet's distance.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
 | 
			
		||||
 * be used on the main server thread.
 | 
			
		||||
 */
 | 
			
		||||
public interface WiredNode extends PacketNetwork {
 | 
			
		||||
    /**
 | 
			
		||||
     * The associated element for this network node.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This node's element.
 | 
			
		||||
     */
 | 
			
		||||
    WiredElement getElement();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The network this node is currently connected to. Note that this may change
 | 
			
		||||
     * after any network operation, so it should not be cached.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This node's network.
 | 
			
		||||
     */
 | 
			
		||||
    WiredNetwork getNetwork();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a connection from this node to another.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @param node The other node to connect to.
 | 
			
		||||
     * @return {@code true} if a connection was created or {@code false} if the connection already exists.
 | 
			
		||||
     * @see WiredNetwork#connect(WiredNode, WiredNode)
 | 
			
		||||
     * @see WiredNode#disconnectFrom(WiredNode)
 | 
			
		||||
     */
 | 
			
		||||
    default boolean connectTo(WiredNode node) {
 | 
			
		||||
        return getNetwork().connect(this, node);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Destroy a connection between this node and another.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @param node The other node to disconnect from.
 | 
			
		||||
     * @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
 | 
			
		||||
     * @throws IllegalArgumentException If {@code node} is not on the same network.
 | 
			
		||||
     * @see WiredNetwork#disconnect(WiredNode, WiredNode)
 | 
			
		||||
     * @see WiredNode#connectTo(WiredNode)
 | 
			
		||||
     */
 | 
			
		||||
    default boolean disconnectFrom(WiredNode node) {
 | 
			
		||||
        return getNetwork().disconnect(this, node);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sever all connections this node has, removing it from this network.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread. You should only call this on nodes
 | 
			
		||||
     * that your network element owns.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
 | 
			
		||||
     * only element.
 | 
			
		||||
     * @throws IllegalArgumentException If the node is not in the network.
 | 
			
		||||
     * @see WiredNetwork#remove(WiredNode)
 | 
			
		||||
     */
 | 
			
		||||
    default boolean remove() {
 | 
			
		||||
        return getNetwork().remove(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Mark this node's peripherals as having changed.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should only be used on the server thread. You should only call this on nodes
 | 
			
		||||
     * that your network element owns.
 | 
			
		||||
     *
 | 
			
		||||
     * @param peripherals The new peripherals for this node.
 | 
			
		||||
     * @see WiredNetwork#updatePeripherals(WiredNode, Map)
 | 
			
		||||
     */
 | 
			
		||||
    default void updatePeripherals(Map<String, IPeripheral> peripherals) {
 | 
			
		||||
        getNetwork().updatePeripherals(this, peripherals);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.network.wired;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.network.PacketSender;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An object on a {@link WiredNetwork} capable of sending packets.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Unlike a regular {@link PacketSender}, this must be associated with the node you are attempting to
 | 
			
		||||
 * to send the packet from.
 | 
			
		||||
 */
 | 
			
		||||
public interface WiredSender extends PacketSender {
 | 
			
		||||
    /**
 | 
			
		||||
     * The node in the network representing this object.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should be used as a proxy for the main network. One should send packets
 | 
			
		||||
     * and register receivers through this object.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The node for this element.
 | 
			
		||||
     */
 | 
			
		||||
    WiredNode getNode();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,47 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A base class for {@link IPocketUpgrade}s.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * One does not have to use this, but it does provide a convenient template.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AbstractPocketUpgrade implements IPocketUpgrade {
 | 
			
		||||
    private final ResourceLocation id;
 | 
			
		||||
    private final String adjective;
 | 
			
		||||
    private final ItemStack stack;
 | 
			
		||||
 | 
			
		||||
    protected AbstractPocketUpgrade(ResourceLocation id, String adjective, ItemStack stack) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        this.adjective = adjective;
 | 
			
		||||
        this.stack = stack;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected AbstractPocketUpgrade(ResourceLocation id, ItemStack stack) {
 | 
			
		||||
        this(id, UpgradeBase.getDefaultAdjective(id), stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final ResourceLocation getUpgradeID() {
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final String getUnlocalisedAdjective() {
 | 
			
		||||
        return adjective;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final ItemStack getCraftingItem() {
 | 
			
		||||
        return stack;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,15 +10,13 @@ import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Wrapper class for pocket computers.
 | 
			
		||||
 */
 | 
			
		||||
public interface IPocketAccess
 | 
			
		||||
{
 | 
			
		||||
public interface IPocketAccess {
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the entity holding this item.
 | 
			
		||||
     * <p>
 | 
			
		||||
@@ -45,7 +43,7 @@ public interface IPocketAccess
 | 
			
		||||
     *               {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
 | 
			
		||||
     * @see #getColour()
 | 
			
		||||
     */
 | 
			
		||||
    void setColour( int colour );
 | 
			
		||||
    void setColour(int colour);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the colour of this pocket computer's light as a RGB number.
 | 
			
		||||
@@ -63,7 +61,7 @@ public interface IPocketAccess
 | 
			
		||||
     *               {@code 0x000000} and {@code 0xFFFFFF} or -1 to reset to the default colour.
 | 
			
		||||
     * @see #getLight()
 | 
			
		||||
     */
 | 
			
		||||
    void setLight( int colour );
 | 
			
		||||
    void setLight(int colour);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the upgrade-specific NBT.
 | 
			
		||||
@@ -73,7 +71,6 @@ public interface IPocketAccess
 | 
			
		||||
     * @return The upgrade's NBT.
 | 
			
		||||
     * @see #updateUpgradeNBTData()
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    CompoundTag getUpgradeNBTData();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -93,6 +90,5 @@ public interface IPocketAccess
 | 
			
		||||
     *
 | 
			
		||||
     * @return A collection of all upgrade names.
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    Map<ResourceLocation, IPeripheral> getUpgrades();
 | 
			
		||||
}
 | 
			
		||||
@@ -6,10 +6,9 @@
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.IUpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -24,8 +23,7 @@ import javax.annotation.Nullable;
 | 
			
		||||
 *
 | 
			
		||||
 * @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
 | 
			
		||||
 */
 | 
			
		||||
public interface IPocketUpgrade extends IUpgradeBase
 | 
			
		||||
{
 | 
			
		||||
public interface IPocketUpgrade extends UpgradeBase {
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a peripheral for the pocket computer.
 | 
			
		||||
     * <p>
 | 
			
		||||
@@ -38,7 +36,7 @@ public interface IPocketUpgrade extends IUpgradeBase
 | 
			
		||||
     * @see #update(IPocketAccess, IPeripheral)
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    IPeripheral createPeripheral( @Nonnull IPocketAccess access );
 | 
			
		||||
    IPeripheral createPeripheral(IPocketAccess access);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the pocket computer item stack updates.
 | 
			
		||||
@@ -47,8 +45,7 @@ public interface IPocketUpgrade extends IUpgradeBase
 | 
			
		||||
     * @param peripheral The peripheral for this upgrade.
 | 
			
		||||
     * @see #createPeripheral(IPocketAccess)
 | 
			
		||||
     */
 | 
			
		||||
    default void update( @Nonnull IPocketAccess access, @Nullable IPeripheral peripheral )
 | 
			
		||||
    {
 | 
			
		||||
    default void update(IPocketAccess access, @Nullable IPeripheral peripheral) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -62,8 +59,7 @@ public interface IPocketUpgrade extends IUpgradeBase
 | 
			
		||||
     * access the GUI.
 | 
			
		||||
     * @see #createPeripheral(IPocketAccess)
 | 
			
		||||
     */
 | 
			
		||||
    default boolean onRightClick( @Nonnull Level world, @Nonnull IPocketAccess access, @Nullable IPeripheral peripheral )
 | 
			
		||||
    {
 | 
			
		||||
    default boolean onRightClick(Level world, IPocketAccess access, @Nullable IPeripheral peripheral) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
 | 
			
		||||
import net.minecraft.data.DataGenerator;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A data provider to generate pocket computer upgrades.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
 | 
			
		||||
 * {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
 | 
			
		||||
 * generate them.
 | 
			
		||||
 *
 | 
			
		||||
 * @see PocketUpgradeSerialiser
 | 
			
		||||
 */
 | 
			
		||||
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {
 | 
			
		||||
    public PocketUpgradeDataProvider(PackOutput output) {
 | 
			
		||||
        super(output, "Pocket Computer Upgrades", "computercraft/pocket_upgrades", PocketUpgradeSerialiser.REGISTRY_ID);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,76 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Reads a {@link IPocketUpgrade} from disk and reads/writes it to a network packet.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This follows the same format as {@link dan200.computercraft.api.turtle.TurtleUpgradeSerialiser} - consult the
 | 
			
		||||
 * documentation there for more information.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of pocket computer upgrade this is responsible for serialising.
 | 
			
		||||
 * @see IPocketUpgrade
 | 
			
		||||
 * @see PocketUpgradeDataProvider
 | 
			
		||||
 */
 | 
			
		||||
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID for the associated registry.
 | 
			
		||||
     */
 | 
			
		||||
    ResourceKey<Registry<PocketUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
 | 
			
		||||
     * but for upgrades.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
 | 
			
		||||
        final class Impl extends SimpleSerialiser<T> implements PocketUpgradeSerialiser<T> {
 | 
			
		||||
            private Impl(Function<ResourceLocation, T> constructor) {
 | 
			
		||||
                super(constructor);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Impl(factory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
 | 
			
		||||
     *                {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade.
 | 
			
		||||
     * @see #simple(Function)  For upgrades whose crafting stack should not vary.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends IPocketUpgrade> PocketUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
        final class Impl extends SerialiserWithCraftingItem<T> implements PocketUpgradeSerialiser<T> {
 | 
			
		||||
            private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
                super(factory);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Impl(factory);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.redstone;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This interface is used to provide bundled redstone output for blocks.
 | 
			
		||||
 *
 | 
			
		||||
 * @see ComputerCraftAPI#registerBundledRedstoneProvider(BundledRedstoneProvider)
 | 
			
		||||
 */
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface BundledRedstoneProvider {
 | 
			
		||||
    /**
 | 
			
		||||
     * Produce an bundled redstone output from a block location.
 | 
			
		||||
     *
 | 
			
		||||
     * @param world The world this block is in.
 | 
			
		||||
     * @param pos   The position this block is at.
 | 
			
		||||
     * @param side  The side to extract the bundled redstone output from.
 | 
			
		||||
     * @return A number in the range 0-65535 to indicate this block is providing output, or -1 if you do not wish to
 | 
			
		||||
     * handle this block.
 | 
			
		||||
     * @see ComputerCraftAPI#registerBundledRedstoneProvider(BundledRedstoneProvider)
 | 
			
		||||
     */
 | 
			
		||||
    int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,54 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A base class for {@link ITurtleUpgrade}s.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * One does not have to use this, but it does provide a convenient template.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade {
 | 
			
		||||
    private final ResourceLocation id;
 | 
			
		||||
    private final TurtleUpgradeType type;
 | 
			
		||||
    private final String adjective;
 | 
			
		||||
    private final ItemStack stack;
 | 
			
		||||
 | 
			
		||||
    protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, String adjective, ItemStack stack) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        this.type = type;
 | 
			
		||||
        this.adjective = adjective;
 | 
			
		||||
        this.stack = stack;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected AbstractTurtleUpgrade(ResourceLocation id, TurtleUpgradeType type, ItemStack stack) {
 | 
			
		||||
        this(id, type, UpgradeBase.getDefaultAdjective(id), stack);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final ResourceLocation getUpgradeID() {
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final String getUnlocalisedAdjective() {
 | 
			
		||||
        return adjective;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final TurtleUpgradeType getType() {
 | 
			
		||||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final ItemStack getCraftingItem() {
 | 
			
		||||
        return stack;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -15,9 +15,7 @@ import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.world.Container;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import net.minecraft.world.phys.Vec3;
 | 
			
		||||
import net.minecraftforge.items.IItemHandlerModifiable;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -26,14 +24,12 @@ import javax.annotation.Nullable;
 | 
			
		||||
 * This should not be implemented by your classes. Do not interact with turtles except via this interface and
 | 
			
		||||
 * {@link ITurtleUpgrade}.
 | 
			
		||||
 */
 | 
			
		||||
public interface ITurtleAccess
 | 
			
		||||
{
 | 
			
		||||
public interface ITurtleAccess {
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the world in which the turtle resides.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the world in which the turtle resides.
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    Level getLevel();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -41,9 +37,22 @@ public interface ITurtleAccess
 | 
			
		||||
     *
 | 
			
		||||
     * @return a vector containing the integer co-ordinates at which the turtle resides.
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    BlockPos getPosition();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if this turtle has been removed.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * It's possible for a turtle to be removed while a {@link TurtleCommand} is executed, for instance if interacting
 | 
			
		||||
     * with a block causes the turtle to be blown up. It's recommended you check the turtle is still present before
 | 
			
		||||
     * trying to interact with it again.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * If a turtle has been removed {@link #getLevel()} and {@link #getPosition()} will continue to function as before.
 | 
			
		||||
     * All other methods will fail.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether this turtle has been removed.
 | 
			
		||||
     */
 | 
			
		||||
    boolean isRemoved();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempt to move this turtle to a new position.
 | 
			
		||||
     * <p>
 | 
			
		||||
@@ -56,7 +65,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * was cancelled.
 | 
			
		||||
     * @throws UnsupportedOperationException When attempting to teleport on the client side.
 | 
			
		||||
     */
 | 
			
		||||
    boolean teleportTo( @Nonnull Level world, @Nonnull BlockPos pos );
 | 
			
		||||
    boolean teleportTo(Level world, BlockPos pos);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a vector containing the floating point co-ordinates at which the turtle is rendered.
 | 
			
		||||
@@ -66,8 +75,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @return A vector containing the floating point co-ordinates at which the turtle resides.
 | 
			
		||||
     * @see #getVisualYaw(float)
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    Vec3 getVisualPosition( float f );
 | 
			
		||||
    Vec3 getVisualPosition(float f);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the yaw the turtle is facing when it is rendered.
 | 
			
		||||
@@ -76,7 +84,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @return The yaw the turtle is facing.
 | 
			
		||||
     * @see #getVisualPosition(float)
 | 
			
		||||
     */
 | 
			
		||||
    float getVisualYaw( float f );
 | 
			
		||||
    float getVisualYaw(float f);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the world direction the turtle is currently facing.
 | 
			
		||||
@@ -84,7 +92,6 @@ public interface ITurtleAccess
 | 
			
		||||
     * @return The world direction the turtle is currently facing.
 | 
			
		||||
     * @see #setDirection(Direction)
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    Direction getDirection();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -94,7 +101,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @param dir The new direction to set. This should be on either the x or z axis (so north, south, east or west).
 | 
			
		||||
     * @see #getDirection()
 | 
			
		||||
     */
 | 
			
		||||
    void setDirection( @Nonnull Direction dir );
 | 
			
		||||
    void setDirection(Direction dir);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the currently selected slot in the turtle's inventory.
 | 
			
		||||
@@ -114,7 +121,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @see #getInventory()
 | 
			
		||||
     * @see #getSelectedSlot()
 | 
			
		||||
     */
 | 
			
		||||
    void setSelectedSlot( int slot );
 | 
			
		||||
    void setSelectedSlot(int slot);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the colour of the turtle to a RGB number.
 | 
			
		||||
@@ -123,7 +130,7 @@ public interface ITurtleAccess
 | 
			
		||||
     *               and {@code 0xFFFFFF} or -1 to reset to the default colour.
 | 
			
		||||
     * @see #getColour()
 | 
			
		||||
     */
 | 
			
		||||
    void setColour( int colour );
 | 
			
		||||
    void setColour(int colour);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the colour of this turtle as a RGB number.
 | 
			
		||||
@@ -148,24 +155,9 @@ public interface ITurtleAccess
 | 
			
		||||
     * Note: this inventory should only be accessed and modified on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This turtle's inventory
 | 
			
		||||
     * @see #getItemHandler()
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    Container getInventory();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Note: this inventory should only be accessed and modified on the server thread.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This turtle's inventory
 | 
			
		||||
     * @see #getInventory()
 | 
			
		||||
     * @see IItemHandlerModifiable
 | 
			
		||||
     * @see net.minecraftforge.items.CapabilityItemHandler#ITEM_HANDLER_CAPABILITY
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    IItemHandlerModifiable getItemHandler();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine whether this turtle will require fuel when performing actions.
 | 
			
		||||
     *
 | 
			
		||||
@@ -194,7 +186,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @see #addFuel(int)
 | 
			
		||||
     * @see #consumeFuel(int)
 | 
			
		||||
     */
 | 
			
		||||
    void setFuelLevel( int fuel );
 | 
			
		||||
    void setFuelLevel(int fuel);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the maximum amount of fuel a turtle can hold.
 | 
			
		||||
@@ -211,7 +203,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * greater than the current fuel level of the turtle. No fuel will be consumed if {@code false} is returned.
 | 
			
		||||
     * @throws UnsupportedOperationException When attempting to consume fuel on the client side.
 | 
			
		||||
     */
 | 
			
		||||
    boolean consumeFuel( int fuel );
 | 
			
		||||
    boolean consumeFuel(int fuel);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Increase the turtle's fuel level by the given amount.
 | 
			
		||||
@@ -219,7 +211,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @param fuel The amount to refuel with.
 | 
			
		||||
     * @throws UnsupportedOperationException When attempting to refuel on the client side.
 | 
			
		||||
     */
 | 
			
		||||
    void addFuel( int fuel );
 | 
			
		||||
    void addFuel(int fuel);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a custom command to the turtles command queue. Unlike peripheral methods, these custom commands will be executed
 | 
			
		||||
@@ -232,11 +224,10 @@ public interface ITurtleAccess
 | 
			
		||||
     * @return The objects the command returned when executed. you should probably return these to the player
 | 
			
		||||
     * unchanged if called from a peripheral method.
 | 
			
		||||
     * @throws UnsupportedOperationException When attempting to execute a command on the client side.
 | 
			
		||||
     * @see ITurtleCommand
 | 
			
		||||
     * @see TurtleCommand
 | 
			
		||||
     * @see MethodResult#pullEvent(String, ILuaCallback)
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    MethodResult executeCommand( @Nonnull ITurtleCommand command );
 | 
			
		||||
    MethodResult executeCommand(TurtleCommand command);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Start playing a specific animation. This will prevent other turtle commands from executing until
 | 
			
		||||
@@ -246,7 +237,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @throws UnsupportedOperationException When attempting to execute play an animation on the client side.
 | 
			
		||||
     * @see TurtleAnimation
 | 
			
		||||
     */
 | 
			
		||||
    void playAnimation( @Nonnull TurtleAnimation animation );
 | 
			
		||||
    void playAnimation(TurtleAnimation animation);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the turtle on the specified side of the turtle, if there is one.
 | 
			
		||||
@@ -256,7 +247,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @see #setUpgrade(TurtleSide, ITurtleUpgrade)
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    ITurtleUpgrade getUpgrade( @Nonnull TurtleSide side );
 | 
			
		||||
    ITurtleUpgrade getUpgrade(TurtleSide side);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
 | 
			
		||||
@@ -265,7 +256,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @param upgrade The upgrade to set, may be {@code null} to clear.
 | 
			
		||||
     * @see #getUpgrade(TurtleSide)
 | 
			
		||||
     */
 | 
			
		||||
    void setUpgrade( @Nonnull TurtleSide side, @Nullable ITurtleUpgrade upgrade );
 | 
			
		||||
    void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
 | 
			
		||||
@@ -274,7 +265,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @return The peripheral created by the upgrade on the specified side of the turtle, {@code null} if none exists.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    IPeripheral getPeripheral( @Nonnull TurtleSide side );
 | 
			
		||||
    IPeripheral getPeripheral(TurtleSide side);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get an upgrade-specific NBT compound, which can be used to store arbitrary data.
 | 
			
		||||
@@ -286,8 +277,7 @@ public interface ITurtleAccess
 | 
			
		||||
     * @return The upgrade-specific data.
 | 
			
		||||
     * @see #updateUpgradeNBTData(TurtleSide)
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    CompoundTag getUpgradeNBTData( @Nullable TurtleSide side );
 | 
			
		||||
    CompoundTag getUpgradeNBTData(TurtleSide side);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Mark the upgrade-specific data as dirty on a specific side. This is required for the data to be synced to the
 | 
			
		||||
@@ -296,5 +286,5 @@ public interface ITurtleAccess
 | 
			
		||||
     * @param side The side to mark dirty.
 | 
			
		||||
     * @see #updateUpgradeNBTData(TurtleSide)
 | 
			
		||||
     */
 | 
			
		||||
    void updateUpgradeNBTData( @Nonnull TurtleSide side );
 | 
			
		||||
    void updateUpgradeNBTData(TurtleSide side);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,83 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The primary interface for defining an update for Turtles. A turtle update can either be a new tool, or a new
 | 
			
		||||
 * peripheral.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
 | 
			
		||||
 * {@link TurtleUpgradeSerialiser} instance, which are then registered in a Forge registry.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
 | 
			
		||||
 * the upgrade registered internally. See the documentation in {@link TurtleUpgradeSerialiser} for details on this process
 | 
			
		||||
 * and where files should be located.
 | 
			
		||||
 *
 | 
			
		||||
 * @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
 | 
			
		||||
 */
 | 
			
		||||
public interface ITurtleUpgrade extends UpgradeBase {
 | 
			
		||||
    /**
 | 
			
		||||
     * Return whether this turtle adds a tool or a peripheral to the turtle.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The type of upgrade this is.
 | 
			
		||||
     * @see TurtleUpgradeType for the differences between them.
 | 
			
		||||
     */
 | 
			
		||||
    TurtleUpgradeType getType();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * The peripheral created will be stored for the lifetime of the upgrade and will be passed as an argument to
 | 
			
		||||
     * {@link #update(ITurtleAccess, TurtleSide)}. It will be attached, detached and have methods called in the same
 | 
			
		||||
     * manner as a Computer peripheral.
 | 
			
		||||
     *
 | 
			
		||||
     * @param turtle Access to the turtle that the peripheral is being created for.
 | 
			
		||||
     * @param side   Which side of the turtle (left or right) that the upgrade resides on.
 | 
			
		||||
     * @return The newly created peripheral. You may return {@code null} if this upgrade is a Tool
 | 
			
		||||
     * and this method is not expected to be called.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    default IPeripheral createPeripheral(ITurtleAccess turtle, TurtleSide side) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
 | 
			
		||||
     * by the turtle, and the tool is required to do some work.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Conforming implementations should fire loader-specific events when using the tool, for instance Forge's
 | 
			
		||||
     * {@code AttackEntityEvent}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param turtle    Access to the turtle that the tool resides on.
 | 
			
		||||
     * @param side      Which side of the turtle (left or right) the tool resides on.
 | 
			
		||||
     * @param verb      Which action (dig or attack) the turtle is being called on to perform.
 | 
			
		||||
     * @param direction Which world direction the action should be performed in, relative to the turtles
 | 
			
		||||
     *                  position. This will either be up, down, or the direction the turtle is facing, depending on
 | 
			
		||||
     *                  whether dig, digUp or digDown was called.
 | 
			
		||||
     * @return Whether the turtle was able to perform the action, and hence whether the {@code turtle.dig()}
 | 
			
		||||
     * or {@code turtle.attack()} lua method should return true. If true is returned, the tool will perform
 | 
			
		||||
     * a swinging animation. You may return {@code null} if this turtle is a Peripheral  and this method is not expected
 | 
			
		||||
     * to be called.
 | 
			
		||||
     */
 | 
			
		||||
    default TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) {
 | 
			
		||||
        return TurtleCommandResult.failure();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called once per tick for each turtle which has the upgrade equipped.
 | 
			
		||||
     *
 | 
			
		||||
     * @param turtle Access to the turtle that the upgrade resides on.
 | 
			
		||||
     * @param side   Which side of the turtle (left or right) the upgrade resides on.
 | 
			
		||||
     */
 | 
			
		||||
    default void update(ITurtleAccess turtle, TurtleSide side) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,8 +12,7 @@ package dan200.computercraft.api.turtle;
 | 
			
		||||
 *
 | 
			
		||||
 * @see ITurtleAccess#playAnimation(TurtleAnimation)
 | 
			
		||||
 */
 | 
			
		||||
public enum TurtleAnimation
 | 
			
		||||
{
 | 
			
		||||
public enum TurtleAnimation {
 | 
			
		||||
    /**
 | 
			
		||||
     * An animation which does nothing. This takes no time to complete.
 | 
			
		||||
     *
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(TurtleCommand)}.
 | 
			
		||||
 *
 | 
			
		||||
 * @see ITurtleAccess#executeCommand(TurtleCommand)
 | 
			
		||||
 */
 | 
			
		||||
@FunctionalInterface
 | 
			
		||||
public interface TurtleCommand {
 | 
			
		||||
    /**
 | 
			
		||||
     * Will be called by the turtle on the main thread when it is time to execute the custom command.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * The handler should either perform the work of the command, and return success, or return
 | 
			
		||||
     * failure with an error message to indicate the command cannot be executed at this time.
 | 
			
		||||
     *
 | 
			
		||||
     * @param turtle Access to the turtle for whom the command was issued.
 | 
			
		||||
     * @return A result, indicating whether this action succeeded or not.
 | 
			
		||||
     * @see ITurtleAccess#executeCommand(TurtleCommand)
 | 
			
		||||
     * @see TurtleCommandResult#success()
 | 
			
		||||
     * @see TurtleCommandResult#failure(String)
 | 
			
		||||
     * @see TurtleCommandResult
 | 
			
		||||
     */
 | 
			
		||||
    TurtleCommandResult execute(ITurtleAccess turtle);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,100 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Used to indicate the result of executing a turtle command.
 | 
			
		||||
 *
 | 
			
		||||
 * @see TurtleCommand#execute(ITurtleAccess)
 | 
			
		||||
 * @see ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)
 | 
			
		||||
 */
 | 
			
		||||
public final class TurtleCommandResult {
 | 
			
		||||
    private static final TurtleCommandResult EMPTY_SUCCESS = new TurtleCommandResult(true, null, null);
 | 
			
		||||
    private static final TurtleCommandResult EMPTY_FAILURE = new TurtleCommandResult(false, null, null);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a successful command result with no result.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A successful command result with no values.
 | 
			
		||||
     */
 | 
			
		||||
    public static TurtleCommandResult success() {
 | 
			
		||||
        return EMPTY_SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a successful command result with the given result values.
 | 
			
		||||
     *
 | 
			
		||||
     * @param results The results of executing this command.
 | 
			
		||||
     * @return A successful command result with the given values.
 | 
			
		||||
     */
 | 
			
		||||
    public static TurtleCommandResult success(@Nullable Object[] results) {
 | 
			
		||||
        if (results == null || results.length == 0) return EMPTY_SUCCESS;
 | 
			
		||||
        return new TurtleCommandResult(true, null, results);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a failed command result with no error message.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A failed command result with no message.
 | 
			
		||||
     */
 | 
			
		||||
    public static TurtleCommandResult failure() {
 | 
			
		||||
        return EMPTY_FAILURE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a failed command result with an error message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param errorMessage The error message to provide.
 | 
			
		||||
     * @return A failed command result with a message.
 | 
			
		||||
     */
 | 
			
		||||
    public static TurtleCommandResult failure(@Nullable String errorMessage) {
 | 
			
		||||
        if (errorMessage == null) return EMPTY_FAILURE;
 | 
			
		||||
        return new TurtleCommandResult(false, errorMessage, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final boolean success;
 | 
			
		||||
    private final @Nullable String errorMessage;
 | 
			
		||||
    private final @Nullable Object[] results;
 | 
			
		||||
 | 
			
		||||
    private TurtleCommandResult(boolean success, @Nullable String errorMessage, @Nullable Object[] results) {
 | 
			
		||||
        this.success = success;
 | 
			
		||||
        this.errorMessage = errorMessage;
 | 
			
		||||
        this.results = results;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine whether the command executed successfully.
 | 
			
		||||
     *
 | 
			
		||||
     * @return If the command was successful.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isSuccess() {
 | 
			
		||||
        return success;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the error message of this command result.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The command's error message, or {@code null} if it was a success.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public String getErrorMessage() {
 | 
			
		||||
        return errorMessage;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the resulting values of this command result.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The command's result, or {@code null} if it was a failure.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Object[] getResults() {
 | 
			
		||||
        return results;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.OptionalInt;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A function called when a turtle attempts to refuel via {@code turtle.refuel()}. This may be used to provide
 | 
			
		||||
 * alternative fuel sources, such as consuming RF batteries.
 | 
			
		||||
 *
 | 
			
		||||
 * @see ComputerCraftAPI#registerRefuelHandler(TurtleRefuelHandler)
 | 
			
		||||
 */
 | 
			
		||||
public interface TurtleRefuelHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * Refuel a turtle using an item.
 | 
			
		||||
     *
 | 
			
		||||
     * @param turtle The turtle to refuel.
 | 
			
		||||
     * @param stack  The stack to refuel with.
 | 
			
		||||
     * @param slot   The slot the stack resides within. This may be used to modify the inventory afterwards.
 | 
			
		||||
     * @param limit  The maximum number of refuel operations to perform. This will often correspond to the number of
 | 
			
		||||
     *               items to consume.
 | 
			
		||||
     *               <p>
 | 
			
		||||
     *               This value may be zero. In this case, you should still detect if the item can be handled (returning
 | 
			
		||||
     *               {@code OptionalInt#of(0)} if so), but should <em>NOT</em> modify the stack or inventory.
 | 
			
		||||
     * @return The amount of fuel gained, or {@link OptionalInt#empty()} if this handler does not accept the given item.
 | 
			
		||||
     */
 | 
			
		||||
    OptionalInt refuel(ITurtleAccess turtle, ItemStack stack, int slot, int limit);
 | 
			
		||||
}
 | 
			
		||||
@@ -8,8 +8,7 @@ package dan200.computercraft.api.turtle;
 | 
			
		||||
/**
 | 
			
		||||
 * An enum representing the two sides of the turtle that a turtle turtle might reside.
 | 
			
		||||
 */
 | 
			
		||||
public enum TurtleSide
 | 
			
		||||
{
 | 
			
		||||
public enum TurtleSide {
 | 
			
		||||
    /**
 | 
			
		||||
     * The turtle's left side (where the pickaxe usually is on a Wireless Mining Turtle).
 | 
			
		||||
     */
 | 
			
		||||
@@ -0,0 +1,139 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftTags;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
 | 
			
		||||
import dan200.computercraft.impl.PlatformHelper;
 | 
			
		||||
import net.minecraft.core.registries.Registries;
 | 
			
		||||
import net.minecraft.data.DataGenerator;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.tags.TagKey;
 | 
			
		||||
import net.minecraft.world.entity.ai.attributes.Attributes;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A data provider to generate turtle upgrades.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This should be subclassed and registered to a {@link DataGenerator.PackGenerator}. Override the
 | 
			
		||||
 * {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
 | 
			
		||||
 * generate them.
 | 
			
		||||
 *
 | 
			
		||||
 * @see TurtleUpgradeSerialiser
 | 
			
		||||
 */
 | 
			
		||||
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
 | 
			
		||||
    private static final ResourceLocation TOOL_ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "tool");
 | 
			
		||||
 | 
			
		||||
    public TurtleUpgradeDataProvider(PackOutput output) {
 | 
			
		||||
        super(output, "Turtle Upgrades", "computercraft/turtle_upgrades", TurtleUpgradeSerialiser.REGISTRY_ID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new turtle tool upgrade, such as a pickaxe or shovel.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id   The ID of this tool.
 | 
			
		||||
     * @param item The item used for tool actions. Note, this doesn't inherit all properties of the tool, you may need
 | 
			
		||||
     *             to specify {@link ToolBuilder#damageMultiplier(float)} and {@link ToolBuilder#breakable(TagKey)}.
 | 
			
		||||
     * @return A tool builder,
 | 
			
		||||
     */
 | 
			
		||||
    public final ToolBuilder tool(ResourceLocation id, Item item) {
 | 
			
		||||
        return new ToolBuilder(id, existingSerialiser(TOOL_ID), item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A builder for custom turtle tool upgrades.
 | 
			
		||||
     *
 | 
			
		||||
     * @see #tool(ResourceLocation, Item)
 | 
			
		||||
     */
 | 
			
		||||
    public static class ToolBuilder {
 | 
			
		||||
        private final ResourceLocation id;
 | 
			
		||||
        private final TurtleUpgradeSerialiser<?> serialiser;
 | 
			
		||||
        private final Item toolItem;
 | 
			
		||||
        private @Nullable String adjective;
 | 
			
		||||
        private @Nullable Item craftingItem;
 | 
			
		||||
        private @Nullable Float damageMultiplier = null;
 | 
			
		||||
        private @Nullable TagKey<Block> breakable;
 | 
			
		||||
 | 
			
		||||
        ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
 | 
			
		||||
            this.id = id;
 | 
			
		||||
            this.serialiser = serialiser;
 | 
			
		||||
            this.toolItem = toolItem;
 | 
			
		||||
            craftingItem = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Specify a custom adjective for this tool. By default this takes its adjective from the tool item.
 | 
			
		||||
         *
 | 
			
		||||
         * @param adjective The new adjective to use.
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder adjective(String adjective) {
 | 
			
		||||
            this.adjective = adjective;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Specify a custom item which is used to craft this upgrade. By default this is the same as the provided tool
 | 
			
		||||
         * item, but you may wish to override it.
 | 
			
		||||
         *
 | 
			
		||||
         * @param craftingItem The item used to craft this upgrade.
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder craftingItem(Item craftingItem) {
 | 
			
		||||
            this.craftingItem = craftingItem;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The amount of damage a swing of this tool will do. This is multiplied by {@link Attributes#ATTACK_DAMAGE} to
 | 
			
		||||
         * get the final damage.
 | 
			
		||||
         *
 | 
			
		||||
         * @param damageMultiplier The damage multiplier.
 | 
			
		||||
         * @return The tool builder, for futher use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder damageMultiplier(float damageMultiplier) {
 | 
			
		||||
            this.damageMultiplier = damageMultiplier;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks
 | 
			
		||||
         * in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can
 | 
			
		||||
         * be broken.
 | 
			
		||||
         *
 | 
			
		||||
         * @param breakable The tag containing all blocks breakable by this item.
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         * @see ComputerCraftTags.Blocks
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder breakable(TagKey<Block> breakable) {
 | 
			
		||||
            this.breakable = breakable;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Register this as an upgrade.
 | 
			
		||||
         *
 | 
			
		||||
         * @param add The callback given to {@link #addUpgrades(Consumer)}.
 | 
			
		||||
         */
 | 
			
		||||
        public void add(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
 | 
			
		||||
            add.accept(new Upgrade<>(id, serialiser, s -> {
 | 
			
		||||
                s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, toolItem).toString());
 | 
			
		||||
                if (adjective != null) s.addProperty("adjective", adjective);
 | 
			
		||||
                if (craftingItem != null) {
 | 
			
		||||
                    s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registries.ITEM, craftingItem).toString());
 | 
			
		||||
                }
 | 
			
		||||
                if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
 | 
			
		||||
                if (breakable != null) s.addProperty("breakable", breakable.location().toString());
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,111 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.item.crafting.RecipeSerializer;
 | 
			
		||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Reads a {@link ITurtleUpgrade} from disk and reads/writes it to a network packet.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
 | 
			
		||||
 * {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
 | 
			
		||||
 *
 | 
			
		||||
 * <h2>Example (Forge)</h2>
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
 | 
			
		||||
 *
 | 
			
		||||
 * // Register a new upgrade serialiser called "my_upgrade".
 | 
			
		||||
 * public static final RegistryObject<TurtleUpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
 | 
			
		||||
 *     SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
 | 
			
		||||
 *
 | 
			
		||||
 * // Then in your constructor
 | 
			
		||||
 * SERIALISERS.register( bus );
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * We can then define a new upgrade using JSON by placing the following in
 | 
			
		||||
 * {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
 | 
			
		||||
 *
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * {
 | 
			
		||||
 *     "type": my_mod:my_upgrade",
 | 
			
		||||
 * }
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Finally, we need to register a model for our upgrade. This is done with
 | 
			
		||||
 * {@link dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller}:
 | 
			
		||||
 *
 | 
			
		||||
 * <pre>{@code
 | 
			
		||||
 * // Register our model inside FMLClientSetupEvent
 | 
			
		||||
 * ComputerCraftAPIClient.registerTurtleUpgradeModeller(MY_UPGRADE.get(), TurtleUpgradeModeller.flatItem())
 | 
			
		||||
 * }</pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of turtle upgrade this is responsible for serialising.
 | 
			
		||||
 * @see ITurtleUpgrade
 | 
			
		||||
 * @see TurtleUpgradeDataProvider
 | 
			
		||||
 * @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
 | 
			
		||||
 */
 | 
			
		||||
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID for the associated registry.
 | 
			
		||||
     */
 | 
			
		||||
    ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade. This is similar to a {@link SimpleCraftingRecipeSerializer},
 | 
			
		||||
     * but for upgrades.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * If you might want to vary the item, it's suggested you use {@link #simpleWithCustomItem(BiFunction)} instead.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simple(Function<ResourceLocation, T> factory) {
 | 
			
		||||
        final class Impl extends SimpleSerialiser<T> implements TurtleUpgradeSerialiser<T> {
 | 
			
		||||
            private Impl(Function<ResourceLocation, T> constructor) {
 | 
			
		||||
                super(constructor);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Impl(factory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an upgrade serialiser for a simple upgrade whose crafting item can be specified.
 | 
			
		||||
     *
 | 
			
		||||
     * @param factory Generate a new upgrade with a specific ID and crafting item. The returned upgrade's
 | 
			
		||||
     *                {@link UpgradeBase#getCraftingItem()} <strong>MUST</strong> equal the provided item.
 | 
			
		||||
     * @param <T>     The type of the generated upgrade.
 | 
			
		||||
     * @return The serialiser for this upgrade.
 | 
			
		||||
     * @see #simple(Function)  For upgrades whose crafting stack should not vary.
 | 
			
		||||
     */
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeSerialiser<T> simpleWithCustomItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
        final class Impl extends SerialiserWithCraftingItem<T> implements TurtleUpgradeSerialiser<T> {
 | 
			
		||||
            private Impl(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
                super(factory);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Impl(factory);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,8 +10,7 @@ package dan200.computercraft.api.turtle;
 | 
			
		||||
 *
 | 
			
		||||
 * @see ITurtleUpgrade#getType()
 | 
			
		||||
 */
 | 
			
		||||
public enum TurtleUpgradeType
 | 
			
		||||
{
 | 
			
		||||
public enum TurtleUpgradeType {
 | 
			
		||||
    /**
 | 
			
		||||
     * A tool is rendered as an item on the side of the turtle, and responds to the {@code turtle.dig()}
 | 
			
		||||
     * and {@code turtle.attack()} methods (Such as pickaxe or sword on Mining and Melee turtles).
 | 
			
		||||
@@ -31,13 +30,11 @@ public enum TurtleUpgradeType
 | 
			
		||||
     */
 | 
			
		||||
    BOTH;
 | 
			
		||||
 | 
			
		||||
    public boolean isTool()
 | 
			
		||||
    {
 | 
			
		||||
    public boolean isTool() {
 | 
			
		||||
        return this == TOOL || this == BOTH;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isPeripheral()
 | 
			
		||||
    {
 | 
			
		||||
    public boolean isPeripheral() {
 | 
			
		||||
        return this == PERIPHERAL || this == BOTH;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -14,8 +14,7 @@ import net.minecraft.core.Direction;
 | 
			
		||||
 * @see ITurtleUpgrade#getType()
 | 
			
		||||
 * @see ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, Direction)
 | 
			
		||||
 */
 | 
			
		||||
public enum TurtleVerb
 | 
			
		||||
{
 | 
			
		||||
public enum TurtleVerb {
 | 
			
		||||
    /**
 | 
			
		||||
     * The turtle called {@code turtle.dig()}, {@code turtle.digUp()} or {@code turtle.digDown()}.
 | 
			
		||||
     */
 | 
			
		||||
@@ -0,0 +1,94 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.impl.PlatformHelper;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
 | 
			
		||||
 */
 | 
			
		||||
public interface UpgradeBase {
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem"
 | 
			
		||||
     * or "my_mod:my_upgrade".
 | 
			
		||||
     * <p>
 | 
			
		||||
     * You should use a unique resource domain to ensure this upgrade is uniquely identified.
 | 
			
		||||
     * The upgrade will fail registration if an already used ID is specified.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The unique ID for this upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    ResourceLocation getUpgradeID();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return an unlocalised string to describe this type of computer in item names.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
 | 
			
		||||
     *
 | 
			
		||||
     * @return The localisation key for this upgrade's adjective.
 | 
			
		||||
     */
 | 
			
		||||
    String getUnlocalisedAdjective();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return an item stack representing the type of item that a computer must be crafted
 | 
			
		||||
     * with to create a version which holds this upgrade. This item stack is also used
 | 
			
		||||
     * to determine the upgrade given by {@code turtle.equipLeft()} or {@code pocket.equipBack()}
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should be constant over a session (or at least a datapack reload). It is recommended
 | 
			
		||||
     * that you cache the stack too, in order to prevent constructing it every time the method
 | 
			
		||||
     * is called.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
 | 
			
		||||
     */
 | 
			
		||||
    ItemStack getCraftingItem();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if an item is suitable for being used for this upgrade.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * When un-equipping an upgrade, we return {@link #getCraftingItem()} rather than
 | 
			
		||||
     * the original stack. In order to prevent people losing items with enchantments (or
 | 
			
		||||
     * repairing items with non-0 damage), we impose additional checks on the item.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * The default check requires that any non-capability NBT is exactly the same as the
 | 
			
		||||
     * crafting item, but this may be relaxed for your upgrade.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This is based on {@code net.minecraftforge.common.crafting.StrictNBTIngredient}'s check.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
 | 
			
		||||
     *              {@link #getCraftingItem()}.
 | 
			
		||||
     * @return If this stack may be used to equip this upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    default boolean isItemSuitable(ItemStack stack) {
 | 
			
		||||
        var crafting = getCraftingItem();
 | 
			
		||||
 | 
			
		||||
        // A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
 | 
			
		||||
        // null one.
 | 
			
		||||
        var shareTag = PlatformHelper.get().getShareTag(stack);
 | 
			
		||||
        var craftingShareTag = PlatformHelper.get().getShareTag(crafting);
 | 
			
		||||
        if (shareTag == craftingShareTag) return true;
 | 
			
		||||
        if (shareTag == null) return Objects.requireNonNull(craftingShareTag).isEmpty();
 | 
			
		||||
        if (craftingShareTag == null) return shareTag.isEmpty();
 | 
			
		||||
        return shareTag.equals(craftingShareTag);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a suitable default unlocalised adjective for an upgrade ID. This converts "modid:some_upgrade" to
 | 
			
		||||
     * "upgrade.modid.some_upgrade.adjective".
 | 
			
		||||
     *
 | 
			
		||||
     * @param id The upgrade ID.
 | 
			
		||||
     * @return The  generated adjective.
 | 
			
		||||
     * @see #getUnlocalisedAdjective()
 | 
			
		||||
     */
 | 
			
		||||
    static String getDefaultAdjective(ResourceLocation id) {
 | 
			
		||||
        return Util.makeDescriptionId("upgrade", id) + ".adjective";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,171 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import com.google.gson.JsonParseException;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.impl.PlatformHelper;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
 | 
			
		||||
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.core.registries.Registries;
 | 
			
		||||
import net.minecraft.data.CachedOutput;
 | 
			
		||||
import net.minecraft.data.DataProvider;
 | 
			
		||||
import net.minecraft.data.PackOutput;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import org.apache.logging.log4j.LogManager;
 | 
			
		||||
import org.apache.logging.log4j.Logger;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A data generator/provider for turtle and pocket computer upgrades. This should not be extended directly, instead see
 | 
			
		||||
 * the other subclasses.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The base class of upgrades.
 | 
			
		||||
 * @param <R> The upgrade serialiser to register for.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
 | 
			
		||||
    private static final Logger LOGGER = LogManager.getLogger();
 | 
			
		||||
 | 
			
		||||
    private final PackOutput output;
 | 
			
		||||
    private final String name;
 | 
			
		||||
    private final String folder;
 | 
			
		||||
    private final ResourceKey<Registry<R>> registry;
 | 
			
		||||
 | 
			
		||||
    private @Nullable List<T> upgrades;
 | 
			
		||||
 | 
			
		||||
    protected UpgradeDataProvider(PackOutput output, String name, String folder, ResourceKey<Registry<R>> registry) {
 | 
			
		||||
        this.output = output;
 | 
			
		||||
        this.name = name;
 | 
			
		||||
        this.folder = folder;
 | 
			
		||||
        this.registry = registry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
 | 
			
		||||
     *
 | 
			
		||||
     * @param id         The ID of the upgrade to create.
 | 
			
		||||
     * @param serialiser The simple serialiser.
 | 
			
		||||
     * @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
 | 
			
		||||
     */
 | 
			
		||||
    public final Upgrade<R> simple(ResourceLocation id, R serialiser) {
 | 
			
		||||
        if (!(serialiser instanceof SimpleSerialiser)) {
 | 
			
		||||
            throw new IllegalStateException(serialiser + " must be a simple() seriaiser.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Upgrade<>(id, serialiser, s -> {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register an upgrade using a "simple" serialiser (e.g. {@link TurtleUpgradeSerialiser#simple(Function)}).
 | 
			
		||||
     *
 | 
			
		||||
     * @param id         The ID of the upgrade to create.
 | 
			
		||||
     * @param serialiser The simple serialiser.
 | 
			
		||||
     * @param item       The crafting upgrade for this item.
 | 
			
		||||
     * @return The constructed upgrade, ready to be passed off to {@link #addUpgrades(Consumer)}'s consumer.
 | 
			
		||||
     */
 | 
			
		||||
    public final Upgrade<R> simpleWithCustomItem(ResourceLocation id, R serialiser, Item item) {
 | 
			
		||||
        if (!(serialiser instanceof SerialiserWithCraftingItem)) {
 | 
			
		||||
            throw new IllegalStateException(serialiser + " must be a simpleWithCustomItem() serialiser.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Upgrade<>(id, serialiser, s ->
 | 
			
		||||
            s.addProperty("item", PlatformHelper.get().getRegistryKey(Registries.ITEM, item).toString())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add all turtle or pocket computer upgrades.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * <strong>Example usage:</strong>
 | 
			
		||||
     * <pre>{@code
 | 
			
		||||
     * protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
 | 
			
		||||
     *     simple(new ResourceLocation("mymod", "speaker"), SPEAKER_SERIALISER.get()).add(addUpgrade);
 | 
			
		||||
     * }
 | 
			
		||||
     * }</pre>
 | 
			
		||||
     *
 | 
			
		||||
     * @param addUpgrade A callback used to register an upgrade.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final CompletableFuture<?> run(CachedOutput cache) {
 | 
			
		||||
        var base = output.getOutputFolder().resolve("data");
 | 
			
		||||
 | 
			
		||||
        Set<ResourceLocation> seen = new HashSet<>();
 | 
			
		||||
        List<T> upgrades = new ArrayList<>();
 | 
			
		||||
        List<CompletableFuture<?>> futures = new ArrayList<>();
 | 
			
		||||
        addUpgrades(upgrade -> {
 | 
			
		||||
            if (!seen.add(upgrade.id())) throw new IllegalStateException("Duplicate upgrade " + upgrade.id());
 | 
			
		||||
 | 
			
		||||
            var json = new JsonObject();
 | 
			
		||||
            json.addProperty("type", PlatformHelper.get().getRegistryKey(registry, upgrade.serialiser()).toString());
 | 
			
		||||
            upgrade.serialise().accept(json);
 | 
			
		||||
 | 
			
		||||
            futures.add(DataProvider.saveStable(cache, json, base.resolve(upgrade.id().getNamespace() + "/" + folder + "/" + upgrade.id().getPath() + ".json")));
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                var result = upgrade.serialiser().fromJson(upgrade.id(), json);
 | 
			
		||||
                upgrades.add(result);
 | 
			
		||||
            } catch (IllegalArgumentException | JsonParseException e) {
 | 
			
		||||
                LOGGER.error("Failed to parse {} {}", name, upgrade.id(), e);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.upgrades = upgrades;
 | 
			
		||||
        return Util.sequenceFailFast(futures);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final String getName() {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public final R existingSerialiser(ResourceLocation id) {
 | 
			
		||||
        var result = PlatformHelper.get().getRegistryObject(registry, id);
 | 
			
		||||
        if (result == null) throw new IllegalArgumentException("No such serialiser " + registry);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<T> getGeneratedUpgrades() {
 | 
			
		||||
        if (upgrades == null) throw new IllegalStateException("Upgrades have not beeen generated yet");
 | 
			
		||||
        return upgrades;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A constructed upgrade instance, produced {@link #addUpgrades(Consumer)}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id         The ID for this upgrade.
 | 
			
		||||
     * @param serialiser The serialiser which reads and writes this upgrade.
 | 
			
		||||
     * @param serialise  Augment the generated JSON with additional fields.
 | 
			
		||||
     * @param <R>        The type of upgrade serialiser.
 | 
			
		||||
     */
 | 
			
		||||
    public record Upgrade<R extends UpgradeSerialiser<?>>(
 | 
			
		||||
        ResourceLocation id, R serialiser, Consumer<JsonObject> serialise
 | 
			
		||||
    ) {
 | 
			
		||||
        /**
 | 
			
		||||
         * Convenience method for registering an upgrade.
 | 
			
		||||
         *
 | 
			
		||||
         * @param add The callback given to {@link #addUpgrades(Consumer)}
 | 
			
		||||
         */
 | 
			
		||||
        public void add(Consumer<Upgrade<R>> add) {
 | 
			
		||||
            add.accept(this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,53 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of the public ComputerCraft API - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
 | 
			
		||||
 * For help using the API, and posting your mods, visit the forums at computercraft.info.
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base interface for upgrade serialisers. This should generally not be implemented directly, instead implementing one
 | 
			
		||||
 * of {@link TurtleUpgradeSerialiser} or {@link PocketUpgradeSerialiser}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * However, it may sometimes be useful to implement this if you have some shared logic between upgrade types.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The upgrade that this class can serialise and deserialise.
 | 
			
		||||
 * @see TurtleUpgradeSerialiser
 | 
			
		||||
 * @see PocketUpgradeSerialiser
 | 
			
		||||
 */
 | 
			
		||||
public interface UpgradeSerialiser<T extends UpgradeBase> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Read this upgrade from a JSON file in a datapack.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id     The ID of this upgrade.
 | 
			
		||||
     * @param object The JSON object to load this upgrade from.
 | 
			
		||||
     * @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
 | 
			
		||||
     * @see net.minecraft.util.GsonHelper For additional JSON helper methods.
 | 
			
		||||
     */
 | 
			
		||||
    T fromJson(ResourceLocation id, JsonObject object);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Read this upgrade from a network packet, sent from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id     The ID of this upgrade.
 | 
			
		||||
     * @param buffer The buffer object to read this upgrade from.
 | 
			
		||||
     * @return The constructed upgrade, with a {@link UpgradeBase#getUpgradeID()} equal to {@code id}.
 | 
			
		||||
     */
 | 
			
		||||
    T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Write this upgrade to a network packet, to be sent to the client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param buffer  The buffer object to write this upgrade to
 | 
			
		||||
     * @param upgrade The upgrade to write.
 | 
			
		||||
     */
 | 
			
		||||
    void toNetwork(FriendlyByteBuf buffer, T upgrade);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.detail.BlockReference;
 | 
			
		||||
import dan200.computercraft.api.detail.DetailRegistry;
 | 
			
		||||
import dan200.computercraft.api.filesystem.Mount;
 | 
			
		||||
import dan200.computercraft.api.filesystem.WritableMount;
 | 
			
		||||
import dan200.computercraft.api.lua.GenericSource;
 | 
			
		||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
 | 
			
		||||
import dan200.computercraft.api.media.MediaProvider;
 | 
			
		||||
import dan200.computercraft.api.network.PacketNetwork;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredElement;
 | 
			
		||||
import dan200.computercraft.api.network.wired.WiredNode;
 | 
			
		||||
import dan200.computercraft.api.redstone.BundledRedstoneProvider;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleRefuelHandler;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.server.MinecraftServer;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Backing interface for {@link ComputerCraftAPI}
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public interface ComputerCraftAPIService {
 | 
			
		||||
    static ComputerCraftAPIService get() {
 | 
			
		||||
        var instance = Instance.INSTANCE;
 | 
			
		||||
        return instance == null ? Services.raise(ComputerCraftAPIService.class, Instance.ERROR) : instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    String getInstalledVersion();
 | 
			
		||||
 | 
			
		||||
    int createUniqueNumberedSaveDir(MinecraftServer server, String parentSubPath);
 | 
			
		||||
 | 
			
		||||
    WritableMount createSaveDirMount(MinecraftServer server, String subPath, long capacity);
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    Mount createResourceMount(MinecraftServer server, String domain, String subPath);
 | 
			
		||||
 | 
			
		||||
    void registerGenericSource(GenericSource source);
 | 
			
		||||
 | 
			
		||||
    void registerBundledRedstoneProvider(BundledRedstoneProvider provider);
 | 
			
		||||
 | 
			
		||||
    int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side);
 | 
			
		||||
 | 
			
		||||
    void registerMediaProvider(MediaProvider provider);
 | 
			
		||||
 | 
			
		||||
    PacketNetwork getWirelessNetwork(MinecraftServer server);
 | 
			
		||||
 | 
			
		||||
    void registerAPIFactory(ILuaAPIFactory factory);
 | 
			
		||||
 | 
			
		||||
    WiredNode createWiredNodeForElement(WiredElement element);
 | 
			
		||||
 | 
			
		||||
    void registerRefuelHandler(TurtleRefuelHandler handler);
 | 
			
		||||
 | 
			
		||||
    DetailRegistry<ItemStack> getItemStackDetailRegistry();
 | 
			
		||||
 | 
			
		||||
    DetailRegistry<BlockReference> getBlockInWorldDetailRegistry();
 | 
			
		||||
 | 
			
		||||
    final class Instance {
 | 
			
		||||
        static final @Nullable ComputerCraftAPIService INSTANCE;
 | 
			
		||||
        static final @Nullable Throwable ERROR;
 | 
			
		||||
 | 
			
		||||
        static {
 | 
			
		||||
            var helper = Services.tryLoad(ComputerCraftAPIService.class);
 | 
			
		||||
            INSTANCE = helper.instance();
 | 
			
		||||
            ERROR = helper.error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Instance() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,82 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstraction layer for Forge and Fabric. See implementations for more details.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public interface PlatformHelper {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the current {@link PlatformHelper} instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The current instance.
 | 
			
		||||
     */
 | 
			
		||||
    static PlatformHelper get() {
 | 
			
		||||
        var instance = Instance.INSTANCE;
 | 
			
		||||
        return instance == null ? Services.raise(PlatformHelper.class, Instance.ERROR) : instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the unique ID for a registered object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param registry The registry to look up this object in.
 | 
			
		||||
     * @param object   The object to look up.
 | 
			
		||||
     * @param <T>      The type of object the registry stores.
 | 
			
		||||
     * @return The registered object's ID.
 | 
			
		||||
     * @throws IllegalArgumentException If the registry or object are not registered.
 | 
			
		||||
     */
 | 
			
		||||
    <T> ResourceLocation getRegistryKey(ResourceKey<Registry<T>> registry, T object);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Look up an ID in a registry, returning the registered object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param registry The registry to look up this object in.
 | 
			
		||||
     * @param id       The ID to look up.
 | 
			
		||||
     * @param <T>      The type of object the registry stores.
 | 
			
		||||
     * @return The resolved registry object.
 | 
			
		||||
     * @throws IllegalArgumentException If the registry or object are not registered.
 | 
			
		||||
     */
 | 
			
		||||
    <T> T getRegistryObject(ResourceKey<Registry<T>> registry, ResourceLocation id);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the subset of an {@link ItemStack}'s {@linkplain ItemStack#getTag() tag} which is synced to the client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param item The stack.
 | 
			
		||||
     * @return The item's tag.
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    default CompoundTag getShareTag(ItemStack item) {
 | 
			
		||||
        return item.getTag();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final class Instance {
 | 
			
		||||
        static final @Nullable PlatformHelper INSTANCE;
 | 
			
		||||
        static final @Nullable Throwable ERROR;
 | 
			
		||||
 | 
			
		||||
        static {
 | 
			
		||||
            // We don't want class initialisation to fail here (as that results in confusing errors). Instead, capture
 | 
			
		||||
            // the error and rethrow it when accessing. This should be JITted away in the common case.
 | 
			
		||||
            var helper = Services.tryLoad(PlatformHelper.class);
 | 
			
		||||
            INSTANCE = helper.instance();
 | 
			
		||||
            ERROR = helper.error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Instance() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -8,6 +8,7 @@ package dan200.computercraft.impl;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A ComputerCraft-related service failed to load.
 | 
			
		||||
@@ -15,12 +16,11 @@ import javax.annotation.Nullable;
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
class ServiceException extends RuntimeException
 | 
			
		||||
{
 | 
			
		||||
class ServiceException extends RuntimeException {
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = -8392300691666423882L;
 | 
			
		||||
 | 
			
		||||
    ServiceException( String message, @Nullable Throwable cause )
 | 
			
		||||
    {
 | 
			
		||||
        super( message, cause );
 | 
			
		||||
    ServiceException(String message, @Nullable Throwable cause) {
 | 
			
		||||
        super(message, cause);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,95 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.ServiceLoader;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utilities for loading services.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public final class Services {
 | 
			
		||||
    private Services() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load a service, asserting that only a single instance is registered.
 | 
			
		||||
     *
 | 
			
		||||
     * @param klass The class of the service to load.
 | 
			
		||||
     * @param <T>   The class of the service to load.
 | 
			
		||||
     * @return The constructed service instance.
 | 
			
		||||
     * @throws IllegalStateException When the service cannot be loaded.
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> T load(Class<T> klass) {
 | 
			
		||||
        var services = ServiceLoader.load(klass).stream().toList();
 | 
			
		||||
        return switch (services.size()) {
 | 
			
		||||
            case 1 -> services.get(0).get();
 | 
			
		||||
            case 0 -> throw new IllegalStateException("Cannot find service for " + klass.getName());
 | 
			
		||||
            default -> {
 | 
			
		||||
                var serviceTypes = services.stream().map(x -> x.type().getName()).collect(Collectors.joining(", "));
 | 
			
		||||
                throw new IllegalStateException("Multiple services for " + klass.getName() + ": " + serviceTypes);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempt to load a service with {@link #load(Class)}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param klass The class of the service to load.
 | 
			
		||||
     * @param <T>   The class of the service to load.
 | 
			
		||||
     * @return The result type, either containing the service or an exception.
 | 
			
		||||
     * @see ComputerCraftAPIService Intended usage of this class.
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> LoadedService<T> tryLoad(Class<T> klass) {
 | 
			
		||||
        try {
 | 
			
		||||
            return new LoadedService<>(load(klass), null);
 | 
			
		||||
        } catch (Exception | LinkageError e) {
 | 
			
		||||
            return new LoadedService<>(null, e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Raise an exception from trying to load a specific service.
 | 
			
		||||
     *
 | 
			
		||||
     * @param klass The class of the service we failed to load.
 | 
			
		||||
     * @param e     The original exception caused by loading this class.
 | 
			
		||||
     * @param <T>   The class of the service to load.
 | 
			
		||||
     * @return Never
 | 
			
		||||
     * @see #tryLoad(Class)
 | 
			
		||||
     * @see LoadedService#error()
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings("DoNotCallSuggester")
 | 
			
		||||
    public static <T> T raise(Class<T> klass, @Nullable Throwable e) {
 | 
			
		||||
        // Throw a new exception so there's a useful stack trace there somewhere!
 | 
			
		||||
        throw new ServiceException("Failed to instantiate " + klass.getName(), e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class LoadedService<T> {
 | 
			
		||||
        private final @Nullable T instance;
 | 
			
		||||
        private final @Nullable Throwable error;
 | 
			
		||||
 | 
			
		||||
        LoadedService(@Nullable T instance, @Nullable Throwable error) {
 | 
			
		||||
            this.instance = instance;
 | 
			
		||||
            this.error = error;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nullable
 | 
			
		||||
        public T instance() {
 | 
			
		||||
            return instance;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nullable
 | 
			
		||||
        public Throwable error() {
 | 
			
		||||
            return error;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Internal interfaces for ComputerCraft's API.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
@DefaultQualifier(value = NonNull.class, locations = {
 | 
			
		||||
    TypeUseLocation.RETURN,
 | 
			
		||||
    TypeUseLocation.PARAMETER,
 | 
			
		||||
    TypeUseLocation.FIELD,
 | 
			
		||||
})
 | 
			
		||||
package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import org.checkerframework.checker.nullness.qual.NonNull;
 | 
			
		||||
import org.checkerframework.framework.qual.DefaultQualifier;
 | 
			
		||||
import org.checkerframework.framework.qual.TypeUseLocation;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
@@ -0,0 +1,50 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.impl.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.util.GsonHelper;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiFunction;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Simple serialiser which returns a constant upgrade with a custom crafting item.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The upgrade that this class can serialise and deserialise.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public abstract class SerialiserWithCraftingItem<T extends UpgradeBase> implements UpgradeSerialiser<T> {
 | 
			
		||||
    private final BiFunction<ResourceLocation, ItemStack, T> factory;
 | 
			
		||||
 | 
			
		||||
    protected SerialiserWithCraftingItem(BiFunction<ResourceLocation, ItemStack, T> factory) {
 | 
			
		||||
        this.factory = factory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromJson(ResourceLocation id, JsonObject object) {
 | 
			
		||||
        var item = GsonHelper.getAsItem(object, "item");
 | 
			
		||||
        return factory.apply(id, new ItemStack(item));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
 | 
			
		||||
        var item = buffer.readItem();
 | 
			
		||||
        return factory.apply(id, item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
 | 
			
		||||
        buffer.writeItem(upgrade.getCraftingItem());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.impl.upgrades;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
 | 
			
		||||
import net.minecraft.network.FriendlyByteBuf;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Simple serialiser which returns a constant upgrade.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The upgrade that this class can serialise and deserialise.
 | 
			
		||||
 */
 | 
			
		||||
@ApiStatus.Internal
 | 
			
		||||
public abstract class SimpleSerialiser<T extends UpgradeBase> implements UpgradeSerialiser<T> {
 | 
			
		||||
    private final Function<ResourceLocation, T> constructor;
 | 
			
		||||
 | 
			
		||||
    public SimpleSerialiser(Function<ResourceLocation, T> constructor) {
 | 
			
		||||
        this.constructor = constructor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromJson(ResourceLocation id, JsonObject object) {
 | 
			
		||||
        return constructor.apply(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final T fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
 | 
			
		||||
        return constructor.apply(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final void toNetwork(FriendlyByteBuf buffer, T upgrade) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								projects/common/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								projects/common/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import cc.tweaked.gradle.annotationProcessorEverywhere
 | 
			
		||||
import cc.tweaked.gradle.clientClasses
 | 
			
		||||
import cc.tweaked.gradle.commonClasses
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("cc-tweaked.vanilla")
 | 
			
		||||
    id("cc-tweaked.gametest")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
minecraft {
 | 
			
		||||
    accessWideners(
 | 
			
		||||
        "src/main/resources/computercraft.accesswidener",
 | 
			
		||||
        "src/main/resources/computercraft-common.accesswidener",
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    // Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
 | 
			
		||||
    implementation(project(":core"))
 | 
			
		||||
    implementation(commonClasses(project(":common-api")))
 | 
			
		||||
    clientImplementation(clientClasses(project(":common-api")))
 | 
			
		||||
 | 
			
		||||
    compileOnly(libs.bundles.externalMods.common)
 | 
			
		||||
 | 
			
		||||
    compileOnly(libs.mixin)
 | 
			
		||||
    annotationProcessorEverywhere(libs.autoService)
 | 
			
		||||
    testFixturesAnnotationProcessor(libs.autoService)
 | 
			
		||||
 | 
			
		||||
    testImplementation(testFixtures(project(":core")))
 | 
			
		||||
    testImplementation(libs.bundles.test)
 | 
			
		||||
    testRuntimeOnly(libs.bundles.testRuntime)
 | 
			
		||||
 | 
			
		||||
    testModImplementation(testFixtures(project(":core")))
 | 
			
		||||
    testModImplementation(testFixtures(project(":common")))
 | 
			
		||||
    testModImplementation(libs.bundles.kotlin)
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,190 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.client;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.audio.Channel;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
 | 
			
		||||
import dan200.computercraft.client.render.CableHighlightRenderer;
 | 
			
		||||
import dan200.computercraft.client.render.PocketItemRenderer;
 | 
			
		||||
import dan200.computercraft.client.render.PrintoutItemRenderer;
 | 
			
		||||
import dan200.computercraft.client.render.monitor.MonitorHighlightRenderer;
 | 
			
		||||
import dan200.computercraft.client.render.monitor.MonitorRenderState;
 | 
			
		||||
import dan200.computercraft.client.sound.SpeakerManager;
 | 
			
		||||
import dan200.computercraft.shared.CommonHooks;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.command.CommandComputerCraft;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ServerContext;
 | 
			
		||||
import dan200.computercraft.shared.media.items.PrintoutItem;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
 | 
			
		||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.util.PauseAwareTimer;
 | 
			
		||||
import dan200.computercraft.shared.util.WorldUtil;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.client.Camera;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.sounds.AudioStream;
 | 
			
		||||
import net.minecraft.client.sounds.SoundEngine;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.world.InteractionHand;
 | 
			
		||||
import net.minecraft.world.entity.decoration.ItemFrame;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.block.state.BlockState;
 | 
			
		||||
import net.minecraft.world.phys.BlockHitResult;
 | 
			
		||||
import net.minecraft.world.phys.HitResult;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Event listeners for client-only code.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This is the client-only version of {@link CommonHooks}, and so should be where all client-specific event handlers are
 | 
			
		||||
 * defined.
 | 
			
		||||
 */
 | 
			
		||||
public final class ClientHooks {
 | 
			
		||||
    private ClientHooks() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void onTick() {
 | 
			
		||||
        FrameInfo.onTick();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void onRenderTick() {
 | 
			
		||||
        PauseAwareTimer.tick(Minecraft.getInstance().isPaused());
 | 
			
		||||
        FrameInfo.onRenderTick();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void onWorldUnload() {
 | 
			
		||||
        MonitorRenderState.destroyAll();
 | 
			
		||||
        SpeakerManager.reset();
 | 
			
		||||
        ClientPocketComputers.reset();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean onChatMessage(String message) {
 | 
			
		||||
        return handleOpenComputerCommand(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
 | 
			
		||||
        return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
 | 
			
		||||
            || MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean onRenderHeldItem(
 | 
			
		||||
        PoseStack transform, MultiBufferSource render, int lightTexture, InteractionHand hand,
 | 
			
		||||
        float pitch, float equipProgress, float swingProgress, ItemStack stack
 | 
			
		||||
    ) {
 | 
			
		||||
        if (stack.getItem() instanceof PocketComputerItem) {
 | 
			
		||||
            PocketItemRenderer.INSTANCE.renderItemFirstPerson(transform, render, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (stack.getItem() instanceof PrintoutItem) {
 | 
			
		||||
            PrintoutItemRenderer.INSTANCE.renderItemFirstPerson(transform, render, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean onRenderItemFrame(PoseStack transform, MultiBufferSource render, ItemFrame frame, ItemStack stack, int light) {
 | 
			
		||||
        if (stack.getItem() instanceof PrintoutItem) {
 | 
			
		||||
            PrintoutItemRenderer.onRenderInFrame(transform, render, frame, stack, light);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void onPlayStreaming(SoundEngine engine, Channel channel, AudioStream stream) {
 | 
			
		||||
        SpeakerManager.onPlayStreaming(engine, channel, stream);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle the {@link CommandComputerCraft#OPEN_COMPUTER} "clientside command". This isn't a true command, as we
 | 
			
		||||
     * don't want it to actually be visible to the user.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message The current chat message.
 | 
			
		||||
     * @return Whether to cancel sending this message.
 | 
			
		||||
     */
 | 
			
		||||
    private static boolean handleOpenComputerCommand(String message) {
 | 
			
		||||
        if (!message.startsWith(CommandComputerCraft.OPEN_COMPUTER)) return false;
 | 
			
		||||
 | 
			
		||||
        var server = Minecraft.getInstance().getSingleplayerServer();
 | 
			
		||||
        if (server == null) return false;
 | 
			
		||||
 | 
			
		||||
        var idStr = message.substring(CommandComputerCraft.OPEN_COMPUTER.length()).trim();
 | 
			
		||||
        int id;
 | 
			
		||||
        try {
 | 
			
		||||
            id = Integer.parseInt(idStr);
 | 
			
		||||
        } catch (NumberFormatException ignore) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
 | 
			
		||||
        if (!file.isDirectory()) return false;
 | 
			
		||||
 | 
			
		||||
        Util.getPlatform().openFile(file);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add additional information about the currently targeted block to the debug screen.
 | 
			
		||||
     *
 | 
			
		||||
     * @param addText A callback which adds a single line of text.
 | 
			
		||||
     */
 | 
			
		||||
    public static void addDebugInfo(Consumer<String> addText) {
 | 
			
		||||
        var minecraft = Minecraft.getInstance();
 | 
			
		||||
        if (!minecraft.options.renderDebug || minecraft.level == null) return;
 | 
			
		||||
        if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
 | 
			
		||||
 | 
			
		||||
        var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
 | 
			
		||||
 | 
			
		||||
        if (tile instanceof MonitorBlockEntity monitor) {
 | 
			
		||||
            addText.accept("");
 | 
			
		||||
            addText.accept(
 | 
			
		||||
                String.format("Targeted monitor: (%d, %d), %d x %d", monitor.getXIndex(), monitor.getYIndex(), monitor.getWidth(), monitor.getHeight())
 | 
			
		||||
            );
 | 
			
		||||
        } else if (tile instanceof TurtleBlockEntity turtle) {
 | 
			
		||||
            addText.accept("");
 | 
			
		||||
            addText.accept("Targeted turtle:");
 | 
			
		||||
            addText.accept(String.format("Id: %d", turtle.getComputerID()));
 | 
			
		||||
            addTurtleUpgrade(addText, turtle, TurtleSide.LEFT);
 | 
			
		||||
            addTurtleUpgrade(addText, turtle, TurtleSide.RIGHT);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void addTurtleUpgrade(Consumer<String> out, TurtleBlockEntity turtle, TurtleSide side) {
 | 
			
		||||
        var upgrade = turtle.getUpgrade(side);
 | 
			
		||||
        if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.getUpgradeID()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static @Nullable BlockState getBlockBreakingState(BlockState state, BlockPos pos) {
 | 
			
		||||
        // Only apply to cables which have both a cable and modem
 | 
			
		||||
        if (state.getBlock() != ModRegistry.Blocks.CABLE.get()
 | 
			
		||||
            || !state.getValue(CableBlock.CABLE)
 | 
			
		||||
            || state.getValue(CableBlock.MODEM) == CableModemVariant.None
 | 
			
		||||
        ) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var hit = Minecraft.getInstance().hitResult;
 | 
			
		||||
        if (hit == null || hit.getType() != HitResult.Type.BLOCK) return null;
 | 
			
		||||
        var hitPos = ((BlockHitResult) hit).getBlockPos();
 | 
			
		||||
 | 
			
		||||
        if (!hitPos.equals(pos)) return null;
 | 
			
		||||
 | 
			
		||||
        return WorldUtil.isVecInside(CableShapes.getModemShape(state), hit.getLocation().subtract(pos.getX(), pos.getY(), pos.getZ()))
 | 
			
		||||
            ? state.getBlock().defaultBlockState().setValue(CableBlock.MODEM, state.getValue(CableBlock.MODEM))
 | 
			
		||||
            : state.setValue(CableBlock.MODEM, CableModemVariant.None);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,198 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.api.client.ComputerCraftAPIClient;
 | 
			
		||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.client.gui.*;
 | 
			
		||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
 | 
			
		||||
import dan200.computercraft.client.render.RenderTypes;
 | 
			
		||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
 | 
			
		||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
 | 
			
		||||
import dan200.computercraft.client.turtle.TurtleModemModeller;
 | 
			
		||||
import dan200.computercraft.core.util.Colour;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.media.items.DiskItem;
 | 
			
		||||
import dan200.computercraft.shared.media.items.TreasureDiskItem;
 | 
			
		||||
import net.minecraft.client.color.item.ItemColor;
 | 
			
		||||
import net.minecraft.client.gui.screens.MenuScreens;
 | 
			
		||||
import net.minecraft.client.multiplayer.ClientLevel;
 | 
			
		||||
import net.minecraft.client.renderer.ShaderInstance;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
 | 
			
		||||
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
 | 
			
		||||
import net.minecraft.client.renderer.item.ItemProperties;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.server.packs.resources.ResourceProvider;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.ItemLike;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntity;
 | 
			
		||||
import net.minecraft.world.level.block.entity.BlockEntityType;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.function.BiConsumer;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Registers client-side objects, such as {@link BlockEntityRendererProvider}s and
 | 
			
		||||
 * {@link MenuScreens.ScreenConstructor}.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * The functions in this class should be called from a loader-specific class.
 | 
			
		||||
 *
 | 
			
		||||
 * @see ModRegistry The common registry for actual game objects.
 | 
			
		||||
 */
 | 
			
		||||
public final class ClientRegistry {
 | 
			
		||||
    private ClientRegistry() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register any client-side objects which don't have to be done on the main thread.
 | 
			
		||||
     */
 | 
			
		||||
    public static void register() {
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_left"),
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_speaker_right")
 | 
			
		||||
        ));
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_left"),
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_crafting_table_right")
 | 
			
		||||
        ));
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
 | 
			
		||||
        ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Register any client-side objects which must be done on the main thread.
 | 
			
		||||
     */
 | 
			
		||||
    public static void registerMainThread() {
 | 
			
		||||
        MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
 | 
			
		||||
        MenuScreens.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), ComputerScreen::new);
 | 
			
		||||
        MenuScreens.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
 | 
			
		||||
        MenuScreens.register(ModRegistry.Menus.TURTLE.get(), TurtleScreen::new);
 | 
			
		||||
 | 
			
		||||
        MenuScreens.register(ModRegistry.Menus.PRINTER.get(), PrinterScreen::new);
 | 
			
		||||
        MenuScreens.register(ModRegistry.Menus.DISK_DRIVE.get(), DiskDriveScreen::new);
 | 
			
		||||
        MenuScreens.register(ModRegistry.Menus.PRINTOUT.get(), PrintoutScreen::new);
 | 
			
		||||
 | 
			
		||||
        MenuScreens.<ViewComputerMenu, ComputerScreen<ViewComputerMenu>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), ComputerScreen::new);
 | 
			
		||||
 | 
			
		||||
        registerItemProperty("state",
 | 
			
		||||
            new UnclampedPropertyFunction((stack, world, player, random) -> ClientPocketComputers.get(stack).getState().ordinal()),
 | 
			
		||||
            ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
 | 
			
		||||
        );
 | 
			
		||||
        registerItemProperty("coloured",
 | 
			
		||||
            (stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
 | 
			
		||||
            ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SafeVarargs
 | 
			
		||||
    private static void registerItemProperty(String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
 | 
			
		||||
        var id = new ResourceLocation(ComputerCraftAPI.MOD_ID, name);
 | 
			
		||||
        for (var item : items) ItemProperties.register(item.get(), id, getter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final String[] EXTRA_MODELS = new String[]{
 | 
			
		||||
        // Turtle upgrades
 | 
			
		||||
        "block/turtle_modem_normal_off_left",
 | 
			
		||||
        "block/turtle_modem_normal_on_left",
 | 
			
		||||
        "block/turtle_modem_normal_off_right",
 | 
			
		||||
        "block/turtle_modem_normal_on_right",
 | 
			
		||||
 | 
			
		||||
        "block/turtle_modem_advanced_off_left",
 | 
			
		||||
        "block/turtle_modem_advanced_on_left",
 | 
			
		||||
        "block/turtle_modem_advanced_off_right",
 | 
			
		||||
        "block/turtle_modem_advanced_on_right",
 | 
			
		||||
 | 
			
		||||
        "block/turtle_crafting_table_left",
 | 
			
		||||
        "block/turtle_crafting_table_right",
 | 
			
		||||
 | 
			
		||||
        "block/turtle_speaker_left",
 | 
			
		||||
        "block/turtle_speaker_right",
 | 
			
		||||
 | 
			
		||||
        // Turtle block renderer
 | 
			
		||||
        "block/turtle_colour",
 | 
			
		||||
        "block/turtle_elf_overlay",
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    public static void registerExtraModels(Consumer<ResourceLocation> register) {
 | 
			
		||||
        for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
 | 
			
		||||
        register.accept(
 | 
			
		||||
            (stack, layer) -> layer == 1 ? ((DiskItem) stack.getItem()).getColour(stack) : 0xFFFFFF,
 | 
			
		||||
            ModRegistry.Items.DISK.get()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        register.accept(
 | 
			
		||||
            (stack, layer) -> layer == 1 ? TreasureDiskItem.getColour(stack) : 0xFFFFFF,
 | 
			
		||||
            ModRegistry.Items.TREASURE_DISK.get()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        register.accept(ClientRegistry::getPocketColour, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get());
 | 
			
		||||
        register.accept(ClientRegistry::getPocketColour, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
 | 
			
		||||
 | 
			
		||||
        register.accept(ClientRegistry::getTurtleColour, ModRegistry.Blocks.TURTLE_NORMAL.get());
 | 
			
		||||
        register.accept(ClientRegistry::getTurtleColour, ModRegistry.Blocks.TURTLE_ADVANCED.get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getPocketColour(ItemStack stack, int layer) {
 | 
			
		||||
        switch (layer) {
 | 
			
		||||
            case 0:
 | 
			
		||||
            default:
 | 
			
		||||
                return 0xFFFFFF;
 | 
			
		||||
            case 1: // Frame colour
 | 
			
		||||
                return IColouredItem.getColourBasic(stack);
 | 
			
		||||
            case 2: { // Light colour
 | 
			
		||||
                var light = ClientPocketComputers.get(stack).getLightState();
 | 
			
		||||
                return light == -1 ? Colour.BLACK.getHex() : light;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int getTurtleColour(ItemStack stack, int layer) {
 | 
			
		||||
        return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerBlockEntityRenderers(BlockEntityRenderRegistry register) {
 | 
			
		||||
        register.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), MonitorBlockEntityRenderer::new);
 | 
			
		||||
        register.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
 | 
			
		||||
        register.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
 | 
			
		||||
        register.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), TurtleBlockEntityRenderer::new);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public interface BlockEntityRenderRegistry {
 | 
			
		||||
        <T extends BlockEntity> void register(BlockEntityType<? extends T> type, BlockEntityRendererProvider<T> provider);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
 | 
			
		||||
        RenderTypes.registerShaders(resources, load);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private record UnclampedPropertyFunction(
 | 
			
		||||
        ClampedItemPropertyFunction function
 | 
			
		||||
    ) implements ClampedItemPropertyFunction {
 | 
			
		||||
        @Override
 | 
			
		||||
        public float unclampedCall(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int layer) {
 | 
			
		||||
            return function.unclampedCall(stack, level, entity, layer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Deprecated
 | 
			
		||||
        @Override
 | 
			
		||||
        public float call(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int layer) {
 | 
			
		||||
            return function.unclampedCall(stack, level, entity, layer);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,81 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.shared.command.text.ChatHelpers;
 | 
			
		||||
import dan200.computercraft.shared.command.text.TableBuilder;
 | 
			
		||||
import dan200.computercraft.shared.command.text.TableFormatter;
 | 
			
		||||
import net.minecraft.ChatFormatting;
 | 
			
		||||
import net.minecraft.client.GuiMessageTag;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.Font;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.util.Mth;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
public class ClientTableFormatter implements TableFormatter {
 | 
			
		||||
    public static final ClientTableFormatter INSTANCE = new ClientTableFormatter();
 | 
			
		||||
 | 
			
		||||
    private static Font renderer() {
 | 
			
		||||
        return Minecraft.getInstance().font;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Component getPadding(Component component, int width) {
 | 
			
		||||
        var extraWidth = width - getWidth(component);
 | 
			
		||||
        if (extraWidth <= 0) return null;
 | 
			
		||||
 | 
			
		||||
        var renderer = renderer();
 | 
			
		||||
 | 
			
		||||
        float spaceWidth = renderer.width(" ");
 | 
			
		||||
        var spaces = Mth.floor(extraWidth / spaceWidth);
 | 
			
		||||
        var extra = extraWidth - (int) (spaces * spaceWidth);
 | 
			
		||||
 | 
			
		||||
        return ChatHelpers.coloured(StringUtils.repeat(' ', spaces) + StringUtils.repeat((char) 712, extra), ChatFormatting.GRAY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getColumnPadding() {
 | 
			
		||||
        return 3;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getWidth(Component component) {
 | 
			
		||||
        return renderer().width(component);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void writeLine(String label, Component component) {
 | 
			
		||||
        var mc = Minecraft.getInstance();
 | 
			
		||||
        var chat = mc.gui.getChat();
 | 
			
		||||
 | 
			
		||||
        // TODO: Trim the text if it goes over the allowed length
 | 
			
		||||
        // int maxWidth = MathHelper.floor( chat.getChatWidth() / chat.getScale() );
 | 
			
		||||
        // List<ITextProperties> list = RenderComponentsUtil.wrapComponents( component, maxWidth, mc.fontRenderer );
 | 
			
		||||
        // if( !list.isEmpty() ) chat.printChatMessageWithOptionalDeletion( list.get( 0 ), id );
 | 
			
		||||
        chat.addMessage(component, null, createTag(label));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void display(TableBuilder table) {
 | 
			
		||||
        var chat = Minecraft.getInstance().gui.getChat();
 | 
			
		||||
 | 
			
		||||
        var tag = createTag(table.getId());
 | 
			
		||||
        if (chat.allMessages.removeIf(guiMessage -> guiMessage.tag() != null && Objects.equals(guiMessage.tag().logTag(), tag.logTag()))) {
 | 
			
		||||
            chat.refreshTrimmedMessage();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        TableFormatter.super.display(table);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static GuiMessageTag createTag(String id) {
 | 
			
		||||
        return new GuiMessageTag(0xa0a0a0, null, null, "ComputerCraft/" + id);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.client;
 | 
			
		||||
 | 
			
		||||
import com.google.auto.service.AutoService;
 | 
			
		||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
 | 
			
		||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
 | 
			
		||||
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
 | 
			
		||||
 | 
			
		||||
@AutoService(ComputerCraftAPIClientService.class)
 | 
			
		||||
public final class ComputerCraftAPIClientImpl implements ComputerCraftAPIClientService {
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(TurtleUpgradeSerialiser<T> serialiser, TurtleUpgradeModeller<T> modeller) {
 | 
			
		||||
        TurtleUpgradeModellers.register(serialiser, modeller);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.client;
 | 
			
		||||
 | 
			
		||||
public final class FrameInfo {
 | 
			
		||||
    private static int tick;
 | 
			
		||||
    private static long renderFrame;
 | 
			
		||||
 | 
			
		||||
    private FrameInfo() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean getGlobalCursorBlink() {
 | 
			
		||||
        return (tick / 8) % 2 == 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static long getRenderFrame() {
 | 
			
		||||
        return renderFrame;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void onTick() {
 | 
			
		||||
        tick++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void onRenderTick() {
 | 
			
		||||
        renderFrame++;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,219 @@
 | 
			
		||||
/*
 | 
			
		||||
 * This file is part of ComputerCraft - http://www.computercraft.info
 | 
			
		||||
 * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
 | 
			
		||||
 * Send enquiries to dratcliffe@gmail.com
 | 
			
		||||
 */
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
 | 
			
		||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
			
		||||
import dan200.computercraft.core.terminal.Terminal;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.InputHandler;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.computer.upload.FileUpload;
 | 
			
		||||
import dan200.computercraft.shared.computer.upload.UploadResult;
 | 
			
		||||
import dan200.computercraft.shared.config.Config;
 | 
			
		||||
import dan200.computercraft.shared.network.server.UploadFileMessage;
 | 
			
		||||
import net.minecraft.ChatFormatting;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.lwjgl.glfw.GLFW;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
 | 
			
		||||
 | 
			
		||||
public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> extends AbstractContainerScreen<T> {
 | 
			
		||||
    private static final Logger LOG = LoggerFactory.getLogger(AbstractComputerScreen.class);
 | 
			
		||||
 | 
			
		||||
    private static final Component OK = Component.translatable("gui.ok");
 | 
			
		||||
    private static final Component NO_RESPONSE_TITLE = Component.translatable("gui.computercraft.upload.no_response");
 | 
			
		||||
    private static final Component NO_RESPONSE_MSG = Component.translatable("gui.computercraft.upload.no_response.msg",
 | 
			
		||||
        Component.literal("import").withStyle(ChatFormatting.DARK_GRAY));
 | 
			
		||||
 | 
			
		||||
    protected @Nullable TerminalWidget terminal;
 | 
			
		||||
    protected Terminal terminalData;
 | 
			
		||||
    protected final ComputerFamily family;
 | 
			
		||||
    protected final InputHandler input;
 | 
			
		||||
 | 
			
		||||
    protected final int sidebarYOffset;
 | 
			
		||||
 | 
			
		||||
    private long uploadNagDeadline = Long.MAX_VALUE;
 | 
			
		||||
    private final ItemStack displayStack;
 | 
			
		||||
 | 
			
		||||
    public AbstractComputerScreen(T container, Inventory player, Component title, int sidebarYOffset) {
 | 
			
		||||
        super(container, player, title);
 | 
			
		||||
        terminalData = container.getTerminal();
 | 
			
		||||
        family = container.getFamily();
 | 
			
		||||
        displayStack = container.getDisplayStack();
 | 
			
		||||
        input = new ClientInputHandler(menu);
 | 
			
		||||
        this.sidebarYOffset = sidebarYOffset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected abstract TerminalWidget createTerminal();
 | 
			
		||||
 | 
			
		||||
    protected final TerminalWidget getTerminal() {
 | 
			
		||||
        if (terminal == null) throw new IllegalStateException("Screen has not been initialised yet");
 | 
			
		||||
        return terminal;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void init() {
 | 
			
		||||
        super.init();
 | 
			
		||||
 | 
			
		||||
        terminal = addRenderableWidget(createTerminal());
 | 
			
		||||
        ComputerSidebar.addButtons(menu::isOn, input, this::addRenderableWidget, leftPos, topPos + sidebarYOffset);
 | 
			
		||||
        setFocused(terminal);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void containerTick() {
 | 
			
		||||
        super.containerTick();
 | 
			
		||||
        getTerminal().update();
 | 
			
		||||
 | 
			
		||||
        if (uploadNagDeadline != Long.MAX_VALUE && Util.getNanos() >= uploadNagDeadline) {
 | 
			
		||||
            new ItemToast(minecraft, displayStack, NO_RESPONSE_TITLE, NO_RESPONSE_MSG, ItemToast.TRANSFER_NO_RESPONSE_TOKEN)
 | 
			
		||||
                .showOrReplace(minecraft.getToasts());
 | 
			
		||||
            uploadNagDeadline = Long.MAX_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean keyPressed(int key, int scancode, int modifiers) {
 | 
			
		||||
        // Forward the tab key to the terminal, rather than moving between controls.
 | 
			
		||||
        if (key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal) {
 | 
			
		||||
            return getFocused().keyPressed(key, scancode, modifiers);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return super.keyPressed(key, scancode, modifiers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mouseReleased(double x, double y, int button) {
 | 
			
		||||
        // Reimplement ContainerEventHandler.mouseReleased, as it's not called in vanilla (it is in Forge, but that
 | 
			
		||||
        // shouldn't matter).
 | 
			
		||||
        setDragging(false);
 | 
			
		||||
        var child = getChildAt(x, y);
 | 
			
		||||
        if (child.isPresent() && child.get().mouseReleased(x, y, button)) return true;
 | 
			
		||||
 | 
			
		||||
        return super.mouseReleased(x, y, button);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(PoseStack stack, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        renderBackground(stack);
 | 
			
		||||
        super.render(stack, mouseX, mouseY, partialTicks);
 | 
			
		||||
        renderTooltip(stack, mouseX, mouseY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mouseClicked(double x, double y, int button) {
 | 
			
		||||
        var changed = super.mouseClicked(x, y, button);
 | 
			
		||||
        // Clicking the terminate/shutdown button steals focus, which means then pressing "enter" will click the button
 | 
			
		||||
        // again. Restore the focus to the terminal in these cases.
 | 
			
		||||
        if (getFocused() instanceof DynamicImageButton) setFocused(terminal);
 | 
			
		||||
        return changed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mouseDragged(double x, double y, int button, double deltaX, double deltaY) {
 | 
			
		||||
        return (getFocused() != null && getFocused().mouseDragged(x, y, button, deltaX, deltaY))
 | 
			
		||||
            || super.mouseDragged(x, y, button, deltaX, deltaY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void renderLabels(PoseStack transform, int mouseX, int mouseY) {
 | 
			
		||||
        // Skip rendering labels.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onFilesDrop(List<Path> files) {
 | 
			
		||||
        if (files.isEmpty()) return;
 | 
			
		||||
 | 
			
		||||
        if (!menu.isOn()) {
 | 
			
		||||
            alert(UploadResult.FAILED_TITLE, UploadResult.COMPUTER_OFF_MSG);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        long size = 0;
 | 
			
		||||
 | 
			
		||||
        List<FileUpload> toUpload = new ArrayList<>();
 | 
			
		||||
        for (var file : files) {
 | 
			
		||||
            // TODO: Recurse directories? If so, we probably want to shunt this off-thread.
 | 
			
		||||
            if (!Files.isRegularFile(file)) continue;
 | 
			
		||||
 | 
			
		||||
            try (var sbc = Files.newByteChannel(file)) {
 | 
			
		||||
                var fileSize = sbc.size();
 | 
			
		||||
                if (fileSize > UploadFileMessage.MAX_SIZE || (size += fileSize) >= UploadFileMessage.MAX_SIZE) {
 | 
			
		||||
                    alert(UploadResult.FAILED_TITLE, UploadResult.TOO_MUCH_MSG);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var name = file.getFileName().toString();
 | 
			
		||||
                if (name.length() > UploadFileMessage.MAX_FILE_NAME) {
 | 
			
		||||
                    alert(UploadResult.FAILED_TITLE, Component.translatable("gui.computercraft.upload.failed.name_too_long"));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var buffer = ByteBuffer.allocateDirect((int) fileSize);
 | 
			
		||||
                sbc.read(buffer);
 | 
			
		||||
                buffer.flip();
 | 
			
		||||
 | 
			
		||||
                var digest = FileUpload.getDigest(buffer);
 | 
			
		||||
                if (digest == null) {
 | 
			
		||||
                    alert(UploadResult.FAILED_TITLE, Component.translatable("gui.computercraft.upload.failed.corrupted"));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                toUpload.add(new FileUpload(name, buffer, digest));
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                LOG.error("Failed uploading files", e);
 | 
			
		||||
                alert(UploadResult.FAILED_TITLE, Component.translatable("gui.computercraft.upload.failed.generic", "Cannot compute checksum"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (toUpload.size() > UploadFileMessage.MAX_FILES) {
 | 
			
		||||
            alert(UploadResult.FAILED_TITLE, Component.translatable("gui.computercraft.upload.failed.too_many_files"));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientPlatformHelper.get()::sendToServer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void uploadResult(UploadResult result, @Nullable Component message) {
 | 
			
		||||
        switch (result) {
 | 
			
		||||
            case QUEUED -> {
 | 
			
		||||
                if (Config.uploadNagDelay > 0) {
 | 
			
		||||
                    uploadNagDeadline = Util.getNanos() + TimeUnit.SECONDS.toNanos(Config.uploadNagDelay);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            case CONSUMED -> uploadNagDeadline = Long.MAX_VALUE;
 | 
			
		||||
            case ERROR -> alert(UploadResult.FAILED_TITLE, assertNonNull(message));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void alert(Component title, Component message) {
 | 
			
		||||
        OptionScreen.show(minecraft, title, message,
 | 
			
		||||
            Collections.singletonList(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
 | 
			
		||||
            () -> minecraft.setScreen(this)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user