mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-03 23:22:59 +00:00 
			
		
		
		
	Compare commits
	
		
			135 Commits
		
	
	
		
			v1.20.1-1.
			...
			v1.20.1-1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3ebdf7ef5e | ||
| 
						 | 
					905d4cb091 | ||
| 
						 | 
					e7ab05d064 | ||
| 
						 | 
					6ec34b42e5 | ||
| 
						 | 
					ab785a0906 | ||
| 
						 | 
					4541decd40 | ||
| 
						 | 
					747a5a53b4 | ||
| 
						 | 
					c0643fadca | ||
| 
						 | 
					0a31de43c2 | ||
| 
						 | 
					96b6947ef2 | ||
| 
						 | 
					e7a1065bfc | ||
| 
						 | 
					663eecff0c | ||
| 
						 | 
					e6125bcf60 | ||
| 
						 | 
					0d6c6e7ae7 | ||
| 
						 | 
					ae71eb3cae | ||
| 
						 | 
					3188197447 | ||
| 
						 | 
					6c8b391dab | ||
| 
						 | 
					b1248e4901 | ||
| 
						 | 
					56d97630e8 | ||
| 
						 | 
					e660192f08 | ||
| 
						 | 
					4e82bd352d | ||
| 
						 | 
					07113c3e9b | ||
| 
						 | 
					d562a051c7 | ||
| 
						 | 
					6ac09742fc | ||
| 
						 | 
					5dd6b9a637 | ||
| 
						 | 
					8fb1dd346c | ||
| 
						 | 
					cdc8592aa3 | ||
| 
						 | 
					0f6ea3deaf | ||
| 
						 | 
					13ed422bd5 | ||
| 
						 | 
					5b58271b92 | ||
| 
						 | 
					4e42394f33 | ||
| 
						 | 
					53546b9f57 | ||
| 
						 | 
					6dfdeb9321 | ||
| 
						 | 
					500406f9eb | ||
| 
						 | 
					b3738a7a63 | ||
| 
						 | 
					5f8b1dd67f | ||
| 
						 | 
					053751b190 | ||
| 
						 | 
					52b78f92cd | ||
| 
						 | 
					2055052a57 | ||
| 
						 | 
					12ee47ff19 | ||
| 
						 | 
					25776abf61 | ||
| 
						 | 
					f7411b40a2 | ||
| 
						 | 
					8c8924f54e | ||
| 
						 | 
					c1628d077a | ||
| 
						 | 
					5b2fdec6ca | ||
| 
						 | 
					5a7259e4c9 | ||
| 
						 | 
					92b335f45f | ||
| 
						 | 
					b93ea9c62e | ||
| 
						 | 
					b9edd7c7f6 | ||
| 
						 | 
					84a761ddd5 | ||
| 
						 | 
					3371c4651c | ||
| 
						 | 
					2a04fb71fd | ||
| 
						 | 
					df61389304 | ||
| 
						 | 
					e6bc1e4e27 | ||
| 
						 | 
					b6632c9ed9 | ||
| 
						 | 
					41b6711b38 | ||
| 
						 | 
					02f0b7ec14 | ||
| 
						 | 
					a9d31cd3c6 | ||
| 
						 | 
					43744b0e85 | ||
| 
						 | 
					55510c42db | ||
| 
						 | 
					57a944fd90 | ||
| 
						 | 
					940f59b116 | ||
| 
						 | 
					dd08d1ec8e | ||
| 
						 | 
					90ed0b24e7 | ||
| 
						 | 
					0ad399a528 | ||
| 
						 | 
					1b88213eca | ||
| 
						 | 
					eef05b9854 | ||
| 
						 | 
					24d74f5c80 | ||
| 
						 | 
					ae50f900af | ||
| 
						 | 
					48889ceb89 | ||
| 
						 | 
					c2988366d8 | ||
| 
						 | 
					ec0765ead1 | ||
| 
						 | 
					b94e34f372 | ||
| 
						 | 
					af3263dec2 | ||
| 
						 | 
					7f25c9a66b | ||
| 
						 | 
					9ca3efff3c | ||
| 
						 | 
					8f1bf4341c | ||
| 
						 | 
					aaf8c248a8 | ||
| 
						 | 
					df26cd267a | ||
| 
						 | 
					8914b78816 | ||
| 
						 | 
					9a48b53a83 | ||
| 
						 | 
					9519448e43 | ||
| 
						 | 
					9ea7f45fa7 | ||
| 
						 | 
					915b6f9d81 | ||
| 
						 | 
					a98f3b2a4c | ||
| 
						 | 
					d351bc33c6 | ||
| 
						 | 
					5d71770931 | ||
| 
						 | 
					4bbde8c50c | ||
| 
						 | 
					cc8c1f38e7 | ||
| 
						 | 
					cab9c9772a | ||
| 
						 | 
					e337a63712 | ||
| 
						 | 
					efa92b741b | ||
| 
						 | 
					a91ac6f214 | ||
| 
						 | 
					943a9406b1 | ||
| 
						 | 
					0b2bb5e7b5 | ||
| 
						 | 
					8708048b6e | ||
| 
						 | 
					d138d9c4a5 | ||
| 
						 | 
					f54cb8a432 | ||
| 
						 | 
					94f5ede75a | ||
| 
						 | 
					1977556da4 | ||
| 
						 | 
					9eabb29999 | ||
| 
						 | 
					ecf880ed82 | ||
| 
						 | 
					655d5aeca8 | ||
| 
						 | 
					34f41c4039 | ||
| 
						 | 
					f5b16261cc | ||
| 
						 | 
					7eb3b691da | ||
| 
						 | 
					910a63214e | ||
| 
						 | 
					591a7eca23 | ||
| 
						 | 
					a29a516a3f | ||
| 
						 | 
					4a5e03c11a | ||
| 
						 | 
					50d460624f | ||
| 
						 | 
					bc500df921 | ||
| 
						 | 
					4accda6b8e | ||
| 
						 | 
					54ab98473f | ||
| 
						 | 
					7ffdbb2316 | ||
| 
						 | 
					672c2cf029 | ||
| 
						 | 
					c3bdb0440e | ||
| 
						 | 
					88f0c44152 | ||
| 
						 | 
					ebaf49508f | ||
| 
						 | 
					c8523bf479 | ||
| 
						 | 
					953372b1b7 | ||
| 
						 | 
					36b9f4ec55 | ||
| 
						 | 
					ccfed0059b | ||
| 
						 | 
					c45fc94752 | ||
| 
						 | 
					7b4ba11fb4 | ||
| 
						 | 
					8ccd5a560c | ||
| 
						 | 
					0f866836a0 | ||
| 
						 | 
					df591cd7c6 | ||
| 
						 | 
					c7f3d4f45d | ||
| 
						 | 
					77ac04cb7a | ||
| 
						 | 
					fd1f6dda32 | ||
| 
						 | 
					201df7e987 | ||
| 
						 | 
					5722e51735 | ||
| 
						 | 
					7a291619ab | ||
| 
						 | 
					4b9b19b02d | 
							
								
								
									
										44
									
								
								.github/workflows/main-ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										44
									
								
								.github/workflows/main-ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -8,16 +8,16 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Clone repository
 | 
			
		||||
    - name: 📥 Clone repository
 | 
			
		||||
      uses: actions/checkout@v3
 | 
			
		||||
 | 
			
		||||
    - name: Set up Java
 | 
			
		||||
    - name: 📥 Set up Java
 | 
			
		||||
      uses: actions/setup-java@v3
 | 
			
		||||
      with:
 | 
			
		||||
        java-version: 17
 | 
			
		||||
        distribution: 'temurin'
 | 
			
		||||
 | 
			
		||||
    - name: Setup Gradle
 | 
			
		||||
    - name: 📥 Setup Gradle
 | 
			
		||||
      uses: gradle/gradle-build-action@v2
 | 
			
		||||
      with:
 | 
			
		||||
        cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
 | 
			
		||||
@@ -27,39 +27,45 @@ jobs:
 | 
			
		||||
        mkdir -p ~/.gradle
 | 
			
		||||
        echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
 | 
			
		||||
 | 
			
		||||
    - name: Build with Gradle
 | 
			
		||||
      run: |
 | 
			
		||||
        ./gradlew assemble || ./gradlew assemble
 | 
			
		||||
        ./gradlew downloadAssets || ./gradlew downloadAssets
 | 
			
		||||
        ./gradlew build
 | 
			
		||||
    - name: ⚒️ Build
 | 
			
		||||
      run: ./gradlew assemble || ./gradlew assemble
 | 
			
		||||
 | 
			
		||||
    - name: Run client tests
 | 
			
		||||
    - name: 💡 Lint
 | 
			
		||||
      uses: pre-commit/action@v3.0.0
 | 
			
		||||
 | 
			
		||||
    - name: 🧪 Run tests
 | 
			
		||||
      run: ./gradlew test validateMixinNames checkChangelog
 | 
			
		||||
 | 
			
		||||
    - name: 📥 Download assets for game tests
 | 
			
		||||
      run: ./gradlew downloadAssets || ./gradlew downloadAssets
 | 
			
		||||
 | 
			
		||||
    - name: 🧪 Run integration tests
 | 
			
		||||
      run: ./gradlew runGametest
 | 
			
		||||
 | 
			
		||||
    - 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
 | 
			
		||||
    - name: 🧪 Parse test reports
 | 
			
		||||
      run: ./tools/parse-reports.py
 | 
			
		||||
      if: ${{ failure() }}
 | 
			
		||||
 | 
			
		||||
    - 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
 | 
			
		||||
    - name: 📤 Upload Jar
 | 
			
		||||
      uses: actions/upload-artifact@v3
 | 
			
		||||
      with:
 | 
			
		||||
        name: CC-Tweaked
 | 
			
		||||
        path: ./jars
 | 
			
		||||
 | 
			
		||||
    - name: Upload coverage
 | 
			
		||||
    - name: 📤 Upload coverage
 | 
			
		||||
      uses: codecov/codecov-action@v3
 | 
			
		||||
 | 
			
		||||
    - name: Parse test reports
 | 
			
		||||
      run: ./tools/parse-reports.py
 | 
			
		||||
      if: ${{ failure() }}
 | 
			
		||||
 | 
			
		||||
    - name: Run linters
 | 
			
		||||
      uses: pre-commit/action@v3.0.0
 | 
			
		||||
 | 
			
		||||
  build-core:
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/make-doc.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/make-doc.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,7 @@ on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
    - mc-1.19.x
 | 
			
		||||
    - mc-1.20.x
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  make_doc:
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
			
		||||
repos:
 | 
			
		||||
- repo: https://github.com/pre-commit/pre-commit-hooks
 | 
			
		||||
  rev: v4.0.1
 | 
			
		||||
  rev: v4.4.0
 | 
			
		||||
  hooks:
 | 
			
		||||
  - id: trailing-whitespace
 | 
			
		||||
  - id: end-of-file-fixer
 | 
			
		||||
@@ -20,14 +20,14 @@ repos:
 | 
			
		||||
    exclude: "tsconfig\\.json$"
 | 
			
		||||
 | 
			
		||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
 | 
			
		||||
  rev: 2.3.54
 | 
			
		||||
  rev: 2.7.2
 | 
			
		||||
  hooks:
 | 
			
		||||
  - id: editorconfig-checker
 | 
			
		||||
    args: ['-disable-indentation']
 | 
			
		||||
    exclude: "^(.*\\.(bat)|LICENSE)$"
 | 
			
		||||
 | 
			
		||||
- repo: https://github.com/fsfe/reuse-tool
 | 
			
		||||
  rev: v1.1.0
 | 
			
		||||
  rev: v2.1.0
 | 
			
		||||
  hooks:
 | 
			
		||||
  - id: reuse
 | 
			
		||||
 | 
			
		||||
@@ -44,7 +44,7 @@ repos:
 | 
			
		||||
    name: Check Java codestyle
 | 
			
		||||
    files: ".*\\.java$"
 | 
			
		||||
    language: system
 | 
			
		||||
    entry: ./gradlew checkstyleMain checkstyleTest
 | 
			
		||||
    entry: ./gradlew checkstyle
 | 
			
		||||
    pass_filenames: false
 | 
			
		||||
    require_serial: true
 | 
			
		||||
  - id: illuaminate
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								.reuse/dep5
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								.reuse/dep5
									
									
									
									
									
								
							@@ -6,11 +6,12 @@ Upstream-Contact: Jonathan Coates <git@squiddev.cc>
 | 
			
		||||
Files:
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/sounds.json
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg
 | 
			
		||||
  projects/common/src/testMod/resources/data/cctest/computercraft/turtle_upgrades/*
 | 
			
		||||
  projects/common/src/testMod/resources/data/cctest/structures/*
 | 
			
		||||
  projects/fabric/src/generated/*
 | 
			
		||||
  projects/forge/src/generated/*
 | 
			
		||||
  projects/web/src/export/index.json
 | 
			
		||||
  projects/web/src/export/items/minecraft/*
 | 
			
		||||
  projects/web/src/htmlTransform/export/index.json
 | 
			
		||||
  projects/web/src/htmlTransform/export/items/minecraft/*
 | 
			
		||||
Comment: Generated/data files are CC0.
 | 
			
		||||
Copyright: The CC: Tweaked Developers
 | 
			
		||||
License: CC0-1.0
 | 
			
		||||
@@ -36,10 +37,10 @@ Files:
 | 
			
		||||
  projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json
 | 
			
		||||
  projects/fabric/src/testMod/resources/fabric.mod.json
 | 
			
		||||
  projects/forge/src/client/resources/computercraft-client.forge.mixins.json
 | 
			
		||||
  projects/web/src/mount/.settings
 | 
			
		||||
  projects/web/src/mount/example.nfp
 | 
			
		||||
  projects/web/src/mount/example.nft
 | 
			
		||||
  projects/web/src/mount/expr_template.lua
 | 
			
		||||
  projects/web/src/frontend/mount/.settings
 | 
			
		||||
  projects/web/src/frontend/mount/example.nfp
 | 
			
		||||
  projects/web/src/frontend/mount/example.nft
 | 
			
		||||
  projects/web/src/frontend/mount/expr_template.lua
 | 
			
		||||
  projects/web/tsconfig.json
 | 
			
		||||
Comment: Several assets where it's inconvenient to create a .license file.
 | 
			
		||||
Copyright: The CC: Tweaked Developers
 | 
			
		||||
@@ -47,6 +48,7 @@ License: MPL-2.0
 | 
			
		||||
 | 
			
		||||
Files:
 | 
			
		||||
  doc/logo.png
 | 
			
		||||
  doc/logo-darkmode.png
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/models/*
 | 
			
		||||
  projects/common/src/main/resources/assets/computercraft/textures/*
 | 
			
		||||
  projects/common/src/main/resources/pack.mcmeta
 | 
			
		||||
@@ -54,7 +56,7 @@ Files:
 | 
			
		||||
  projects/core/src/main/resources/data/computercraft/lua/rom/autorun/.ignoreme
 | 
			
		||||
  projects/core/src/main/resources/data/computercraft/lua/rom/help/*
 | 
			
		||||
  projects/core/src/main/resources/data/computercraft/lua/rom/programs/fun/advanced/levels/*
 | 
			
		||||
  projects/web/src/export/items/computercraft/*
 | 
			
		||||
  projects/web/src/htmlTransform/export/items/computercraft/*
 | 
			
		||||
Comment: Bulk-license original assets as CCPL.
 | 
			
		||||
Copyright: 2011 Daniel Ratcliffe
 | 
			
		||||
License: LicenseRef-CCPL
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
# Contributing to CC: Tweaked
 | 
			
		||||
As with many open source projects, CC: Tweaked thrives on contributions from other people! This document (hopefully)
 | 
			
		||||
provides an introduction as to how to get started in helping out.
 | 
			
		||||
provides an introduction as to how to get started with helping out.
 | 
			
		||||
 | 
			
		||||
If you've any other questions, [just ask the community][community] or [open an issue][new-issue].
 | 
			
		||||
 | 
			
		||||
@@ -28,7 +28,7 @@ automatically with GitHub, so please don't submit PRs adding/changing translatio
 | 
			
		||||
## Setting up a development environment
 | 
			
		||||
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
 | 
			
		||||
 | 
			
		||||
 - Make sure you've got the following software instealled:
 | 
			
		||||
 - Make sure you've got the following software installed:
 | 
			
		||||
   - 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].
 | 
			
		||||
@@ -51,10 +51,10 @@ If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble`
 | 
			
		||||
## 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]!
 | 
			
		||||
looking to make your changes. As always, if you're not sure, [do ask the community][community]!
 | 
			
		||||
 | 
			
		||||
### Testing
 | 
			
		||||
When making larger changes, it's may be useful to write a test to make sure your code works as expected.
 | 
			
		||||
When making larger changes, it 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:
 | 
			
		||||
 | 
			
		||||
@@ -91,16 +91,15 @@ file.
 | 
			
		||||
 | 
			
		||||
Documentation is built using [illuaminate] which, while not currently documented (somewhat ironic), is largely the same
 | 
			
		||||
as [ldoc][ldoc]. Documentation comments are written in Markdown, though note that we do not support many GitHub-specific
 | 
			
		||||
markdown features - if you can, do check what the documentation looks like locally!
 | 
			
		||||
markdown features. If you can, do check what the documentation looks like locally!
 | 
			
		||||
 | 
			
		||||
When writing long-form documentation (such as the guides in [doc/guides](doc/guides)), I find it useful to tell a
 | 
			
		||||
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept and then talk
 | 
			
		||||
about how you can build on that, until you've covered everything!
 | 
			
		||||
narrative. Think of what you want the user to learn or achieve, then start introducing a simple concept, and then talk
 | 
			
		||||
about how you can build on that until you've covered everything!
 | 
			
		||||
 | 
			
		||||
[new-issue]: https://github.com/cc-tweaked/CC-Tweaked/issues/new/choose "Create a new issue"
 | 
			
		||||
[community]: README.md#community "Get in touch with the community."
 | 
			
		||||
[Adoptium]: https://adoptium.net/temurin/releases?version=17 "Download OpenJDK 17"
 | 
			
		||||
[checkstyle]: https://checkstyle.org/
 | 
			
		||||
[illuaminate]: https://github.com/SquidDev/illuaminate/ "Illuaminate on GitHub"
 | 
			
		||||
[weblate]: https://i18n.tweaked.cc/projects/cc-tweaked/minecraft/ "CC: Tweaked weblate instance"
 | 
			
		||||
[docs]: https://tweaked.cc/ "CC: Tweaked documentation"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								README.md
									
									
									
									
									
								
							@@ -4,7 +4,12 @@ SPDX-FileCopyrightText: 2017 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
# 
 | 
			
		||||
<picture>
 | 
			
		||||
  <source media="(prefers-color-scheme: dark)" srcset="./doc/logo-darkmode.png">
 | 
			
		||||
  <source media="(prefers-color-scheme: light)" srcset="./doc/logo.png">
 | 
			
		||||
  <img alt="CC: Tweaked" src="./doc/logo.png">
 | 
			
		||||
</picture>
 | 
			
		||||
 | 
			
		||||
[](https://github.com/cc-tweaked/CC-Tweaked/actions "Current build status")
 | 
			
		||||
[][CurseForge]
 | 
			
		||||
[][Modrinth]
 | 
			
		||||
@@ -44,7 +49,7 @@ repositories {
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  // Vanilla (i.e. for multi-loader systems)
 | 
			
		||||
  compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
 | 
			
		||||
  compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
 | 
			
		||||
 | 
			
		||||
  // Forge Gradle
 | 
			
		||||
  compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
 | 
			
		||||
@@ -57,6 +62,19 @@ dependencies {
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
When using ForgeGradle, you may also need to add the following:
 | 
			
		||||
 | 
			
		||||
```groovy
 | 
			
		||||
minecraft {
 | 
			
		||||
    runs {
 | 
			
		||||
        configureEach {
 | 
			
		||||
            property 'mixin.env.remapRefMap', 'true'
 | 
			
		||||
            property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
 | 
			
		||||
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
 | 
			
		||||
exposing more features.
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@ dependencies {
 | 
			
		||||
    implementation(libs.forgeGradle)
 | 
			
		||||
    implementation(libs.librarian)
 | 
			
		||||
    implementation(libs.minotaur)
 | 
			
		||||
    implementation(libs.quiltflower)
 | 
			
		||||
    implementation(libs.vineflower)
 | 
			
		||||
    implementation(libs.vanillaGradle)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
plugins {
 | 
			
		||||
    `java-library`
 | 
			
		||||
    id("fabric-loom")
 | 
			
		||||
    id("io.github.juuxel.loom-quiltflower")
 | 
			
		||||
    id("io.github.juuxel.loom-vineflower")
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,9 @@ import cc.tweaked.gradle.IdeaRunConfigurations
 | 
			
		||||
import cc.tweaked.gradle.MinecraftConfigurations
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
    id("net.minecraftforge.gradle")
 | 
			
		||||
    // We must apply java-convention after Forge, as we need the fg extension to be present.
 | 
			
		||||
    id("cc-tweaked.java-convention")
 | 
			
		||||
    id("org.parchmentmc.librarian.forgegradle")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,22 +37,40 @@ java {
 | 
			
		||||
 | 
			
		||||
repositories {
 | 
			
		||||
    mavenCentral()
 | 
			
		||||
    maven("https://squiddev.cc/maven") {
 | 
			
		||||
 | 
			
		||||
    val mainMaven = maven("https://squiddev.cc/maven") {
 | 
			
		||||
        name = "SquidDev"
 | 
			
		||||
        content {
 | 
			
		||||
            includeGroup("org.squiddev")
 | 
			
		||||
            includeGroup("cc.tweaked")
 | 
			
		||||
            // Things we mirror
 | 
			
		||||
            includeGroup("dev.architectury")
 | 
			
		||||
            includeGroup("maven.modrinth")
 | 
			
		||||
            includeGroup("me.shedaniel")
 | 
			
		||||
            includeGroup("me.shedaniel.cloth")
 | 
			
		||||
            includeGroup("mezz.jei")
 | 
			
		||||
            includeModule("com.terraformersmc", "modmenu")
 | 
			
		||||
            // Until https://github.com/SpongePowered/Mixin/pull/593 is merged
 | 
			
		||||
            includeModule("org.spongepowered", "mixin")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    exclusiveContent {
 | 
			
		||||
        forRepositories(mainMaven)
 | 
			
		||||
 | 
			
		||||
        // Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
 | 
			
		||||
        // enforce in our Forge overlay.
 | 
			
		||||
        val fg =
 | 
			
		||||
            project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
 | 
			
		||||
        if (fg != null) forRepositories(fg.repository)
 | 
			
		||||
 | 
			
		||||
        filter {
 | 
			
		||||
            includeGroup("cc.tweaked")
 | 
			
		||||
            includeModule("org.squiddev", "Cobalt")
 | 
			
		||||
            // Things we mirror
 | 
			
		||||
            includeGroup("commoble.morered")
 | 
			
		||||
            includeGroup("dev.architectury")
 | 
			
		||||
            includeGroup("dev.emi")
 | 
			
		||||
            includeGroup("maven.modrinth")
 | 
			
		||||
            includeGroup("me.shedaniel.cloth")
 | 
			
		||||
            includeGroup("me.shedaniel")
 | 
			
		||||
            includeGroup("mezz.jei")
 | 
			
		||||
            includeGroup("org.teavm")
 | 
			
		||||
            includeModule("com.terraformersmc", "modmenu")
 | 
			
		||||
            includeModule("me.lucko", "fabric-permissions-api")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
@@ -82,7 +100,10 @@ sourceSets.all {
 | 
			
		||||
            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:AnnotatedPackages",
 | 
			
		||||
                listOf("dan200.computercraft", "cc.tweaked", "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")
 | 
			
		||||
@@ -104,6 +125,7 @@ tasks.withType(JavaCompile::class.java).configureEach {
 | 
			
		||||
 | 
			
		||||
tasks.processResources {
 | 
			
		||||
    exclude("**/*.license")
 | 
			
		||||
    exclude(".cache")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.withType(AbstractArchiveTask::class.java).configureEach {
 | 
			
		||||
@@ -156,6 +178,12 @@ project.plugins.withType(CCTweakedPlugin::class.java) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("checkstyle") {
 | 
			
		||||
    description = "Run Checkstyle on all sources"
 | 
			
		||||
    group = LifecycleBasePlugin.VERIFICATION_GROUP
 | 
			
		||||
    dependsOn(tasks.withType(Checkstyle::class.java))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
spotless {
 | 
			
		||||
    encoding = StandardCharsets.UTF_8
 | 
			
		||||
    lineEndings = LineEnding.UNIX
 | 
			
		||||
@@ -173,6 +201,7 @@ spotless {
 | 
			
		||||
 | 
			
		||||
    val ktlintConfig = mapOf(
 | 
			
		||||
        "ktlint_standard_no-wildcard-imports" to "disabled",
 | 
			
		||||
        "ktlint_standard_class-naming" to "disabled",
 | 
			
		||||
        "ij_kotlin_allow_trailing_comma" to "true",
 | 
			
		||||
        "ij_kotlin_allow_trailing_comma_on_call_site" to "true",
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.file.DirectoryProperty
 | 
			
		||||
import org.gradle.api.provider.Property
 | 
			
		||||
import org.gradle.api.tasks.AbstractExecTask
 | 
			
		||||
import org.gradle.api.tasks.OutputDirectory
 | 
			
		||||
@@ -11,5 +12,5 @@ import java.io.File
 | 
			
		||||
 | 
			
		||||
abstract class ExecToDir : AbstractExecTask<ExecToDir>(ExecToDir::class.java) {
 | 
			
		||||
    @get:OutputDirectory
 | 
			
		||||
    abstract val output: Property<File>
 | 
			
		||||
    abstract val output: DirectoryProperty
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import org.gradle.api.artifacts.dsl.DependencyHandler
 | 
			
		||||
import org.gradle.api.file.FileSystemLocation
 | 
			
		||||
import org.gradle.api.file.FileSystemLocationProperty
 | 
			
		||||
import org.gradle.api.provider.Property
 | 
			
		||||
import org.gradle.api.provider.Provider
 | 
			
		||||
import org.gradle.api.tasks.JavaExec
 | 
			
		||||
@@ -124,3 +126,6 @@ class CloseScope : AutoCloseable {
 | 
			
		||||
 | 
			
		||||
/** Proxy method to avoid overload ambiguity. */
 | 
			
		||||
fun <T> Property<T>.setProvider(provider: Provider<out T>) = set(provider)
 | 
			
		||||
 | 
			
		||||
/** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */
 | 
			
		||||
fun Provider<out FileSystemLocation>.getAbsolutePath(): String = get().asFile.absolutePath
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import net.minecraftforge.gradle.common.util.RunConfig
 | 
			
		||||
import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal
 | 
			
		||||
import org.gradle.api.plugins.JavaPluginExtension
 | 
			
		||||
import org.gradle.api.tasks.JavaExec
 | 
			
		||||
import org.gradle.jvm.toolchain.JavaToolchainService
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set [JavaExec] task to run a given [RunConfig].
 | 
			
		||||
 */
 | 
			
		||||
fun JavaExec.setRunConfig(config: RunConfig) {
 | 
			
		||||
    dependsOn("prepareRuns")
 | 
			
		||||
    setRunConfigInternal(project, this, config)
 | 
			
		||||
    doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
 | 
			
		||||
 | 
			
		||||
    javaLauncher.set(
 | 
			
		||||
        project.extensions.getByType(JavaToolchainService::class.java)
 | 
			
		||||
            .launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@@ -60,7 +60,7 @@ class IlluaminatePlugin : Plugin<Project> {
 | 
			
		||||
 | 
			
		||||
    /** Define a dependency for illuaminate from a version number and the current operating system. */
 | 
			
		||||
    private fun illuaminateArtifact(project: Project, version: String): Dependency {
 | 
			
		||||
        val osName = System.getProperty("os.name").toLowerCase()
 | 
			
		||||
        val osName = System.getProperty("os.name").lowercase()
 | 
			
		||||
        val (os, suffix) = when {
 | 
			
		||||
            osName.contains("windows") -> Pair("windows", ".exe")
 | 
			
		||||
            osName.contains("mac os") || osName.contains("darwin") -> Pair("macos", "")
 | 
			
		||||
@@ -68,7 +68,7 @@ class IlluaminatePlugin : Plugin<Project> {
 | 
			
		||||
            else -> error("Unsupported OS $osName for illuaminate")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val osArch = System.getProperty("os.arch").toLowerCase()
 | 
			
		||||
        val osArch = System.getProperty("os.arch").lowercase()
 | 
			
		||||
        val arch = when {
 | 
			
		||||
            // On macOS the x86_64 binary will work for both ARM and Intel Macs through Rosetta.
 | 
			
		||||
            os == "macos" -> "x86_64"
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
 | 
			
		||||
package cc.tweaked.gradle
 | 
			
		||||
 | 
			
		||||
import net.minecraftforge.gradle.common.util.RunConfig
 | 
			
		||||
import org.gradle.api.GradleException
 | 
			
		||||
import org.gradle.api.file.FileSystemOperations
 | 
			
		||||
import org.gradle.api.invocation.Gradle
 | 
			
		||||
@@ -32,11 +33,14 @@ abstract class ClientJavaExec : JavaExec() {
 | 
			
		||||
        usesService(clientRunner)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @get:Input
 | 
			
		||||
    val renderdoc get() = project.hasProperty("renderdoc")
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When [false], tests will not be run automatically, allowing the user to debug rendering.
 | 
			
		||||
     */
 | 
			
		||||
    @get:Input
 | 
			
		||||
    val clientDebug get() = project.hasProperty("clientDebug")
 | 
			
		||||
    val clientDebug get() = renderdoc || project.hasProperty("clientDebug")
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * When [false], tests will not run under a framebuffer.
 | 
			
		||||
@@ -50,6 +54,25 @@ abstract class ClientJavaExec : JavaExec() {
 | 
			
		||||
    @get:OutputFile
 | 
			
		||||
    val testResults = project.layout.buildDirectory.file("test-results/$name.xml")
 | 
			
		||||
 | 
			
		||||
    private fun setTestProperties() {
 | 
			
		||||
        if (!clientDebug) systemProperty("cctest.client", "")
 | 
			
		||||
        if (renderdoc) environment("LD_PRELOAD", "/usr/lib/librenderdoc.so")
 | 
			
		||||
        systemProperty("cctest.gametest-report", testResults.get().asFile.absoluteFile)
 | 
			
		||||
        workingDir(project.buildDir.resolve("gametest").resolve(name))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        setTestProperties()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set this task to run a given [RunConfig].
 | 
			
		||||
     */
 | 
			
		||||
    fun setRunConfig(config: RunConfig) {
 | 
			
		||||
        (this as JavaExec).setRunConfig(config)
 | 
			
		||||
        setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Copy configuration from a task with the given name.
 | 
			
		||||
     */
 | 
			
		||||
@@ -61,10 +84,7 @@ abstract class ClientJavaExec : JavaExec() {
 | 
			
		||||
    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))
 | 
			
		||||
        setTestProperties() // copyToFull may clobber some properties, ensure everything is set.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package net.minecraftforge.gradle.common.util.runs
 | 
			
		||||
 | 
			
		||||
import net.minecraftforge.gradle.common.util.RunConfig
 | 
			
		||||
import org.gradle.api.Project
 | 
			
		||||
import org.gradle.process.CommandLineArgumentProvider
 | 
			
		||||
import org.gradle.process.JavaExecSpec
 | 
			
		||||
import java.io.File
 | 
			
		||||
import java.util.function.Supplier
 | 
			
		||||
import java.util.stream.Collectors
 | 
			
		||||
import java.util.stream.Stream
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set up a [JavaExecSpec] to execute a [RunConfig].
 | 
			
		||||
 *
 | 
			
		||||
 * [MinecraftRunTask] sets up all its properties when the task is executed, rather than when configured. As such, it's
 | 
			
		||||
 * not possible to use [cc.tweaked.gradle.copyToFull] like we do for Fabric. Instead, we set up the task manually.
 | 
			
		||||
 *
 | 
			
		||||
 * Unfortunately most of the functionality we need is package-private, and so we have to put our code into the package.
 | 
			
		||||
 */
 | 
			
		||||
internal fun setRunConfigInternal(project: Project, spec: JavaExecSpec, config: RunConfig) {
 | 
			
		||||
    spec.workingDir = File(config.workingDirectory)
 | 
			
		||||
 | 
			
		||||
    spec.mainClass.set(config.main)
 | 
			
		||||
    for (source in config.allSources) spec.classpath(source.runtimeClasspath)
 | 
			
		||||
 | 
			
		||||
    val originalTask = project.tasks.named(config.taskName, MinecraftRunTask::class.java)
 | 
			
		||||
 | 
			
		||||
    // Add argument and JVM argument via providers, to be as lazy as possible with fetching artifacts.
 | 
			
		||||
    val lazyTokens = RunConfigGenerator.configureTokensLazy(
 | 
			
		||||
        project, config, RunConfigGenerator.mapModClassesToGradle(project, config),
 | 
			
		||||
        originalTask.get().minecraftArtifacts,
 | 
			
		||||
        originalTask.get().runtimeClasspathArtifacts,
 | 
			
		||||
    )
 | 
			
		||||
    spec.argumentProviders.add(
 | 
			
		||||
        CommandLineArgumentProvider {
 | 
			
		||||
            RunConfigGenerator.getArgsStream(config, lazyTokens, false).toList()
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
    spec.jvmArgumentProviders.add(
 | 
			
		||||
        CommandLineArgumentProvider {
 | 
			
		||||
            (if (config.isClient) config.jvmArgs + originalTask.get().additionalClientArgs.get() else config.jvmArgs).map { config.replace(lazyTokens, it) } +
 | 
			
		||||
                config.properties.map { (k, v) -> "-D${k}=${config.replace(lazyTokens, v)}" }
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    for ((key, value) in config.environment) spec.environment(key, config.replace(lazyTokens, value))
 | 
			
		||||
}
 | 
			
		||||
@@ -16,4 +16,10 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
    <!-- The commands API is documented in Lua. -->
 | 
			
		||||
    <suppress checks="SummaryJavadocCheck" files=".*[\\/]CommandAPI.java" />
 | 
			
		||||
 | 
			
		||||
    <!-- Allow putting files in other packages if they look like our TeaVM stubs. -->
 | 
			
		||||
    <suppress checks="PackageName" files=".*[\\/]T[A-Za-z]+.java" />
 | 
			
		||||
 | 
			
		||||
    <!-- Allow underscores in our test classes. -->
 | 
			
		||||
    <suppress checks="MethodName" files=".*Contract.java" />
 | 
			
		||||
</suppressions>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{alarm} event is fired when an alarm started with @{os.setAlarm} completes.
 | 
			
		||||
The [`alarm`] event is fired when an alarm started with [`os.setAlarm`] completes.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The ID of the alarm that finished.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The ID of the alarm that finished.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Starts a timer and then waits for it to complete.
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,15 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{char} event is fired when a character is typed on the keyboard.
 | 
			
		||||
The [`char`] event is fired when a character is typed on the keyboard.
 | 
			
		||||
 | 
			
		||||
The @{char} event is different to a key press. Sometimes multiple key presses may result in one character being
 | 
			
		||||
The [`char`] event is different to a key press. Sometimes multiple key presses may result in one character being
 | 
			
		||||
typed (for instance, on some European keyboards). Similarly, some keys (e.g. <kbd>Ctrl</kbd>) do not have any
 | 
			
		||||
corresponding character. The @{key} should be used if you want to listen to key presses themselves.
 | 
			
		||||
corresponding character. The [`key`] should be used if you want to listen to key presses themselves.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The string representing the character that was pressed.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The string representing the character that was pressed.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,11 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{computer_command} event is fired when the `/computercraft queue` command is run for the current computer.
 | 
			
		||||
The [`computer_command`] event is fired when the `/computercraft queue` command is run for the current computer.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}<abbr title="Variable number of arguments">…</abbr>: The arguments passed to the command.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]<abbr title="Variable number of arguments">…</abbr>: The arguments passed to the command.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints the contents of messages sent:
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{disk} event is fired when a disk is inserted into an adjacent or networked disk drive.
 | 
			
		||||
The [`disk`] event is fired when a disk is inserted into an adjacent or networked disk drive.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The side of the disk drive that had a disk inserted.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The side of the disk drive that had a disk inserted.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when a disk is inserted:
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{disk_eject} event is fired when a disk is removed from an adjacent or networked disk drive.
 | 
			
		||||
The [`disk_eject`] event is fired when a disk is removed from an adjacent or networked disk drive.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The side of the disk drive that had a disk removed.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The side of the disk drive that had a disk removed.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when a disk is removed:
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,15 @@ SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{file_transfer} event is queued when a user drags-and-drops a file on an open computer.
 | 
			
		||||
The [`file_transfer`] event is queued when a user drags-and-drops a file on an open computer.
 | 
			
		||||
 | 
			
		||||
This event contains a single argument of type @{TransferredFiles}, which can be used to @{TransferredFiles.getFiles|get
 | 
			
		||||
the files to be transferred}. Each file returned is a @{fs.BinaryReadHandle|binary file handle} with an additional
 | 
			
		||||
@{TransferredFile.getName|getName} method.
 | 
			
		||||
This event contains a single argument of type [`TransferredFiles`], which can be used to [get the files to be
 | 
			
		||||
transferred][`TransferredFiles.getFiles`]. Each file returned is a [binary file handle][`fs.BinaryReadHandle`] with an
 | 
			
		||||
additional [getName][`TransferredFile.getName`] method.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name
 | 
			
		||||
2. @{TransferredFiles}: The list of transferred files.
 | 
			
		||||
1. [`string`]: The event name
 | 
			
		||||
2. [`TransferredFiles`]: The list of transferred files.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Waits for a user to drop files on top of the computer, then prints the list of files and the size of each file.
 | 
			
		||||
@@ -29,7 +29,7 @@ for _, file in ipairs(files.getFiles()) do
 | 
			
		||||
  local size = file.seek("end")
 | 
			
		||||
  file.seek("set", 0)
 | 
			
		||||
 | 
			
		||||
  print(file.getName() .. " " .. file.getSize())
 | 
			
		||||
  print(file.getName() .. " " .. size)
 | 
			
		||||
end
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,12 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{http_check} event is fired when a URL check finishes.
 | 
			
		||||
The [`http_check`] event is fired when a URL check finishes.
 | 
			
		||||
 | 
			
		||||
This event is normally handled inside @{http.checkURL}, but it can still be seen when using @{http.checkURLAsync}.
 | 
			
		||||
This event is normally handled inside [`http.checkURL`], but it can still be seen when using [`http.checkURLAsync`].
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The URL requested to be checked.
 | 
			
		||||
3. @{boolean}: Whether the check succeeded.
 | 
			
		||||
4. <span class="type">@{string}|@{nil}</span>: If the check failed, a reason explaining why the check failed.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The URL requested to be checked.
 | 
			
		||||
3. [`boolean`]: Whether the check succeeded.
 | 
			
		||||
4. <span class="type">[`string`]|[`nil`]</span>: If the check failed, a reason explaining why the check failed.
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,15 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{http_failure} event is fired when an HTTP request fails.
 | 
			
		||||
The [`http_failure`] event is fired when an HTTP request fails.
 | 
			
		||||
 | 
			
		||||
This event is normally handled inside @{http.get} and @{http.post}, but it can still be seen when using @{http.request}.
 | 
			
		||||
This event is normally handled inside [`http.get`] and [`http.post`], but it can still be seen when using [`http.request`].
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The URL of the site requested.
 | 
			
		||||
3. @{string}: An error describing the failure.
 | 
			
		||||
4. <span class="type">@{http.Response}|@{nil}</span>: A response handle if the connection succeeded, but the server's
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The URL of the site requested.
 | 
			
		||||
3. [`string`]: An error describing the failure.
 | 
			
		||||
4. <span class="type">[`http.Response`]|[`nil`]</span>: A response handle if the connection succeeded, but the server's
 | 
			
		||||
   response indicated failure.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,14 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{http_success} event is fired when an HTTP request returns successfully.
 | 
			
		||||
The [`http_success`] event is fired when an HTTP request returns successfully.
 | 
			
		||||
 | 
			
		||||
This event is normally handled inside @{http.get} and @{http.post}, but it can still be seen when using @{http.request}.
 | 
			
		||||
This event is normally handled inside [`http.get`] and [`http.post`], but it can still be seen when using [`http.request`].
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The URL of the site requested.
 | 
			
		||||
3. @{http.Response}: The successful HTTP response.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The URL of the site requested.
 | 
			
		||||
3. [`http.Response`]: The successful HTTP response.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints the content of a website (this may fail if the request fails):
 | 
			
		||||
 
 | 
			
		||||
@@ -11,15 +11,15 @@ SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
This event is fired when any key is pressed while the terminal is focused.
 | 
			
		||||
 | 
			
		||||
This event returns a numerical "key code" (for instance, <kbd>F1</kbd> is 290). This value may vary between versions and
 | 
			
		||||
so it is recommended to use the constants in the @{keys} API rather than hard coding numeric values.
 | 
			
		||||
so it is recommended to use the constants in the [`keys`] API rather than hard coding numeric values.
 | 
			
		||||
 | 
			
		||||
If the button pressed represented a printable character, then the @{key} event will be followed immediately by a @{char}
 | 
			
		||||
event. If you are consuming text input, use a @{char} event instead!
 | 
			
		||||
If the button pressed represented a printable character, then the [`key`] event will be followed immediately by a [`char`]
 | 
			
		||||
event. If you are consuming text input, use a [`char`] event instead!
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The numerical key value of the key pressed.
 | 
			
		||||
3. @{boolean}: Whether the key event was generated while holding the key (@{true}), rather than pressing it the first time (@{false}).
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The numerical key value of the key pressed.
 | 
			
		||||
3. [`boolean`]: Whether the key event was generated while holding the key ([`true`]), rather than pressing it the first time ([`false`]).
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints each key when the user presses it, and if the key is being held.
 | 
			
		||||
 
 | 
			
		||||
@@ -12,14 +12,14 @@ SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
Fired whenever a key is released (or the terminal is closed while a key was being pressed).
 | 
			
		||||
 | 
			
		||||
This event returns a numerical "key code" (for instance, <kbd>F1</kbd> is 290). This value may vary between versions and
 | 
			
		||||
so it is recommended to use the constants in the @{keys} API rather than hard coding numeric values.
 | 
			
		||||
so it is recommended to use the constants in the [`keys`] API rather than hard coding numeric values.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The numerical key value of the key pressed.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The numerical key value of the key pressed.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints each key released on the keyboard whenever a @{key_up} event is fired.
 | 
			
		||||
Prints each key released on the keyboard whenever a [`key_up`] event is fired.
 | 
			
		||||
 | 
			
		||||
```lua
 | 
			
		||||
while true do
 | 
			
		||||
 
 | 
			
		||||
@@ -8,18 +8,18 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{modem_message} event is fired when a message is received on an open channel on any @{modem}.
 | 
			
		||||
The [`modem_message`] event is fired when a message is received on an open channel on any [`modem`].
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The side of the modem that received the message.
 | 
			
		||||
3. @{number}: The channel that the message was sent on.
 | 
			
		||||
4. @{number}: The reply channel set by the sender.
 | 
			
		||||
5. @{any}: The message as sent by the sender.
 | 
			
		||||
6. <span class="type">@{number}|@{nil}</span>: The distance between the sender and the receiver in blocks, or @{nil} if the message was sent between dimensions.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The side of the modem that received the message.
 | 
			
		||||
3. [`number`]: The channel that the message was sent on.
 | 
			
		||||
4. [`number`]: The reply channel set by the sender.
 | 
			
		||||
5. [`any`]: The message as sent by the sender.
 | 
			
		||||
6. <span class="type">[`number`]|[`nil`]</span>: The distance between the sender and the receiver in blocks, or [`nil`] if the message was sent between dimensions.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Wraps a @{modem} peripheral, opens channel 0 for listening, and prints all received messages.
 | 
			
		||||
Wraps a [`modem`] peripheral, opens channel 0 for listening, and prints all received messages.
 | 
			
		||||
 | 
			
		||||
```lua
 | 
			
		||||
local modem = peripheral.find("modem") or error("No modem attached", 0)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,11 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{monitor_resize} event is fired when an adjacent or networked monitor's size is changed.
 | 
			
		||||
The [`monitor_resize`] event is fired when an adjacent or networked monitor's size is changed.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The side or network ID of the monitor that was resized.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The side or network ID of the monitor that was resized.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when a monitor is resized:
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,13 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{monitor_touch} event is fired when an adjacent or networked Advanced Monitor is right-clicked.
 | 
			
		||||
The [`monitor_touch`] event is fired when an adjacent or networked Advanced Monitor is right-clicked.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The side or network ID of the monitor that was touched.
 | 
			
		||||
3. @{number}: The X coordinate of the touch, in characters.
 | 
			
		||||
4. @{number}: The Y coordinate of the touch, in characters.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The side or network ID of the monitor that was touched.
 | 
			
		||||
3. [`number`]: The X coordinate of the touch, in characters.
 | 
			
		||||
4. [`number`]: The Y coordinate of the touch, in characters.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when a monitor is touched:
 | 
			
		||||
 
 | 
			
		||||
@@ -12,13 +12,13 @@ This event is fired when the terminal is clicked with a mouse. This event is onl
 | 
			
		||||
advanced turtles and pocket computers).
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The mouse button that was clicked.
 | 
			
		||||
3. @{number}: The X-coordinate of the click.
 | 
			
		||||
4. @{number}: The Y-coordinate of the click.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The mouse button that was clicked.
 | 
			
		||||
3. [`number`]: The X-coordinate of the click.
 | 
			
		||||
4. [`number`]: The Y-coordinate of the click.
 | 
			
		||||
 | 
			
		||||
## Mouse buttons
 | 
			
		||||
Several mouse events (@{mouse_click}, @{mouse_up}, @{mouse_scroll}) contain a "mouse button" code. This takes a
 | 
			
		||||
Several mouse events ([`mouse_click`], [`mouse_up`], [`mouse_scroll`]) contain a "mouse button" code. This takes a
 | 
			
		||||
numerical value depending on which button on your mouse was last pressed when this event occurred.
 | 
			
		||||
 | 
			
		||||
| Button Code | Mouse Button  |
 | 
			
		||||
 
 | 
			
		||||
@@ -12,10 +12,10 @@ SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
This event is fired every time the mouse is moved while a mouse button is being held.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The [mouse button](mouse_click.html#Mouse_buttons) that is being pressed.
 | 
			
		||||
3. @{number}: The X-coordinate of the mouse.
 | 
			
		||||
4. @{number}: The Y-coordinate of the mouse.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The [mouse button](mouse_click.html#Mouse_buttons) that is being pressed.
 | 
			
		||||
3. [`number`]: The X-coordinate of the mouse.
 | 
			
		||||
4. [`number`]: The Y-coordinate of the mouse.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Print the button and the coordinates whenever the mouse is dragged.
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,10 @@ SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
This event is fired when a mouse wheel is scrolled in the terminal.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The direction of the scroll. (-1 = up, 1 = down)
 | 
			
		||||
3. @{number}: The X-coordinate of the mouse when scrolling.
 | 
			
		||||
4. @{number}: The Y-coordinate of the mouse when scrolling.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The direction of the scroll. (-1 = up, 1 = down)
 | 
			
		||||
3. [`number`]: The X-coordinate of the mouse when scrolling.
 | 
			
		||||
4. [`number`]: The Y-coordinate of the mouse when scrolling.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints the direction of each scroll, and the position of the mouse at the time.
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,10 @@ SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
This event is fired when a mouse button is released or a held mouse leaves the computer's terminal.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The [mouse button](mouse_click.html#Mouse_buttons) that was released.
 | 
			
		||||
3. @{number}: The X-coordinate of the mouse.
 | 
			
		||||
4. @{number}: The Y-coordinate of the mouse.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The [mouse button](mouse_click.html#Mouse_buttons) that was released.
 | 
			
		||||
3. [`number`]: The X-coordinate of the mouse.
 | 
			
		||||
4. [`number`]: The Y-coordinate of the mouse.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints the coordinates and button number whenever the mouse is released.
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,11 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{paste} event is fired when text is pasted into the computer through Ctrl-V (or ⌘V on Mac).
 | 
			
		||||
The [`paste`] event is fired when text is pasted into the computer through Ctrl-V (or ⌘V on Mac).
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string} The text that was pasted.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`] The text that was pasted.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints pasted text:
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{peripheral} event is fired when a peripheral is attached on a side or to a modem.
 | 
			
		||||
The [`peripheral`] event is fired when a peripheral is attached on a side or to a modem.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The side the peripheral was attached to.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The side the peripheral was attached to.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when a peripheral is attached:
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{peripheral_detach} event is fired when a peripheral is detached from a side or from a modem.
 | 
			
		||||
The [`peripheral_detach`] event is fired when a peripheral is detached from a side or from a modem.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The side the peripheral was detached from.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The side the peripheral was detached from.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when a peripheral is detached:
 | 
			
		||||
 
 | 
			
		||||
@@ -10,17 +10,17 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{rednet_message} event is fired when a message is sent over Rednet.
 | 
			
		||||
The [`rednet_message`] event is fired when a message is sent over Rednet.
 | 
			
		||||
 | 
			
		||||
This event is usually handled by @{rednet.receive}, but it can also be pulled manually.
 | 
			
		||||
This event is usually handled by [`rednet.receive`], but it can also be pulled manually.
 | 
			
		||||
 | 
			
		||||
@{rednet_message} events are sent by @{rednet.run} in the top-level coroutine in response to @{modem_message} events. A @{rednet_message} event is always preceded by a @{modem_message} event. They are generated inside CraftOS rather than being sent by the ComputerCraft machine.
 | 
			
		||||
[`rednet_message`] events are sent by [`rednet.run`] in the top-level coroutine in response to [`modem_message`] events. A [`rednet_message`] event is always preceded by a [`modem_message`] event. They are generated inside CraftOS rather than being sent by the ComputerCraft machine.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The ID of the sending computer.
 | 
			
		||||
3. @{any}: The message sent.
 | 
			
		||||
4. <span class="type">@{string}|@{nil}</span>: The protocol of the message, if provided.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The ID of the sending computer.
 | 
			
		||||
3. [`any`]: The message sent.
 | 
			
		||||
4. <span class="type">[`string`]|[`nil`]</span>: The protocol of the message, if provided.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when one is sent:
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,10 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{event!redstone} event is fired whenever any redstone inputs on the computer change.
 | 
			
		||||
The [`event!redstone`] event is fired whenever any redstone inputs on the computer change.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when a redstone input changes:
 | 
			
		||||
 
 | 
			
		||||
@@ -10,13 +10,13 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The name of the speaker which is available to play more audio.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The name of the speaker which is available to play more audio.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
This uses @{io.lines} to read audio data in blocks of 16KiB from "example_song.dfpwm", and then attempts to play it
 | 
			
		||||
using @{speaker.playAudio}. If the speaker's buffer is full, it waits for an event and tries again.
 | 
			
		||||
This uses [`io.lines`] to read audio data in blocks of 16KiB from "example_song.dfpwm", and then attempts to play it
 | 
			
		||||
using [`speaker.playAudio`]. If the speaker's buffer is full, it waits for an event and tries again.
 | 
			
		||||
 | 
			
		||||
```lua {data-peripheral=speaker}
 | 
			
		||||
local dfpwm = require("cc.audio.dfpwm")
 | 
			
		||||
 
 | 
			
		||||
@@ -9,13 +9,13 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{task_complete} event is fired when an asynchronous task completes. This is usually handled inside the function call that queued the task; however, functions such as @{commands.execAsync} return immediately so the user can wait for completion.
 | 
			
		||||
The [`task_complete`] event is fired when an asynchronous task completes. This is usually handled inside the function call that queued the task; however, functions such as [`commands.execAsync`] return immediately so the user can wait for completion.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The ID of the task that completed.
 | 
			
		||||
3. @{boolean}: Whether the command succeeded.
 | 
			
		||||
4. @{string}: If the command failed, an error message explaining the failure. (This is not present if the command succeeded.)
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The ID of the task that completed.
 | 
			
		||||
3. [`boolean`]: Whether the command succeeded.
 | 
			
		||||
4. [`string`]: If the command failed, an error message explaining the failure. (This is not present if the command succeeded.)
 | 
			
		||||
5. <abbr title="Variable number of arguments">…</abbr>: Any parameters returned from the command.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
 
 | 
			
		||||
@@ -8,15 +8,15 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{term_resize} event is fired when the main terminal is resized. For instance:
 | 
			
		||||
 - When a the tab bar is shown or hidden in @{multishell}.
 | 
			
		||||
The [`term_resize`] event is fired when the main terminal is resized. For instance:
 | 
			
		||||
 - When a the tab bar is shown or hidden in [`multishell`].
 | 
			
		||||
 - When the terminal is redirected to a monitor via the "monitor" program and the monitor is resized.
 | 
			
		||||
 | 
			
		||||
When this event fires, some parts of the terminal may have been moved or deleted. Simple terminal programs (those
 | 
			
		||||
not using @{term.setCursorPos}) can ignore this event, but more complex GUI programs should redraw the entire screen.
 | 
			
		||||
not using [`term.setCursorPos`]) can ignore this event, but more complex GUI programs should redraw the entire screen.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Print a message each time the terminal is resized.
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,14 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{terminate} event is fired when <kbd>Ctrl-T</kbd> is held down.
 | 
			
		||||
The [`terminate`] event is fired when <kbd>Ctrl-T</kbd> is held down.
 | 
			
		||||
 | 
			
		||||
This event is normally handled by @{os.pullEvent}, and will not be returned. However, @{os.pullEventRaw} will return this event when fired.
 | 
			
		||||
This event is normally handled by [`os.pullEvent`], and will not be returned. However, [`os.pullEventRaw`] will return this event when fired.
 | 
			
		||||
 | 
			
		||||
@{terminate} will be sent even when a filter is provided to @{os.pullEventRaw}. When using @{os.pullEventRaw} with a filter, make sure to check that the event is not @{terminate}.
 | 
			
		||||
[`terminate`] will be sent even when a filter is provided to [`os.pullEventRaw`]. When using [`os.pullEventRaw`] with a filter, make sure to check that the event is not [`terminate`].
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when Ctrl-T is held:
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{timer} event is fired when a timer started with @{os.startTimer} completes.
 | 
			
		||||
The [`timer`] event is fired when a timer started with [`os.startTimer`] completes.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{number}: The ID of the timer that finished.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`number`]: The ID of the timer that finished.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Start and wait for a timer to finish.
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,10 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{turtle_inventory} event is fired when a turtle's inventory is changed.
 | 
			
		||||
The [`turtle_inventory`] event is fired when a turtle's inventory is changed.
 | 
			
		||||
 | 
			
		||||
## Return values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when the inventory is changed:
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,20 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{websocket_closed} event is fired when an open WebSocket connection is closed.
 | 
			
		||||
The [`websocket_closed`] event is fired when an open WebSocket connection is closed.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The URL of the WebSocket that was closed.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The URL of the WebSocket that was closed.
 | 
			
		||||
3. <span class="type">[`string`]|[`nil`]</span>: The [server-provided reason][close_reason]
 | 
			
		||||
   the websocket was closed. This will be [`nil`] if the connection was closed
 | 
			
		||||
   abnormally.
 | 
			
		||||
4. <span class="type">[`number`]|[`nil`]</span>: The [connection close code][close_code],
 | 
			
		||||
   indicating why the socket was closed. This will be [`nil`] if the connection
 | 
			
		||||
   was closed abnormally.
 | 
			
		||||
 | 
			
		||||
[close_reason]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6 "The WebSocket Connection Close Reason, RFC 6455"
 | 
			
		||||
[close_code]: https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5 "The WebSocket Connection Close Code, RFC 6455"
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message when a WebSocket is closed (this may take a minute):
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,14 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{websocket_failure} event is fired when a WebSocket connection request fails.
 | 
			
		||||
The [`websocket_failure`] event is fired when a WebSocket connection request fails.
 | 
			
		||||
 | 
			
		||||
This event is normally handled inside @{http.websocket}, but it can still be seen when using @{http.websocketAsync}.
 | 
			
		||||
This event is normally handled inside [`http.websocket`], but it can still be seen when using [`http.websocketAsync`].
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The URL of the site requested.
 | 
			
		||||
3. @{string}: An error describing the failure.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The URL of the site requested.
 | 
			
		||||
3. [`string`]: An error describing the failure.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints an error why the website cannot be contacted:
 | 
			
		||||
 
 | 
			
		||||
@@ -8,15 +8,15 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{websocket_message} event is fired when a message is received on an open WebSocket connection.
 | 
			
		||||
The [`websocket_message`] event is fired when a message is received on an open WebSocket connection.
 | 
			
		||||
 | 
			
		||||
This event is normally handled by @{http.Websocket.receive}, but it can also be pulled manually.
 | 
			
		||||
This event is normally handled by [`http.Websocket.receive`], but it can also be pulled manually.
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The URL of the WebSocket.
 | 
			
		||||
3. @{string}: The contents of the message.
 | 
			
		||||
4. @{boolean}: Whether this is a binary message.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The URL of the WebSocket.
 | 
			
		||||
3. [`string`]: The contents of the message.
 | 
			
		||||
4. [`boolean`]: Whether this is a binary message.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints a message sent by a WebSocket:
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,14 @@ SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
The @{websocket_success} event is fired when a WebSocket connection request returns successfully.
 | 
			
		||||
The [`websocket_success`] event is fired when a WebSocket connection request returns successfully.
 | 
			
		||||
 | 
			
		||||
This event is normally handled inside @{http.websocket}, but it can still be seen when using @{http.websocketAsync}.
 | 
			
		||||
This event is normally handled inside [`http.websocket`], but it can still be seen when using [`http.websocketAsync`].
 | 
			
		||||
 | 
			
		||||
## Return Values
 | 
			
		||||
1. @{string}: The event name.
 | 
			
		||||
2. @{string}: The URL of the site.
 | 
			
		||||
3. @{http.Websocket}: The handle for the WebSocket.
 | 
			
		||||
1. [`string`]: The event name.
 | 
			
		||||
2. [`string`]: The URL of the site.
 | 
			
		||||
3. [`http.Websocket`]: The handle for the WebSocket.
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
Prints the content of a website (this may fail if the request fails):
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
# Setting up GPS
 | 
			
		||||
The @{gps} API allows computers and turtles to find their current position using wireless modems.
 | 
			
		||||
The [`gps`] API allows computers and turtles to find their current position using wireless modems.
 | 
			
		||||
 | 
			
		||||
In order to use GPS, you'll need to set up multiple *GPS hosts*. These are computers running the special `gps host`
 | 
			
		||||
program, which tell other computers the host's position. Several hosts running together are known as a *GPS
 | 
			
		||||
@@ -19,22 +19,21 @@ In order to give the best results, a GPS constellation needs at least four compu
 | 
			
		||||
constellation is redundant, but it does not cause problems.
 | 
			
		||||
 | 
			
		||||
## Building a GPS constellation
 | 
			
		||||
{.big-image}
 | 
			
		||||
<img alt="An example GPS constellation." src="/images/gps-constellation-example.png" class="big-image" />
 | 
			
		||||
 | 
			
		||||
We are going to build our GPS constellation as shown in the image above. You will need 4 computers and either 4 wireless
 | 
			
		||||
modems or 4 ender modems. Try not to mix ender and wireless modems together as you might get some odd behavior when your
 | 
			
		||||
requesting computers are out of range.
 | 
			
		||||
 | 
			
		||||
:::tip Ender modems vs wireless modems
 | 
			
		||||
Ender modems have a very large range, which makes them very useful for setting up GPS hosts. If you do this then you
 | 
			
		||||
will likely only need one GPS constellation for the whole dimension (such as the Overworld or Nether).
 | 
			
		||||
 | 
			
		||||
If you do use wireless modems then you may find that you need multiple GPS constellations to cover your needs.
 | 
			
		||||
 | 
			
		||||
A computer needs a wireless or ender modem and to be in range of a GPS constellation that is in the same dimension as it
 | 
			
		||||
to use the GPS API. The reason for this is that ComputerCraft mimics real-life GPS by making use of the distance
 | 
			
		||||
parameter of @{modem_message|modem messages} and some maths.
 | 
			
		||||
:::
 | 
			
		||||
> [Ender modems vs wireless modems][!TIP]
 | 
			
		||||
> Ender modems have a very large range, which makes them very useful for setting up GPS hosts. If you do this then you
 | 
			
		||||
> will likely only need one GPS constellation for the whole dimension (such as the Overworld or Nether).
 | 
			
		||||
>
 | 
			
		||||
> If you do use wireless modems then you may find that you need multiple GPS constellations to cover your needs.
 | 
			
		||||
>
 | 
			
		||||
> A computer needs a wireless or ender modem and to be in range of a GPS constellation that is in the same dimension as
 | 
			
		||||
> it to use the GPS API. The reason for this is that ComputerCraft mimics real-life GPS by making use of the distance
 | 
			
		||||
> parameter of [modem messages][`modem_message`] and some maths.
 | 
			
		||||
 | 
			
		||||
Locate where you want to place your GPS constellation. You will need an area at least 6 blocks high, 6 blocks wide, and
 | 
			
		||||
6 blocks deep (6x6x6). If you are using wireless modems then you may want to build your constellation as high as you can
 | 
			
		||||
@@ -79,18 +78,16 @@ To hide Minecraft's debug screen, press <kbd>F3</kbd> again.
 | 
			
		||||
Create similar startup files for the other computers in your constellation, making sure to input the each computer's own
 | 
			
		||||
coordinates.
 | 
			
		||||
 | 
			
		||||
:::caution Modem messages come from the computer's position, not the modem's
 | 
			
		||||
Wireless modems transmit from the block that they are attached to *not* the block space that they occupy, the
 | 
			
		||||
coordinates that you input into your GPS host should be the position of the computer and not the position of the modem.
 | 
			
		||||
:::
 | 
			
		||||
> [Modem messages come from the computer's position, not the modem's][!WARNING]
 | 
			
		||||
> Wireless modems transmit from the block that they are attached to *not* the block space that they occupy, the
 | 
			
		||||
> coordinates that you input into your GPS host should be the position of the computer and not the position of the modem.
 | 
			
		||||
 | 
			
		||||
Congratulations, your constellation is now fully set up! You can test it by placing another computer close by, placing a
 | 
			
		||||
wireless modem on it, and running the `gps locate` program (or calling the @{gps.locate} function).
 | 
			
		||||
wireless modem on it, and running the `gps locate` program (or calling the [`gps.locate`] function).
 | 
			
		||||
 | 
			
		||||
:::info Why use Minecraft's coordinates?
 | 
			
		||||
CC doesn't care if you use Minecraft's coordinate system, so long as all of the GPS hosts with overlapping ranges use
 | 
			
		||||
the same reference point (requesting computers will get confused if hosts have different reference points). However,
 | 
			
		||||
using MC's coordinate system does provide a nice standard to adopt server-wide. It also is consistent with how command
 | 
			
		||||
computers get their location, they use MC's command system to get their block which returns that in MC's coordinate
 | 
			
		||||
system.
 | 
			
		||||
:::
 | 
			
		||||
> [Why use Minecraft's coordinates?][!INFO]
 | 
			
		||||
> CC doesn't care if you use Minecraft's coordinate system, so long as all of the GPS hosts with overlapping ranges use
 | 
			
		||||
> the same reference point (requesting computers will get confused if hosts have different reference points). However,
 | 
			
		||||
> using MC's coordinate system does provide a nice standard to adopt server-wide. It also is consistent with how command
 | 
			
		||||
> computers get their location, they use MC's command system to get their block which returns that in MC's coordinate
 | 
			
		||||
> system.
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
# Playing audio with speakers
 | 
			
		||||
CC: Tweaked's speaker peripheral provides a powerful way to play any audio you like with the @{speaker.playAudio}
 | 
			
		||||
CC: Tweaked's speaker peripheral provides a powerful way to play any audio you like with the [`speaker.playAudio`]
 | 
			
		||||
method. However, for people unfamiliar with digital audio, it's not the most intuitive thing to use. This guide provides
 | 
			
		||||
an introduction to digital audio, demonstrates how to play music with CC: Tweaked's speakers, and then briefly discusses
 | 
			
		||||
the more complex topic of audio processing.
 | 
			
		||||
@@ -60,7 +60,7 @@ sine waves (and why wouldn't you?), you'd need a table with almost 3 _million_.
 | 
			
		||||
up very quickly, and these tables take up more and more memory.
 | 
			
		||||
 | 
			
		||||
Instead of building our entire song (well, sine wave) in one go, we can produce it in small batches, each of which get
 | 
			
		||||
passed off to @{speaker.playAudio} when the time is right. This allows us to build a _stream_ of audio, where we read
 | 
			
		||||
passed off to [`speaker.playAudio`] when the time is right. This allows us to build a _stream_ of audio, where we read
 | 
			
		||||
chunks of audio one at a time (either from a file or a tone generator like above), do some optional processing to each
 | 
			
		||||
one, and then play them.
 | 
			
		||||
 | 
			
		||||
@@ -84,15 +84,15 @@ end
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It looks pretty similar to before, aside from we've wrapped the generation and playing code in a while loop, and added a
 | 
			
		||||
rather odd loop with @{speaker.playAudio} and @{os.pullEvent}.
 | 
			
		||||
rather odd loop with [`speaker.playAudio`] and [`os.pullEvent`].
 | 
			
		||||
 | 
			
		||||
Let's talk about this loop, why do we need to keep calling @{speaker.playAudio}? Remember that what we're trying to do
 | 
			
		||||
Let's talk about this loop, why do we need to keep calling [`speaker.playAudio`]? Remember that what we're trying to do
 | 
			
		||||
here is avoid keeping too much audio in memory at once. However, if we're generating audio quicker than the speakers can
 | 
			
		||||
play it, we're not helping at all - all this audio is still hanging around waiting to be played!
 | 
			
		||||
 | 
			
		||||
In order to avoid this, the speaker rejects any new chunks of audio if its backlog is too large. When this happens,
 | 
			
		||||
@{speaker.playAudio} returns false. Once enough audio has played, and the backlog has been reduced, a
 | 
			
		||||
@{speaker_audio_empty} event is queued, and we can try to play our chunk once more.
 | 
			
		||||
[`speaker.playAudio`] returns false. Once enough audio has played, and the backlog has been reduced, a
 | 
			
		||||
[`speaker_audio_empty`] event is queued, and we can try to play our chunk once more.
 | 
			
		||||
 | 
			
		||||
## Storing audio
 | 
			
		||||
PCM is a fantastic way of representing audio when we want to manipulate it, but it's not very efficient when we want to
 | 
			
		||||
@@ -106,7 +106,7 @@ computer. Instead, we need something much simpler.
 | 
			
		||||
 | 
			
		||||
DFPWM (Dynamic Filter Pulse Width Modulation) is the de facto standard audio format of the ComputerCraft (and
 | 
			
		||||
OpenComputers) world. Originally popularised by the addon mod [Computronics], CC:T now has built-in support for it with
 | 
			
		||||
the @{cc.audio.dfpwm} module. This allows you to read DFPWM files from disk, decode them to PCM, and then play them
 | 
			
		||||
the [`cc.audio.dfpwm`] module. This allows you to read DFPWM files from disk, decode them to PCM, and then play them
 | 
			
		||||
using the speaker.
 | 
			
		||||
 | 
			
		||||
Let's dive in with an example, and we'll explain things afterwards:
 | 
			
		||||
@@ -125,16 +125,16 @@ for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
 | 
			
		||||
end
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Once again, we see the @{speaker.playAudio}/@{speaker_audio_empty} loop. However, the rest of the program is a little
 | 
			
		||||
Once again, we see the [`speaker.playAudio`]/[`speaker_audio_empty`] loop. However, the rest of the program is a little
 | 
			
		||||
different.
 | 
			
		||||
 | 
			
		||||
First, we require the dfpwm module and call @{cc.audio.dfpwm.make_decoder} to construct a new decoder. This decoder
 | 
			
		||||
First, we require the dfpwm module and call [`cc.audio.dfpwm.make_decoder`] to construct a new decoder. This decoder
 | 
			
		||||
accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker.
 | 
			
		||||
 | 
			
		||||
As mentioned above, @{speaker.playAudio} accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
 | 
			
		||||
As mentioned above, [`speaker.playAudio`] accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
 | 
			
		||||
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
 | 
			
		||||
@{io.lines}, which provides a nice way to loop over chunks of a file. You can of course just use @{fs.open} and
 | 
			
		||||
@{fs.BinaryReadHandle.read} if you prefer.
 | 
			
		||||
[`io.lines`], which provides a nice way to loop over chunks of a file. You can of course just use [`fs.open`] and
 | 
			
		||||
[`fs.BinaryReadHandle.read`] if you prefer.
 | 
			
		||||
 | 
			
		||||
## Processing audio
 | 
			
		||||
As mentioned near the beginning of this guide, PCM audio is pretty easy to work with as it's just a list of amplitudes.
 | 
			
		||||
@@ -189,10 +189,9 @@ for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
 | 
			
		||||
end
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::note Confused?
 | 
			
		||||
Don't worry if you don't understand this example. It's quite advanced, and does use some ideas that this guide doesn't
 | 
			
		||||
cover. That said, don't be afraid to ask on [GitHub Discussions] or [IRC] either!
 | 
			
		||||
:::
 | 
			
		||||
> [Confused?][!NOTE]
 | 
			
		||||
> Don't worry if you don't understand this example. It's quite advanced, and does use some ideas that this guide doesn't
 | 
			
		||||
> cover. That said, don't be afraid to ask on [GitHub Discussions] or [IRC] either!
 | 
			
		||||
 | 
			
		||||
It's worth noting that the examples of audio processing we've mentioned here are about manipulating the _amplitude_ of
 | 
			
		||||
the wave. If you wanted to modify the _frequency_ (for instance, shifting the pitch), things get rather more complex.
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ A library is a collection of useful functions and other definitions which is sto
 | 
			
		||||
might want to create a library because you have some functions which are used in multiple programs, or just to split
 | 
			
		||||
your program into multiple more modular files.
 | 
			
		||||
 | 
			
		||||
Let's say we want to create a small library to make working with the @{term|terminal} a little easier. We'll provide two
 | 
			
		||||
Let's say we want to create a small library to make working with the [terminal][`term`] a little easier. We'll provide two
 | 
			
		||||
functions: `reset`, which clears the terminal and sets the cursor to (1, 1), and `write_center`, which prints some text
 | 
			
		||||
in the middle of the screen.
 | 
			
		||||
 | 
			
		||||
@@ -48,32 +48,32 @@ more_term.write_center("Hello, world!")
 | 
			
		||||
When run, this'll clear the screen and print some text in the middle of the first line.
 | 
			
		||||
 | 
			
		||||
## require in depth
 | 
			
		||||
While the previous section is a good introduction to how @{require} operates, there are a couple of remaining points
 | 
			
		||||
While the previous section is a good introduction to how [`require`] operates, there are a couple of remaining points
 | 
			
		||||
which are worth mentioning for more advanced usage.
 | 
			
		||||
 | 
			
		||||
### Libraries can return anything
 | 
			
		||||
In our above example, we return a table containing the functions we want to expose. However, it's worth pointing out
 | 
			
		||||
that you can return ''anything'' from your library - a table, a function or even just a string! @{require} treats them
 | 
			
		||||
that you can return ''anything'' from your library - a table, a function or even just a string! [`require`] treats them
 | 
			
		||||
all the same, and just returns whatever your library provides.
 | 
			
		||||
 | 
			
		||||
### Module resolution and the package path
 | 
			
		||||
In the above examples, we defined our library in a file, and @{require} read from it. While this is what you'll do most
 | 
			
		||||
of the time, it is possible to make @{require} look elsewhere for your library, such as downloading from a website or
 | 
			
		||||
In the above examples, we defined our library in a file, and [`require`] read from it. While this is what you'll do most
 | 
			
		||||
of the time, it is possible to make [`require`] look elsewhere for your library, such as downloading from a website or
 | 
			
		||||
loading from an in-memory library store.
 | 
			
		||||
 | 
			
		||||
As a result, the *module name* you pass to @{require} doesn't correspond to a file path. One common mistake is to load
 | 
			
		||||
As a result, the *module name* you pass to [`require`] doesn't correspond to a file path. One common mistake is to load
 | 
			
		||||
code from a sub-directory using `require("folder/library")` or even `require("folder/library.lua")`, neither of which
 | 
			
		||||
will do quite what you expect.
 | 
			
		||||
 | 
			
		||||
When loading libraries (also referred to as *modules*) from files, @{require} searches along the *@{package.path|module
 | 
			
		||||
path}*. By default, this looks something like:
 | 
			
		||||
When loading libraries (also referred to as *modules*) from files, [`require`] searches along the [*module
 | 
			
		||||
path*][`package.path`]. By default, this looks something like:
 | 
			
		||||
 | 
			
		||||
* `?.lua`
 | 
			
		||||
* `?/init.lua`
 | 
			
		||||
* `/rom/modules/main/?.lua`
 | 
			
		||||
* etc...
 | 
			
		||||
 | 
			
		||||
When you call `require("my_library")`, @{require} replaces the `?` in each element of the path with your module name, and
 | 
			
		||||
When you call `require("my_library")`, [`require`] replaces the `?` in each element of the path with your module name, and
 | 
			
		||||
checks if the file exists. In this case, we'd look for `my_library.lua`, `my_library/init.lua`,
 | 
			
		||||
`/rom/modules/main/my_library.lua` and so on. Note that this works *relative to the current program*, so if your
 | 
			
		||||
program is actually called `folder/program`, then we'll look for `folder/my_library.lua`, etc...
 | 
			
		||||
@@ -86,4 +86,4 @@ before we start looking for the library.
 | 
			
		||||
There are several external resources which go into require in a little more detail:
 | 
			
		||||
 | 
			
		||||
 - The [Lua Module tutorial](http://lua-users.org/wiki/ModulesTutorial) on the Lua wiki.
 | 
			
		||||
 - [Lua's manual section on @{require}](https://www.lua.org/manual/5.1/manual.html#pdf-require).
 | 
			
		||||
 - [Lua's manual section on `require`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
 | 
			
		||||
 
 | 
			
		||||
@@ -15,13 +15,13 @@ CC: Tweaked can be installed from [CurseForge] or [Modrinth]. It runs on both [M
 | 
			
		||||
Controlled using the [Lua programming language][lua], CC: Tweaked's computers provides all the tools you need to start
 | 
			
		||||
writing code and automating your Minecraft world.
 | 
			
		||||
 | 
			
		||||
{.big-image}
 | 
			
		||||
<img alt="A ComputerCraft terminal open and ready to be programmed." src="images/basic-terminal.png" class="big-image" />
 | 
			
		||||
 | 
			
		||||
While computers are incredibly powerful, they're rather limited by their inability to move about. *Turtles* are the
 | 
			
		||||
solution here. They can move about the world, placing and breaking blocks, swinging a sword to protect you from zombies,
 | 
			
		||||
or whatever else you program them to!
 | 
			
		||||
 | 
			
		||||
{.big-image}
 | 
			
		||||
<img alt="A turtle tunneling in Minecraft." src="images/turtle.png" class="big-image" />
 | 
			
		||||
 | 
			
		||||
Not all problems can be solved with a pickaxe though, and so CC: Tweaked also provides a bunch of additional peripherals
 | 
			
		||||
for your computers. You can play a tune with speakers, display text or images on a monitor, connect all your
 | 
			
		||||
@@ -30,7 +30,7 @@ computers together with modems, and much more.
 | 
			
		||||
Computers can now also interact with inventories such as chests, allowing you to build complex inventory and item
 | 
			
		||||
management systems.
 | 
			
		||||
 | 
			
		||||
{.big-image}
 | 
			
		||||
<img alt="A chest's contents being read by a computer and displayed on a monitor." src="images/peripherals.png" class="big-image" />
 | 
			
		||||
 | 
			
		||||
## Getting Started
 | 
			
		||||
While ComputerCraft is lovely for both experienced programmers and for people who have never coded before, it can be a
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								doc/logo-darkmode.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/logo-darkmode.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.3 KiB  | 
@@ -13,22 +13,20 @@ include standard Lua functions.
 | 
			
		||||
 | 
			
		||||
As it waits for a fixed amount of world ticks, `time` will automatically be
 | 
			
		||||
rounded up to the nearest multiple of 0.05 seconds. If you are using coroutines
 | 
			
		||||
or the @{parallel|parallel API}, it will only pause execution of the current
 | 
			
		||||
or the [parallel API][`parallel`], it will only pause execution of the current
 | 
			
		||||
thread, not the whole program.
 | 
			
		||||
 | 
			
		||||
:::tip
 | 
			
		||||
Because sleep internally uses timers, it is a function that yields. This means
 | 
			
		||||
that you can use it to prevent "Too long without yielding" errors. However, as
 | 
			
		||||
the minimum sleep time is 0.05 seconds, it will slow your program down.
 | 
			
		||||
:::
 | 
			
		||||
> [!TIP]
 | 
			
		||||
> Because sleep internally uses timers, it is a function that yields. This means
 | 
			
		||||
> that you can use it to prevent "Too long without yielding" errors. However, as
 | 
			
		||||
> the minimum sleep time is 0.05 seconds, it will slow your program down.
 | 
			
		||||
 | 
			
		||||
:::caution
 | 
			
		||||
Internally, this function queues and waits for a timer event (using
 | 
			
		||||
@{os.startTimer}), however it does not listen for any other events. This means
 | 
			
		||||
that any event that occurs while sleeping will be entirely discarded. If you
 | 
			
		||||
need to receive events while sleeping, consider using @{os.startTimer|timers},
 | 
			
		||||
or the @{parallel|parallel API}.
 | 
			
		||||
:::
 | 
			
		||||
> [!WARNING]
 | 
			
		||||
> Internally, this function queues and waits for a timer event (using
 | 
			
		||||
> [`os.startTimer`]), however it does not listen for any other events. This means
 | 
			
		||||
> that any event that occurs while sleeping will be entirely discarded. If you
 | 
			
		||||
> need to receive events while sleeping, consider using [timers][`os.startTimer`],
 | 
			
		||||
> or the [parallel API][`parallel`].
 | 
			
		||||
 | 
			
		||||
@tparam number time The number of seconds to sleep for, rounded up to the
 | 
			
		||||
nearest multiple of 0.05.
 | 
			
		||||
@@ -116,7 +114,7 @@ function read(replaceChar, history, completeFn, default) end
 | 
			
		||||
 | 
			
		||||
--- Stores the current ComputerCraft and Minecraft versions.
 | 
			
		||||
--
 | 
			
		||||
-- Outside of Minecraft (for instance, in an emulator) @{_HOST} will contain the
 | 
			
		||||
-- Outside of Minecraft (for instance, in an emulator) [`_HOST`] will contain the
 | 
			
		||||
-- emulator's version instead.
 | 
			
		||||
--
 | 
			
		||||
-- For example, `ComputerCraft 1.93.0 (Minecraft 1.15.2)`.
 | 
			
		||||
 
 | 
			
		||||
@@ -15,27 +15,27 @@ variables and functions exported by it will by available through the use of
 | 
			
		||||
@deprecated When possible it's best to avoid using this function. It pollutes
 | 
			
		||||
the global table and can mask errors.
 | 
			
		||||
 | 
			
		||||
@{require} should be used to load libraries instead.
 | 
			
		||||
[`require`] should be used to load libraries instead.
 | 
			
		||||
]]
 | 
			
		||||
function loadAPI(path) end
 | 
			
		||||
 | 
			
		||||
--- Unloads an API which was loaded by @{os.loadAPI}.
 | 
			
		||||
--- Unloads an API which was loaded by [`os.loadAPI`].
 | 
			
		||||
--
 | 
			
		||||
-- This effectively removes the specified table from `_G`.
 | 
			
		||||
--
 | 
			
		||||
-- @tparam string name The name of the API to unload.
 | 
			
		||||
-- @since 1.2
 | 
			
		||||
-- @deprecated See @{os.loadAPI} for why.
 | 
			
		||||
-- @deprecated See [`os.loadAPI`] for why.
 | 
			
		||||
function unloadAPI(name) end
 | 
			
		||||
 | 
			
		||||
--[[- Pause execution of the current thread and waits for any events matching
 | 
			
		||||
`filter`.
 | 
			
		||||
 | 
			
		||||
This function @{coroutine.yield|yields} the current process and waits for it
 | 
			
		||||
This function [yields][`coroutine.yield`] the current process and waits for it
 | 
			
		||||
to be resumed with a vararg list where the first element matches `filter`.
 | 
			
		||||
If no `filter` is supplied, this will match all events.
 | 
			
		||||
 | 
			
		||||
Unlike @{os.pullEventRaw}, it will stop the application upon a "terminate"
 | 
			
		||||
Unlike [`os.pullEventRaw`], it will stop the application upon a "terminate"
 | 
			
		||||
event, printing the error "Terminated".
 | 
			
		||||
 | 
			
		||||
@tparam[opt] string filter Event to filter for.
 | 
			
		||||
@@ -69,7 +69,7 @@ function pullEvent(filter) end
 | 
			
		||||
--[[- Pause execution of the current thread and waits for events, including the
 | 
			
		||||
`terminate` event.
 | 
			
		||||
 | 
			
		||||
This behaves almost the same as @{os.pullEvent}, except it allows you to handle
 | 
			
		||||
This behaves almost the same as [`os.pullEvent`], except it allows you to handle
 | 
			
		||||
the `terminate` event yourself - the program will not stop execution when
 | 
			
		||||
<kbd>Ctrl+T</kbd> is pressed.
 | 
			
		||||
 | 
			
		||||
@@ -89,7 +89,7 @@ the `terminate` event yourself - the program will not stop execution when
 | 
			
		||||
]]
 | 
			
		||||
function pullEventRaw(filter) end
 | 
			
		||||
 | 
			
		||||
--- Pauses execution for the specified number of seconds, alias of @{_G.sleep}.
 | 
			
		||||
--- Pauses execution for the specified number of seconds, alias of [`_G.sleep`].
 | 
			
		||||
--
 | 
			
		||||
-- @tparam number time The number of seconds to sleep for, rounded up to the
 | 
			
		||||
-- nearest multiple of 0.05.
 | 
			
		||||
@@ -109,12 +109,12 @@ arguments.
 | 
			
		||||
 | 
			
		||||
This function does not resolve program names like the shell does. This means
 | 
			
		||||
that, for example, `os.run("edit")` will not work. As well as this, it does not
 | 
			
		||||
provide access to the @{shell} API in the environment. For this behaviour, use
 | 
			
		||||
@{shell.run} instead.
 | 
			
		||||
provide access to the [`shell`] API in the environment. For this behaviour, use
 | 
			
		||||
[`shell.run`] instead.
 | 
			
		||||
 | 
			
		||||
If the program cannot be found, or failed to run, it will print the error and
 | 
			
		||||
return `false`. If you want to handle this more gracefully, use an alternative
 | 
			
		||||
such as @{loadfile}.
 | 
			
		||||
such as [`loadfile`].
 | 
			
		||||
 | 
			
		||||
@tparam table env The environment to run the program with.
 | 
			
		||||
@tparam string path The exact path of the program to run.
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
 | 
			
		||||
 | 
			
		||||
# Mod properties
 | 
			
		||||
isUnstable=false
 | 
			
		||||
modVersion=1.105.0
 | 
			
		||||
modVersion=1.108.3
 | 
			
		||||
 | 
			
		||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
 | 
			
		||||
mcVersion=1.20.1
 | 
			
		||||
 
 | 
			
		||||
@@ -7,20 +7,20 @@
 | 
			
		||||
# Minecraft
 | 
			
		||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
 | 
			
		||||
# Remember to update corresponding versions in fabric.mod.json/mods.toml
 | 
			
		||||
fabric-api = "0.83.1+1.20.1"
 | 
			
		||||
fabric-api = "0.86.1+1.20.1"
 | 
			
		||||
fabric-loader = "0.14.21"
 | 
			
		||||
forge = "47.0.1"
 | 
			
		||||
forge = "47.1.0"
 | 
			
		||||
forgeSpi = "6.0.0"
 | 
			
		||||
mixin = "0.8.5"
 | 
			
		||||
parchment = "2023.03.12"
 | 
			
		||||
parchmentMc = "1.19.3"
 | 
			
		||||
parchment = "2023.08.20"
 | 
			
		||||
parchmentMc = "1.20.1"
 | 
			
		||||
 | 
			
		||||
# Normal dependencies
 | 
			
		||||
asm = "9.3"
 | 
			
		||||
autoService = "1.0.1"
 | 
			
		||||
asm = "9.5"
 | 
			
		||||
autoService = "1.1.1"
 | 
			
		||||
checkerFramework = "3.32.0"
 | 
			
		||||
cobalt = "0.7.0"
 | 
			
		||||
cobalt-next = "0.7.1" # Not a real version, used to constrain the version we accept.
 | 
			
		||||
cobalt = "0.7.3"
 | 
			
		||||
cobalt-next = "0.7.4" # Not a real version, used to constrain the version we accept.
 | 
			
		||||
fastutil = "8.5.9"
 | 
			
		||||
guava = "31.1-jre"
 | 
			
		||||
jetbrainsAnnotations = "24.0.1"
 | 
			
		||||
@@ -29,47 +29,52 @@ jzlib = "1.1.3"
 | 
			
		||||
kotlin = "1.8.10"
 | 
			
		||||
kotlin-coroutines = "1.6.4"
 | 
			
		||||
netty = "4.1.82.Final"
 | 
			
		||||
nightConfig = "3.6.5"
 | 
			
		||||
slf4j = "1.7.36"
 | 
			
		||||
nightConfig = "3.6.7"
 | 
			
		||||
slf4j = "2.0.1"
 | 
			
		||||
 | 
			
		||||
# Minecraft mods
 | 
			
		||||
iris = "1.5.2+1.19.4"
 | 
			
		||||
jei = "13.1.0.11"
 | 
			
		||||
modmenu = "6.1.0-rc.1"
 | 
			
		||||
emi = "1.0.8+1.20.1"
 | 
			
		||||
fabricPermissions = "0.3.20230723"
 | 
			
		||||
iris = "1.6.4+1.20"
 | 
			
		||||
jei = "15.2.0.22"
 | 
			
		||||
modmenu = "7.1.0"
 | 
			
		||||
moreRed = "4.0.0.4"
 | 
			
		||||
oculus = "1.2.5"
 | 
			
		||||
rei = "10.0.578"
 | 
			
		||||
rei = "12.0.626"
 | 
			
		||||
rubidium = "0.6.1"
 | 
			
		||||
sodium = "mc1.19.4-0.4.10"
 | 
			
		||||
sodium = "mc1.20-0.4.10"
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
byteBuddy = "1.14.2"
 | 
			
		||||
byteBuddy = "1.14.7"
 | 
			
		||||
hamcrest = "2.2"
 | 
			
		||||
jqwik = "1.7.2"
 | 
			
		||||
junit = "5.9.2"
 | 
			
		||||
jqwik = "1.7.4"
 | 
			
		||||
junit = "5.10.0"
 | 
			
		||||
 | 
			
		||||
# Build tools
 | 
			
		||||
cctJavadoc = "1.7.0"
 | 
			
		||||
checkstyle = "10.3.4"
 | 
			
		||||
cctJavadoc = "1.8.0"
 | 
			
		||||
checkstyle = "10.12.3"
 | 
			
		||||
curseForgeGradle = "1.0.14"
 | 
			
		||||
errorProne-core = "2.18.0"
 | 
			
		||||
errorProne-plugin = "3.0.1"
 | 
			
		||||
fabric-loom = "1.1.10"
 | 
			
		||||
forgeGradle = "5.1.+"
 | 
			
		||||
githubRelease = "2.2.12"
 | 
			
		||||
ideaExt = "1.1.6"
 | 
			
		||||
illuaminate = "0.1.0-24-gdb28902"
 | 
			
		||||
errorProne-core = "2.21.1"
 | 
			
		||||
errorProne-plugin = "3.1.0"
 | 
			
		||||
fabric-loom = "1.3.7"
 | 
			
		||||
forgeGradle = "6.0.8"
 | 
			
		||||
githubRelease = "2.4.1"
 | 
			
		||||
ideaExt = "1.1.7"
 | 
			
		||||
illuaminate = "0.1.0-44-g9ee0055"
 | 
			
		||||
librarian = "1.+"
 | 
			
		||||
minotaur = "2.+"
 | 
			
		||||
mixinGradle = "0.7.+"
 | 
			
		||||
nullAway = "0.9.9"
 | 
			
		||||
quiltflower = "1.8.0"
 | 
			
		||||
spotless = "6.17.0"
 | 
			
		||||
spotless = "6.21.0"
 | 
			
		||||
taskTree = "2.1.1"
 | 
			
		||||
vanillaGradle = "0.2.1-SNAPSHOT"
 | 
			
		||||
vineflower = "1.11.0"
 | 
			
		||||
teavm = "0.9.0-SQUID.1"
 | 
			
		||||
 | 
			
		||||
[libraries]
 | 
			
		||||
# Normal dependencies
 | 
			
		||||
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
 | 
			
		||||
asm-commons = { module = "org.ow2.asm:asm-commons", 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" }
 | 
			
		||||
@@ -92,12 +97,15 @@ 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" }
 | 
			
		||||
fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
 | 
			
		||||
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
 | 
			
		||||
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
 | 
			
		||||
jei-api = { module = "mezz.jei:jei-1.19.4-common-api", version.ref = "jei" }
 | 
			
		||||
jei-fabric = { module = "mezz.jei:jei-1.19.4-fabric", version.ref = "jei" }
 | 
			
		||||
jei-forge = { module = "mezz.jei:jei-1.19.4-forge", version.ref = "jei" }
 | 
			
		||||
jei-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" }
 | 
			
		||||
jei-fabric = { module = "mezz.jei:jei-1.20.1-fabric", version.ref = "jei" }
 | 
			
		||||
jei-forge = { module = "mezz.jei:jei-1.20.1-forge", version.ref = "jei" }
 | 
			
		||||
mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" }
 | 
			
		||||
modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" }
 | 
			
		||||
moreRed = { module = "commoble.morered:morered-1.20.1", version.ref = "moreRed" }
 | 
			
		||||
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" }
 | 
			
		||||
@@ -131,9 +139,17 @@ kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.
 | 
			
		||||
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
 | 
			
		||||
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
 | 
			
		||||
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" }
 | 
			
		||||
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
 | 
			
		||||
teavm-jso = { module = "org.teavm:teavm-jso", version.ref = "teavm" }
 | 
			
		||||
teavm-jso-apis = { module = "org.teavm:teavm-jso-apis", version.ref = "teavm" }
 | 
			
		||||
teavm-jso-impl = { module = "org.teavm:teavm-jso-impl", version.ref = "teavm" }
 | 
			
		||||
teavm-metaprogramming-api = { module = "org.teavm:teavm-metaprogramming-api", version.ref = "teavm" }
 | 
			
		||||
teavm-metaprogramming-impl = { module = "org.teavm:teavm-metaprogramming-impl", version.ref = "teavm" }
 | 
			
		||||
teavm-platform = { module = "org.teavm:teavm-platform", version.ref = "teavm" }
 | 
			
		||||
teavm-tooling = { module = "org.teavm:teavm-tooling", version.ref = "teavm" }
 | 
			
		||||
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
 | 
			
		||||
vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vineflower" }
 | 
			
		||||
 | 
			
		||||
[plugins]
 | 
			
		||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
 | 
			
		||||
@@ -145,16 +161,21 @@ mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
 | 
			
		||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
 | 
			
		||||
 | 
			
		||||
[bundles]
 | 
			
		||||
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
 | 
			
		||||
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
 | 
			
		||||
 | 
			
		||||
# Minecraft
 | 
			
		||||
externalMods-common = ["jei-api", "nightConfig-core", "nightConfig-toml"]
 | 
			
		||||
externalMods-forge-compile = ["oculus", "jei-api"]
 | 
			
		||||
externalMods-forge-runtime = []
 | 
			
		||||
externalMods-forge-compile = ["moreRed", "oculus", "jei-api"]
 | 
			
		||||
externalMods-forge-runtime = ["jei-forge"]
 | 
			
		||||
externalMods-fabric = ["nightConfig-core", "nightConfig-toml"]
 | 
			
		||||
externalMods-fabric-compile = ["iris", "jei-api", "rei-api", "rei-builtin"]
 | 
			
		||||
externalMods-fabric-runtime = []
 | 
			
		||||
externalMods-fabric-compile = ["fabricPermissions", "iris", "jei-api", "rei-api", "rei-builtin"]
 | 
			
		||||
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
 | 
			
		||||
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
 | 
			
		||||
 | 
			
		||||
# Build tools
 | 
			
		||||
teavm-api = [ "teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api" ]
 | 
			
		||||
teavm-tooling = [ "teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl" ]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										3
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,6 @@
 | 
			
		||||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
 | 
			
		||||
networkTimeout=10000
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							@@ -55,7 +55,7 @@
 | 
			
		||||
#       Darwin, MinGW, and NonStop.
 | 
			
		||||
#
 | 
			
		||||
#   (3) This script is generated from the Groovy template
 | 
			
		||||
#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 | 
			
		||||
#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 | 
			
		||||
#       within the Gradle project.
 | 
			
		||||
#
 | 
			
		||||
#       You can find Gradle at https://github.com/gradle/gradle/.
 | 
			
		||||
@@ -80,13 +80,10 @@ do
 | 
			
		||||
    esac
 | 
			
		||||
done
 | 
			
		||||
 | 
			
		||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
 | 
			
		||||
 | 
			
		||||
APP_NAME="Gradle"
 | 
			
		||||
# This is normally unused
 | 
			
		||||
# shellcheck disable=SC2034
 | 
			
		||||
APP_BASE_NAME=${0##*/}
 | 
			
		||||
 | 
			
		||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 | 
			
		||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 | 
			
		||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
 | 
			
		||||
 | 
			
		||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
 | 
			
		||||
MAX_FD=maximum
 | 
			
		||||
@@ -143,12 +140,16 @@ fi
 | 
			
		||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
 | 
			
		||||
    case $MAX_FD in #(
 | 
			
		||||
      max*)
 | 
			
		||||
        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
 | 
			
		||||
        # shellcheck disable=SC3045
 | 
			
		||||
        MAX_FD=$( ulimit -H -n ) ||
 | 
			
		||||
            warn "Could not query maximum file descriptor limit"
 | 
			
		||||
    esac
 | 
			
		||||
    case $MAX_FD in  #(
 | 
			
		||||
      '' | soft) :;; #(
 | 
			
		||||
      *)
 | 
			
		||||
        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
 | 
			
		||||
        # shellcheck disable=SC3045
 | 
			
		||||
        ulimit -n "$MAX_FD" ||
 | 
			
		||||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
 | 
			
		||||
    esac
 | 
			
		||||
@@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then
 | 
			
		||||
    done
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 | 
			
		||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 | 
			
		||||
 | 
			
		||||
# Collect all arguments for the java command;
 | 
			
		||||
#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
 | 
			
		||||
#     shell script including quotes and variable substitutions, so put them in
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
 | 
			
		||||
 | 
			
		||||
set DIRNAME=%~dp0
 | 
			
		||||
if "%DIRNAME%"=="" set DIRNAME=.
 | 
			
		||||
@rem This is normally unused
 | 
			
		||||
set APP_BASE_NAME=%~n0
 | 
			
		||||
set APP_HOME=%DIRNAME%
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,7 @@
 | 
			
		||||
  /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)
 | 
			
		||||
 | 
			
		||||
  /projects/web/src/frontend/mount)
 | 
			
		||||
 | 
			
		||||
(doc
 | 
			
		||||
  ; Also defined in projects/web/build.gradle.kts
 | 
			
		||||
@@ -24,7 +23,7 @@
 | 
			
		||||
    (url https://tweaked.cc/)
 | 
			
		||||
    (source-link https://github.com/cc-tweaked/CC-Tweaked/blob/${commit}/${path}#L${line})
 | 
			
		||||
 | 
			
		||||
    (styles /projects/web/src/styles.css)
 | 
			
		||||
    (styles  /projects/web/build/rollup/index.css)
 | 
			
		||||
    (scripts /projects/web/build/rollup/index.js)
 | 
			
		||||
    (head doc/head.html))
 | 
			
		||||
 | 
			
		||||
@@ -50,6 +49,8 @@
 | 
			
		||||
(at /
 | 
			
		||||
  (linters
 | 
			
		||||
    syntax:string-index
 | 
			
		||||
    doc:docusaurus-admonition
 | 
			
		||||
    doc:ldoc-reference
 | 
			
		||||
 | 
			
		||||
    ;; It'd be nice to avoid this, but right now there's a lot of instances of
 | 
			
		||||
    ;; it.
 | 
			
		||||
@@ -82,23 +83,19 @@
 | 
			
		||||
      ;; isn't smart enough.
 | 
			
		||||
      sleep write printError read rs)))
 | 
			
		||||
 | 
			
		||||
;; We disable the unused global linter in bios.lua and the APIs. In the future
 | 
			
		||||
;; hopefully we'll get illuaminate to handle this.
 | 
			
		||||
;; We disable the unused global linter in bios.lua, APIs and our documentation
 | 
			
		||||
;; stubs docs. In the future hopefully we'll get illuaminate to handle this.
 | 
			
		||||
(at
 | 
			
		||||
  (/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/ /projects/forge/build/docs/luaJavadoc/)
 | 
			
		||||
  (/doc/stub/
 | 
			
		||||
   /projects/core/src/main/resources/data/computercraft/lua/bios.lua
 | 
			
		||||
   /projects/core/src/main/resources/data/computercraft/lua/rom/apis/
 | 
			
		||||
   /projects/forge/build/docs/luaJavadoc/)
 | 
			
		||||
  (linters -var:unused-global)
 | 
			
		||||
  (lint (allow-toplevel-global true)))
 | 
			
		||||
 | 
			
		||||
;; Suppress warnings for currently undocumented modules.
 | 
			
		||||
(at
 | 
			
		||||
  (; Lua APIs
 | 
			
		||||
   /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))
 | 
			
		||||
@@ -118,4 +115,4 @@
 | 
			
		||||
      :max sleep write
 | 
			
		||||
      cct_test describe expect howlci fail it pending stub before_each)))
 | 
			
		||||
 | 
			
		||||
(at /projects/web/src/mount/expr_template.lua (lint (globals :max __expr__)))
 | 
			
		||||
(at /projects/web/src/frontend/mount/expr_template.lua (lint (globals :max __expr__)))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3351
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3351
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										26
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								package.json
									
									
									
									
									
								
							@@ -6,24 +6,24 @@
 | 
			
		||||
  "license": "BSD-3-Clause",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@squid-dev/cc-web-term": "^2.0.0",
 | 
			
		||||
    "preact": "^10.5.5",
 | 
			
		||||
    "setimmediate": "^1.0.5",
 | 
			
		||||
    "tslib": "^2.0.3"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@rollup/plugin-terser": "^0.4.0",
 | 
			
		||||
    "@rollup/plugin-node-resolve": "^15.2.1",
 | 
			
		||||
    "@rollup/plugin-typescript": "^11.0.0",
 | 
			
		||||
    "@rollup/plugin-url": "^8.0.1",
 | 
			
		||||
    "@types/glob": "^8.1.0",
 | 
			
		||||
    "@types/react-dom": "^18.0.5",
 | 
			
		||||
    "glob": "^9.3.0",
 | 
			
		||||
    "react-dom": "^18.1.0",
 | 
			
		||||
    "react": "^18.1.0",
 | 
			
		||||
    "rehype-highlight": "^6.0.0",
 | 
			
		||||
    "rehype-react": "^7.1.1",
 | 
			
		||||
    "rehype": "^12.0.1",
 | 
			
		||||
    "requirejs": "^2.3.6",
 | 
			
		||||
    "rollup": "^3.19.1",
 | 
			
		||||
    "ts-node": "^10.8.0",
 | 
			
		||||
    "typescript": "^4.0.5"
 | 
			
		||||
    "@swc/core": "^1.3.92",
 | 
			
		||||
    "@types/node": "^20.8.3",
 | 
			
		||||
    "lightningcss": "^1.22.0",
 | 
			
		||||
    "preact-render-to-string": "^6.2.1",
 | 
			
		||||
    "rehype": "^13.0.0",
 | 
			
		||||
    "rehype-highlight": "^7.0.0",
 | 
			
		||||
    "rehype-react": "^8.0.0",
 | 
			
		||||
    "rollup": "^4.0.0",
 | 
			
		||||
    "tsx": "^3.12.10",
 | 
			
		||||
    "typescript": "^5.2.2"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,13 @@ 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.client.resources.model.UnbakedModel;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides models for a {@link ITurtleUpgrade}.
 | 
			
		||||
@@ -28,15 +32,45 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
     * 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 turtle  Access to the turtle that the upgrade resides on. This will be null when getting item models, unless
 | 
			
		||||
     *                {@link #getModel(ITurtleUpgrade, CompoundTag, TurtleSide)} is overriden.
 | 
			
		||||
     * @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}.
 | 
			
		||||
     * Obtain the model to be used when rendering a turtle peripheral.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This is used when rendering the turtle's item model, and so no {@link ITurtleAccess} is available.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade The upgrade that you're getting the model for.
 | 
			
		||||
     * @param data    Upgrade data instance for current turtle side.
 | 
			
		||||
     * @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.
 | 
			
		||||
     */
 | 
			
		||||
    default TransformedModel getModel(T upgrade, CompoundTag data, TurtleSide side) {
 | 
			
		||||
        return getModel(upgrade, (ITurtleAccess) null, side);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a list of models that this turtle modeller depends on.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Models included in this list will be loaded and baked alongside item and block models, and so may be referenced
 | 
			
		||||
     * by {@link TransformedModel#of(ResourceLocation)}. You do not need to override this method if you will load models
 | 
			
		||||
     * by other means.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A list of models that this modeller depends on.
 | 
			
		||||
     * @see UnbakedModel#getDependencies()
 | 
			
		||||
     */
 | 
			
		||||
    default Collection<ResourceLocation> getDependencies() {
 | 
			
		||||
        return List.of();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A basic {@link TurtleUpgradeModeller} which renders using the upgrade's {@linkplain ITurtleUpgrade#getUpgradeItem(CompoundTag)}
 | 
			
		||||
     * upgrade 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.
 | 
			
		||||
@@ -46,7 +80,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> flatItem() {
 | 
			
		||||
        return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.FLAT_ITEM;
 | 
			
		||||
        return (TurtleUpgradeModeller<T>) TurtleUpgradeModellers.UPGRADE_ITEM;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -58,7 +92,8 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
     * @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);
 | 
			
		||||
        // TODO(1.21.0): Remove this.
 | 
			
		||||
        return sided((ResourceLocation) left, right);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -70,6 +105,16 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
 | 
			
		||||
     * @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);
 | 
			
		||||
        return new TurtleUpgradeModeller<>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
 | 
			
		||||
                return TransformedModel.of(side == TurtleSide.LEFT ? left : right);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public Collection<ResourceLocation> getDependencies() {
 | 
			
		||||
                return List.of(left, right);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,13 +6,20 @@ package dan200.computercraft.api.client.turtle;
 | 
			
		||||
 | 
			
		||||
import com.mojang.math.Transformation;
 | 
			
		||||
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.impl.client.ClientPlatformHelper;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
 | 
			
		||||
class TurtleUpgradeModellers {
 | 
			
		||||
    private static final Transformation leftTransform = getMatrixFor(-0.40625f);
 | 
			
		||||
    private static final Transformation rightTransform = getMatrixFor(0.40625f);
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
final class TurtleUpgradeModellers {
 | 
			
		||||
    private static final Transformation leftTransform = getMatrixFor(-0.4065f);
 | 
			
		||||
    private static final Transformation rightTransform = getMatrixFor(0.4065f);
 | 
			
		||||
 | 
			
		||||
    private static Transformation getMatrixFor(float offset) {
 | 
			
		||||
        var matrix = new Matrix4f();
 | 
			
		||||
@@ -26,6 +33,23 @@ class TurtleUpgradeModellers {
 | 
			
		||||
        return new Transformation(matrix);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->
 | 
			
		||||
        TransformedModel.of(upgrade.getCraftingItem(), side == TurtleSide.LEFT ? leftTransform : rightTransform);
 | 
			
		||||
    static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
 | 
			
		||||
 | 
			
		||||
    private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
 | 
			
		||||
        @Override
 | 
			
		||||
        public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side) {
 | 
			
		||||
            return getModel(turtle == null ? upgrade.getCraftingItem() : upgrade.getUpgradeItem(turtle.getUpgradeNBTData(side)), side);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public TransformedModel getModel(ITurtleUpgrade upgrade, CompoundTag data, TurtleSide side) {
 | 
			
		||||
            return getModel(upgrade.getUpgradeItem(data), side);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private TransformedModel getModel(ItemStack stack, TurtleSide side) {
 | 
			
		||||
            var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
 | 
			
		||||
            if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
 | 
			
		||||
            return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
package dan200.computercraft.impl.client;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.impl.Services;
 | 
			
		||||
import net.minecraft.client.renderer.RenderType;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelManager;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
@@ -24,6 +25,15 @@ public interface ClientPlatformHelper {
 | 
			
		||||
     */
 | 
			
		||||
    BakedModel getModel(ModelManager manager, ResourceLocation location);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Wrap this model in a version which renders a foil/enchantment glint.
 | 
			
		||||
     *
 | 
			
		||||
     * @param model The model to wrap.
 | 
			
		||||
     * @return The wrapped model.
 | 
			
		||||
     * @see RenderType#glint()
 | 
			
		||||
     */
 | 
			
		||||
    BakedModel createdFoiledModel(BakedModel model);
 | 
			
		||||
 | 
			
		||||
    static ClientPlatformHelper get() {
 | 
			
		||||
        var instance = Instance.INSTANCE;
 | 
			
		||||
        return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,14 @@ public class ComputerCraftTags {
 | 
			
		||||
        public static final TagKey<Block> WIRED_MODEM = make("wired_modem");
 | 
			
		||||
        public static final TagKey<Block> MONITOR = make("monitor");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Blocks which should be ignored by a {@code peripheral_hub} peripheral.
 | 
			
		||||
         * <p>
 | 
			
		||||
         * This should include blocks which themselves expose a peripheral hub (such as {@linkplain #WIRED_MODEM wired
 | 
			
		||||
         * modems}).
 | 
			
		||||
         */
 | 
			
		||||
        public static final TagKey<Block> PERIPHERAL_HUB_IGNORE = make("peripheral_hub_ignore");
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Blocks which can be broken by any turtle tool.
 | 
			
		||||
         */
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,11 @@
 | 
			
		||||
package dan200.computercraft.api.pocket;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.entity.Entity;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
@@ -69,6 +71,8 @@ public interface IPocketAccess {
 | 
			
		||||
     *
 | 
			
		||||
     * @return The upgrade's NBT.
 | 
			
		||||
     * @see #updateUpgradeNBTData()
 | 
			
		||||
     * @see UpgradeBase#getUpgradeItem(CompoundTag)
 | 
			
		||||
     * @see UpgradeBase#getUpgradeData(ItemStack)
 | 
			
		||||
     */
 | 
			
		||||
    CompoundTag getUpgradeNBTData();
 | 
			
		||||
 | 
			
		||||
@@ -80,7 +84,10 @@ public interface IPocketAccess {
 | 
			
		||||
    void updateUpgradeNBTData();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove the current peripheral and create a new one. You may wish to do this if the methods available change.
 | 
			
		||||
     * Remove the current peripheral and create a new one.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * You may wish to do this if the methods available change, for instance when the {@linkplain #getEntity() owning
 | 
			
		||||
     * entity} changes.
 | 
			
		||||
     */
 | 
			
		||||
    void invalidatePeripheral();
 | 
			
		||||
 | 
			
		||||
@@ -88,6 +95,8 @@ public interface IPocketAccess {
 | 
			
		||||
     * Get a list of all upgrades for the pocket computer.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A collection of all upgrade names.
 | 
			
		||||
     * @deprecated This is a relic of a previous API, which no longer makes sense with newer versions of ComputerCraft.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    Map<ResourceLocation, IPeripheral> getUpgrades();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,13 @@ import com.mojang.authlib.GameProfile;
 | 
			
		||||
import dan200.computercraft.api.lua.ILuaCallback;
 | 
			
		||||
import dan200.computercraft.api.lua.MethodResult;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import net.minecraft.core.BlockPos;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.world.Container;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.Level;
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus;
 | 
			
		||||
 | 
			
		||||
@@ -221,23 +224,51 @@ public interface ITurtleAccess {
 | 
			
		||||
    void playAnimation(TurtleAnimation animation);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the turtle on the specified side of the turtle, if there is one.
 | 
			
		||||
     * Returns the upgrade on the specified side of the turtle, if there is one.
 | 
			
		||||
     *
 | 
			
		||||
     * @param side The side to get the upgrade from.
 | 
			
		||||
     * @return The upgrade on the specified side of the turtle, if there is one.
 | 
			
		||||
     * @see #setUpgrade(TurtleSide, ITurtleUpgrade)
 | 
			
		||||
     * @see #getUpgradeWithData(TurtleSide)
 | 
			
		||||
     * @see #setUpgradeWithData(TurtleSide, UpgradeData)
 | 
			
		||||
     */
 | 
			
		||||
    @Nullable
 | 
			
		||||
    ITurtleUpgrade getUpgrade(TurtleSide side);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the upgrade on the specified side of the turtle, along with its {@linkplain #getUpgradeNBTData(TurtleSide)
 | 
			
		||||
     * update data}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param side The side to get the upgrade from.
 | 
			
		||||
     * @return The upgrade on the specified side of the turtle, along with its upgrade data, if there is one.
 | 
			
		||||
     * @see #getUpgradeWithData(TurtleSide)
 | 
			
		||||
     * @see #setUpgradeWithData(TurtleSide, UpgradeData)
 | 
			
		||||
     */
 | 
			
		||||
    default @Nullable UpgradeData<ITurtleUpgrade> getUpgradeWithData(TurtleSide side) {
 | 
			
		||||
        var upgrade = getUpgrade(side);
 | 
			
		||||
        return upgrade == null ? null : UpgradeData.of(upgrade, getUpgradeNBTData(side));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the upgrade for a given side, resetting peripherals and clearing upgrade specific data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param side    The side to set the upgrade on.
 | 
			
		||||
     * @param upgrade The upgrade to set, may be {@code null} to clear.
 | 
			
		||||
     * @see #getUpgrade(TurtleSide)
 | 
			
		||||
     * @deprecated Use {@link #setUpgradeWithData(TurtleSide, UpgradeData)}
 | 
			
		||||
     */
 | 
			
		||||
    void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade);
 | 
			
		||||
    @Deprecated
 | 
			
		||||
    default void setUpgrade(TurtleSide side, @Nullable ITurtleUpgrade upgrade) {
 | 
			
		||||
        setUpgradeWithData(side, upgrade == null ? null : UpgradeData.ofDefault(upgrade));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the upgrade for a given side and its upgrade data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param side    The side to set the upgrade on.
 | 
			
		||||
     * @param upgrade The upgrade to set, may be {@code null} to clear.
 | 
			
		||||
     * @see #getUpgradeWithData(TurtleSide)
 | 
			
		||||
     */
 | 
			
		||||
    void setUpgradeWithData(TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the peripheral created by the upgrade on the specified side of the turtle, if there is one.
 | 
			
		||||
@@ -257,6 +288,8 @@ public interface ITurtleAccess {
 | 
			
		||||
     * @param side The side to get the upgrade data for.
 | 
			
		||||
     * @return The upgrade-specific data.
 | 
			
		||||
     * @see #updateUpgradeNBTData(TurtleSide)
 | 
			
		||||
     * @see UpgradeBase#getUpgradeItem(CompoundTag)
 | 
			
		||||
     * @see UpgradeBase#getUpgradeData(ItemStack)
 | 
			
		||||
     */
 | 
			
		||||
    CompoundTag getUpgradeNBTData(TurtleSide side);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package dan200.computercraft.api.turtle;
 | 
			
		||||
import dan200.computercraft.api.peripheral.IPeripheral;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeBase;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
@@ -79,4 +80,17 @@ public interface ITurtleUpgrade extends UpgradeBase {
 | 
			
		||||
     */
 | 
			
		||||
    default void update(ITurtleAccess turtle, TurtleSide side) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get upgrade data that should be persisted when the turtle was broken.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This method should be overridden when you don't need to store all upgrade data by default. For instance, if you
 | 
			
		||||
     * store peripheral state in the upgrade data, which should be lost when the turtle is broken.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgradeData Data that currently stored for this upgrade
 | 
			
		||||
     * @return Filtered version of this data.
 | 
			
		||||
     */
 | 
			
		||||
    default CompoundTag getPersistedData(CompoundTag upgradeData) {
 | 
			
		||||
        return upgradeData;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.turtle;
 | 
			
		||||
 | 
			
		||||
import net.minecraft.util.StringRepresentable;
 | 
			
		||||
import net.minecraft.world.entity.EquipmentSlot;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Indicates if an equipped turtle item will consume durability.
 | 
			
		||||
 *
 | 
			
		||||
 * @see TurtleUpgradeDataProvider.ToolBuilder#consumeDurability(TurtleToolDurability)
 | 
			
		||||
 */
 | 
			
		||||
public enum TurtleToolDurability implements StringRepresentable {
 | 
			
		||||
    /**
 | 
			
		||||
     * The equipped tool always consumes durability when using.
 | 
			
		||||
     */
 | 
			
		||||
    ALWAYS("always"),
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has
 | 
			
		||||
     * {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
 | 
			
		||||
     */
 | 
			
		||||
    WHEN_ENCHANTED("when_enchanted"),
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The equipped tool never consumes durability. Tools which have been damaged cannot be used as upgrades.
 | 
			
		||||
     */
 | 
			
		||||
    NEVER("never");
 | 
			
		||||
 | 
			
		||||
    private final String serialisedName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The codec which may be used for serialising/deserialising {@link TurtleToolDurability}s.
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings("deprecation")
 | 
			
		||||
    public static final StringRepresentable.EnumCodec<TurtleToolDurability> CODEC = StringRepresentable.fromEnum(TurtleToolDurability::values);
 | 
			
		||||
 | 
			
		||||
    TurtleToolDurability(String serialisedName) {
 | 
			
		||||
        this.serialisedName = serialisedName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getSerializedName() {
 | 
			
		||||
        return serialisedName;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,8 +13,10 @@ 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.EquipmentSlot;
 | 
			
		||||
import net.minecraft.world.entity.ai.attributes.Attributes;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import net.minecraft.world.level.block.Block;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
@@ -61,6 +63,8 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
 | 
			
		||||
        private @Nullable Item craftingItem;
 | 
			
		||||
        private @Nullable Float damageMultiplier = null;
 | 
			
		||||
        private @Nullable TagKey<Block> breakable;
 | 
			
		||||
        private boolean allowEnchantments = false;
 | 
			
		||||
        private TurtleToolDurability consumeDurability = TurtleToolDurability.NEVER;
 | 
			
		||||
 | 
			
		||||
        ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) {
 | 
			
		||||
            this.id = id;
 | 
			
		||||
@@ -104,6 +108,28 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have
 | 
			
		||||
         * {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}.
 | 
			
		||||
         *
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder allowEnchantments() {
 | 
			
		||||
            allowEnchantments = true;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Set when the tool will consume durability.
 | 
			
		||||
         *
 | 
			
		||||
         * @param durability The durability predicate.
 | 
			
		||||
         * @return The tool builder, for further use.
 | 
			
		||||
         */
 | 
			
		||||
        public ToolBuilder consumeDurability(TurtleToolDurability durability) {
 | 
			
		||||
            consumeDurability = durability;
 | 
			
		||||
            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
 | 
			
		||||
@@ -132,6 +158,10 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur
 | 
			
		||||
                }
 | 
			
		||||
                if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
 | 
			
		||||
                if (breakable != null) s.addProperty("breakable", breakable.location().toString());
 | 
			
		||||
                if (allowEnchantments) s.addProperty("allowEnchantments", true);
 | 
			
		||||
                if (consumeDurability != TurtleToolDurability.NEVER) {
 | 
			
		||||
                    s.addProperty("consumeDurability", consumeDurability.getSerializedName());
 | 
			
		||||
                }
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,14 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketAccess;
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleAccess;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.impl.PlatformHelper;
 | 
			
		||||
import net.minecraft.Util;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
@@ -50,6 +54,42 @@ public interface UpgradeBase {
 | 
			
		||||
     */
 | 
			
		||||
    ItemStack getCraftingItem();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the item stack representing a currently equipped turtle upgrade.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * While upgrades can store upgrade data ({@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} and
 | 
			
		||||
     * {@link IPocketAccess#getUpgradeNBTData()}}, by default this data is discarded when an upgrade is unequipped,
 | 
			
		||||
     * and the original item stack is returned.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * By overriding this method, you can create a new {@link ItemStack} which contains enough data to
 | 
			
		||||
     * {@linkplain #getUpgradeData(ItemStack) re-create the upgrade data} if the item is re-equipped.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * When overriding this, you should override {@link #getUpgradeData(ItemStack)} and {@link #isItemSuitable(ItemStack)}
 | 
			
		||||
     * at the same time,
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgradeData The current upgrade data. This should <strong>NOT</strong> be mutated.
 | 
			
		||||
     * @return The item stack returned when unequipping.
 | 
			
		||||
     */
 | 
			
		||||
    default ItemStack getUpgradeItem(CompoundTag upgradeData) {
 | 
			
		||||
        return getCraftingItem();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Extract upgrade data from an {@link ItemStack}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This upgrade data will be available with {@link ITurtleAccess#getUpgradeNBTData(TurtleSide)} or
 | 
			
		||||
     * {@link IPocketAccess#getUpgradeNBTData()}.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This should be an inverse to {@link #getUpgradeItem(CompoundTag)}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stack The stack that was equipped by the turtle or pocket computer. This will have the same item as
 | 
			
		||||
     *              {@link #getCraftingItem()}.
 | 
			
		||||
     * @return The upgrade data that should be set on the turtle or pocket computer.
 | 
			
		||||
     */
 | 
			
		||||
    default CompoundTag getUpgradeData(ItemStack stack) {
 | 
			
		||||
        return new CompoundTag();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine if an item is suitable for being used for this upgrade.
 | 
			
		||||
     * <p>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,82 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.api.upgrades;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.jetbrains.annotations.Contract;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <strong>IMPORTANT:</strong> The {@link #data()} in an upgrade data is often a reference to the original upgrade data.
 | 
			
		||||
 * Be careful to take a {@linkplain #copy() defensive copy} if you plan to use the data in this upgrade.
 | 
			
		||||
 *
 | 
			
		||||
 * @param upgrade The current upgrade.
 | 
			
		||||
 * @param data    The upgrade's data.
 | 
			
		||||
 * @param <T>     The type of upgrade, either {@link ITurtleUpgrade} or {@link IPocketUpgrade}.
 | 
			
		||||
 */
 | 
			
		||||
public record UpgradeData<T extends UpgradeBase>(T upgrade, CompoundTag data) {
 | 
			
		||||
    /**
 | 
			
		||||
     * A utility method to construct a new {@link UpgradeData} instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade An upgrade.
 | 
			
		||||
     * @param data    The upgrade's data.
 | 
			
		||||
     * @param <T>     The type of upgrade.
 | 
			
		||||
     * @return The new {@link UpgradeData} instance.
 | 
			
		||||
     */
 | 
			
		||||
    public static <T extends UpgradeBase> UpgradeData<T> of(T upgrade, CompoundTag data) {
 | 
			
		||||
        return new UpgradeData<>(upgrade, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create an {@link UpgradeData} containing the default {@linkplain #data() data} for an upgrade.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade The upgrade instance.
 | 
			
		||||
     * @param <T>     The type of upgrade.
 | 
			
		||||
     * @return The default upgrade data.
 | 
			
		||||
     */
 | 
			
		||||
    public static <T extends UpgradeBase> UpgradeData<T> ofDefault(T upgrade) {
 | 
			
		||||
        return of(upgrade, upgrade.getUpgradeData(upgrade.getCraftingItem()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Take a copy of a (possibly {@code null}) {@link UpgradeData} instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @param upgrade The copied upgrade data.
 | 
			
		||||
     * @param <T>     The type of upgrade.
 | 
			
		||||
     * @return The newly created upgrade data.
 | 
			
		||||
     */
 | 
			
		||||
    @Contract("!null -> !null; null -> null")
 | 
			
		||||
    public static <T extends UpgradeBase> @Nullable UpgradeData<T> copyOf(@Nullable UpgradeData<T> upgrade) {
 | 
			
		||||
        return upgrade == null ? null : upgrade.copy();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the {@linkplain UpgradeBase#getUpgradeItem(CompoundTag) upgrade item} for this upgrade.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This returns a defensive copy of the item, to prevent accidental mutation of the upgrade data or original
 | 
			
		||||
     * {@linkplain UpgradeBase#getCraftingItem() upgrade stack}.
 | 
			
		||||
     *
 | 
			
		||||
     * @return This upgrade's item.
 | 
			
		||||
     */
 | 
			
		||||
    public ItemStack getUpgradeItem() {
 | 
			
		||||
        return upgrade.getUpgradeItem(data).copy();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Take a copy of this {@link UpgradeData}. This returns a new instance with the same upgrade and a fresh copy of
 | 
			
		||||
     * the upgrade data.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A copy of the current instance.
 | 
			
		||||
     */
 | 
			
		||||
    public UpgradeData<T> copy() {
 | 
			
		||||
        return new UpgradeData<>(upgrade(), data().copy());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,10 +23,7 @@ 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.*;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
@@ -104,7 +101,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
 | 
			
		||||
    protected abstract void addUpgrades(Consumer<Upgrade<R>> addUpgrade);
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public final CompletableFuture<?> run(CachedOutput cache) {
 | 
			
		||||
    public CompletableFuture<?> run(CachedOutput cache) {
 | 
			
		||||
        var base = output.getOutputFolder().resolve("data");
 | 
			
		||||
 | 
			
		||||
        Set<ResourceLocation> seen = new HashSet<>();
 | 
			
		||||
@@ -127,7 +124,7 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.upgrades = upgrades;
 | 
			
		||||
        this.upgrades = Collections.unmodifiableList(upgrades);
 | 
			
		||||
        return Util.sequenceFailFast(futures);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -166,5 +163,21 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
 | 
			
		||||
        public void add(Consumer<Upgrade<R>> add) {
 | 
			
		||||
            add.accept(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Return a new {@link Upgrade} which requires the given mod to be present.
 | 
			
		||||
         * <p>
 | 
			
		||||
         * This uses mod-loader-specific hooks (Forge's crafting conditions and Fabric's resource conditions). If using
 | 
			
		||||
         * this in a multi-loader setup, you must generate resources separately for the two loaders.
 | 
			
		||||
         *
 | 
			
		||||
         * @param modId The id of the mod.
 | 
			
		||||
         * @return A new upgrade instance.
 | 
			
		||||
         */
 | 
			
		||||
        public Upgrade<R> requireMod(String modId) {
 | 
			
		||||
            return new Upgrade<>(id, serialiser, json -> {
 | 
			
		||||
                PlatformHelper.get().addRequiredModCondition(json, modId);
 | 
			
		||||
                serialise.accept(json);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.impl;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
 | 
			
		||||
import net.minecraft.core.Registry;
 | 
			
		||||
import net.minecraft.nbt.CompoundTag;
 | 
			
		||||
import net.minecraft.resources.ResourceKey;
 | 
			
		||||
@@ -63,6 +65,15 @@ public interface PlatformHelper {
 | 
			
		||||
        return item.getTag();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a resource condition which requires a mod to be loaded. This should be used by data providers such as
 | 
			
		||||
     * {@link UpgradeDataProvider}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param object The JSON object we're generating.
 | 
			
		||||
     * @param modId  The mod ID that we require.
 | 
			
		||||
     */
 | 
			
		||||
    void addRequiredModCondition(JsonObject object, String modId);
 | 
			
		||||
 | 
			
		||||
    final class Instance {
 | 
			
		||||
        static final @Nullable PlatformHelper INSTANCE;
 | 
			
		||||
        static final @Nullable Throwable ERROR;
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import cc.tweaked.gradle.clientClasses
 | 
			
		||||
import cc.tweaked.gradle.commonClasses
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("cc-tweaked.publishing")
 | 
			
		||||
    id("cc-tweaked.vanilla")
 | 
			
		||||
    id("cc-tweaked.gametest")
 | 
			
		||||
}
 | 
			
		||||
@@ -25,6 +26,7 @@ dependencies {
 | 
			
		||||
    clientImplementation(clientClasses(project(":common-api")))
 | 
			
		||||
 | 
			
		||||
    compileOnly(libs.bundles.externalMods.common)
 | 
			
		||||
    clientCompileOnly(variantOf(libs.emi) { classifier("api") })
 | 
			
		||||
 | 
			
		||||
    compileOnly(libs.mixin)
 | 
			
		||||
    annotationProcessorEverywhere(libs.autoService)
 | 
			
		||||
@@ -37,4 +39,6 @@ dependencies {
 | 
			
		||||
    testModImplementation(testFixtures(project(":core")))
 | 
			
		||||
    testModImplementation(testFixtures(project(":common")))
 | 
			
		||||
    testModImplementation(libs.bundles.kotlin)
 | 
			
		||||
 | 
			
		||||
    testFixturesImplementation(testFixtures(project(":core")))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ 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.client.turtle.TurtleUpgradeModellers;
 | 
			
		||||
import dan200.computercraft.core.util.Colour;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.common.IColouredItem;
 | 
			
		||||
@@ -20,6 +21,7 @@ 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.Minecraft;
 | 
			
		||||
import net.minecraft.client.color.item.ItemColor;
 | 
			
		||||
import net.minecraft.client.gui.screens.MenuScreens;
 | 
			
		||||
import net.minecraft.client.multiplayer.ClientLevel;
 | 
			
		||||
@@ -29,6 +31,7 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
 | 
			
		||||
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.PreparableReloadListener;
 | 
			
		||||
import net.minecraft.server.packs.resources.ResourceProvider;
 | 
			
		||||
import net.minecraft.world.entity.LivingEntity;
 | 
			
		||||
import net.minecraft.world.item.Item;
 | 
			
		||||
@@ -106,25 +109,11 @@ public final class ClientRegistry {
 | 
			
		||||
        for (var item : items) ItemProperties.register(item.get(), id, getter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerReloadListeners(Consumer<PreparableReloadListener> register, Minecraft minecraft) {
 | 
			
		||||
        register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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",
 | 
			
		||||
        "block/turtle_rainbow_overlay",
 | 
			
		||||
@@ -133,6 +122,7 @@ public final class ClientRegistry {
 | 
			
		||||
 | 
			
		||||
    public static void registerExtraModels(Consumer<ResourceLocation> register) {
 | 
			
		||||
        for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraftAPI.MOD_ID, model));
 | 
			
		||||
        TurtleUpgradeModellers.getDependencies().forEach(register);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,13 +7,14 @@ package dan200.computercraft.client.gui;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
 | 
			
		||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
 | 
			
		||||
import dan200.computercraft.client.render.RenderTypes;
 | 
			
		||||
import dan200.computercraft.client.render.SpriteRenderer;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.world.entity.player.Inventory;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER;
 | 
			
		||||
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A GUI for computers which renders the terminal (and border), but with no UI elements.
 | 
			
		||||
@@ -39,11 +40,14 @@ public final class ComputerScreen<T extends AbstractComputerMenu> extends Abstra
 | 
			
		||||
    public void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        // Draw a border around the terminal
 | 
			
		||||
        var terminal = getTerminal();
 | 
			
		||||
        var texture = ComputerBorderRenderer.getTexture(family);
 | 
			
		||||
        var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
 | 
			
		||||
        var computerTextures = GuiSprites.getComputerTextures(family);
 | 
			
		||||
 | 
			
		||||
        ComputerBorderRenderer.render(
 | 
			
		||||
            graphics.pose().last().pose(), texture, terminal.getX(), terminal.getY(),
 | 
			
		||||
            FULL_BRIGHT_LIGHTMAP, terminal.getWidth(), terminal.getHeight()
 | 
			
		||||
            spriteRenderer, computerTextures,
 | 
			
		||||
            terminal.getX(), terminal.getY(), terminal.getWidth(), terminal.getHeight(), false
 | 
			
		||||
        );
 | 
			
		||||
        ComputerSidebar.renderBackground(graphics, texture, leftPos, topPos + sidebarYOffset);
 | 
			
		||||
        ComputerSidebar.renderBackground(spriteRenderer, computerTextures, leftPos, topPos + sidebarYOffset);
 | 
			
		||||
        graphics.flush(); // Flush to ensure background textures are drawn before foreground.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,127 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
 | 
			
		||||
import dan200.computercraft.data.client.ClientDataProviders;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureManager;
 | 
			
		||||
import net.minecraft.client.resources.TextureAtlasHolder;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Sprite sheet for all GUI texutres in the mod.
 | 
			
		||||
 */
 | 
			
		||||
public final class GuiSprites extends TextureAtlasHolder {
 | 
			
		||||
    public static final ResourceLocation SPRITE_SHEET = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui");
 | 
			
		||||
    public static final ResourceLocation TEXTURE = SPRITE_SHEET.withPath(x -> "textures/atlas/" + x + ".png");
 | 
			
		||||
 | 
			
		||||
    public static final ButtonTextures TURNED_OFF = button("turned_off");
 | 
			
		||||
    public static final ButtonTextures TURNED_ON = button("turned_on");
 | 
			
		||||
    public static final ButtonTextures TERMINATE = button("terminate");
 | 
			
		||||
 | 
			
		||||
    public static final ComputerTextures COMPUTER_NORMAL = computer("normal", true, true);
 | 
			
		||||
    public static final ComputerTextures COMPUTER_ADVANCED = computer("advanced", true, true);
 | 
			
		||||
    public static final ComputerTextures COMPUTER_COMMAND = computer("command", false, true);
 | 
			
		||||
    public static final ComputerTextures COMPUTER_COLOUR = computer("colour", true, false);
 | 
			
		||||
 | 
			
		||||
    private static ButtonTextures button(String name) {
 | 
			
		||||
        return new ButtonTextures(
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name),
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name + "_hover")
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ComputerTextures computer(String name, boolean pocket, boolean sidebar) {
 | 
			
		||||
        return new ComputerTextures(
 | 
			
		||||
            new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/border_" + name),
 | 
			
		||||
            pocket ? new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/pocket_bottom_" + name) : null,
 | 
			
		||||
            sidebar ? new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sidebar_" + name) : null
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static @Nullable GuiSprites instance;
 | 
			
		||||
 | 
			
		||||
    private GuiSprites(TextureManager textureManager) {
 | 
			
		||||
        super(textureManager, TEXTURE, SPRITE_SHEET);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialise the singleton {@link GuiSprites} instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @param textureManager The current texture manager.
 | 
			
		||||
     * @return The singleton {@link GuiSprites} instance, to register as resource reload listener.
 | 
			
		||||
     */
 | 
			
		||||
    public static GuiSprites initialise(TextureManager textureManager) {
 | 
			
		||||
        if (instance != null) throw new IllegalStateException("GuiSprites has already been initialised");
 | 
			
		||||
        return instance = new GuiSprites(textureManager);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Lookup a texture on the atlas.
 | 
			
		||||
     *
 | 
			
		||||
     * @param texture The texture to find.
 | 
			
		||||
     * @return The sprite on the atlas.
 | 
			
		||||
     */
 | 
			
		||||
    public static TextureAtlasSprite get(ResourceLocation texture) {
 | 
			
		||||
        if (instance == null) throw new IllegalStateException("GuiSprites has not been initialised");
 | 
			
		||||
        return instance.getSprite(texture);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the appropriate textures to use for a particular computer family.
 | 
			
		||||
     *
 | 
			
		||||
     * @param family The computer family.
 | 
			
		||||
     * @return The family-specific textures.
 | 
			
		||||
     */
 | 
			
		||||
    public static ComputerTextures getComputerTextures(ComputerFamily family) {
 | 
			
		||||
        return switch (family) {
 | 
			
		||||
            case NORMAL -> COMPUTER_NORMAL;
 | 
			
		||||
            case ADVANCED -> COMPUTER_ADVANCED;
 | 
			
		||||
            case COMMAND -> COMPUTER_COMMAND;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A set of sprites for a button, with both a normal and "active" state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param normal The normal texture for the button.
 | 
			
		||||
     * @param active The texture for the button when it is active (hovered or focused).
 | 
			
		||||
     */
 | 
			
		||||
    public record ButtonTextures(ResourceLocation normal, ResourceLocation active) {
 | 
			
		||||
        public TextureAtlasSprite get(boolean active) {
 | 
			
		||||
            return GuiSprites.get(active ? this.active : normal);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Stream<ResourceLocation> textures() {
 | 
			
		||||
            return Stream.of(normal, active);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the set of sprites for a computer family.
 | 
			
		||||
     *
 | 
			
		||||
     * @param border       The texture for the computer's border.
 | 
			
		||||
     * @param pocketBottom The texture for the bottom of a pocket computer.
 | 
			
		||||
     * @param sidebar      The texture for the computer sidebar.
 | 
			
		||||
     * @see ComputerBorderRenderer
 | 
			
		||||
     * @see ClientDataProviders
 | 
			
		||||
     */
 | 
			
		||||
    public record ComputerTextures(
 | 
			
		||||
        ResourceLocation border,
 | 
			
		||||
        @Nullable ResourceLocation pocketBottom,
 | 
			
		||||
        @Nullable ResourceLocation sidebar
 | 
			
		||||
    ) {
 | 
			
		||||
        public Stream<ResourceLocation> textures() {
 | 
			
		||||
            return Stream.of(border, pocketBottom, sidebar).filter(Objects::nonNull);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -100,9 +100,9 @@ public class ItemToast implements Toast {
 | 
			
		||||
            graphics.renderFakeItem(stack, MARGIN, MARGIN + height() / 2 - IMAGE_SIZE);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        graphics.drawString(component.getMinecraft().font, title, textX, MARGIN, 0xff500050);
 | 
			
		||||
        graphics.drawString(component.getMinecraft().font, title, textX, MARGIN, 0xff500050, false);
 | 
			
		||||
        for (var i = 0; i < message.size(); ++i) {
 | 
			
		||||
            graphics.drawString(component.getMinecraft().font, message.get(i), textX, LINE_SPACING + (i + 1) * LINE_SPACING, 0xff000000);
 | 
			
		||||
            graphics.drawString(component.getMinecraft().font, message.get(i), textX, LINE_SPACING + (i + 1) * LINE_SPACING, 0xff000000, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return time - firstDisplay < DISPLAY_TIME ? Visibility.SHOW : Visibility.HIDE;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: LicenseRef-CCPL
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui;
 | 
			
		||||
 | 
			
		||||
@@ -42,7 +42,6 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void init() {
 | 
			
		||||
        // FIXME: passEvents = true; // Pass mouse vents through to the game's mouse handler.
 | 
			
		||||
        // First ensure we're still grabbing the mouse, so the user can look around. Then reset bits of state that
 | 
			
		||||
        // grabbing unsets.
 | 
			
		||||
        minecraft.mouseHandler.grabMouse();
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,8 @@ package dan200.computercraft.client.gui;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
 | 
			
		||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
 | 
			
		||||
import dan200.computercraft.client.render.RenderTypes;
 | 
			
		||||
import dan200.computercraft.client.render.SpriteRenderer;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
 | 
			
		||||
@@ -25,9 +26,11 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
 | 
			
		||||
 | 
			
		||||
    private static final int TEX_WIDTH = 254;
 | 
			
		||||
    private static final int TEX_WIDTH = 278;
 | 
			
		||||
    private static final int TEX_HEIGHT = 217;
 | 
			
		||||
 | 
			
		||||
    private static final int FULL_TEX_SIZE = 512;
 | 
			
		||||
 | 
			
		||||
    public TurtleScreen(TurtleMenu container, Inventory player, Component title) {
 | 
			
		||||
        super(container, player, title, BORDER);
 | 
			
		||||
 | 
			
		||||
@@ -44,18 +47,22 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
 | 
			
		||||
    protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
 | 
			
		||||
        var advanced = family == ComputerFamily.ADVANCED;
 | 
			
		||||
        var texture = advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL;
 | 
			
		||||
        graphics.blit(texture, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, TEX_WIDTH, TEX_HEIGHT);
 | 
			
		||||
        graphics.blit(texture, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE);
 | 
			
		||||
 | 
			
		||||
        // Render selected slot
 | 
			
		||||
        var slot = getMenu().getSelectedSlot();
 | 
			
		||||
        if (slot >= 0) {
 | 
			
		||||
            var slotX = slot % 4;
 | 
			
		||||
            var slotY = slot / 4;
 | 
			
		||||
            graphics.blit(texture,
 | 
			
		||||
                leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18,
 | 
			
		||||
                0, 217, 24, 24
 | 
			
		||||
                leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0,
 | 
			
		||||
                0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ComputerSidebar.renderBackground(graphics, ComputerBorderRenderer.getTexture(family), leftPos, topPos + sidebarYOffset);
 | 
			
		||||
        // Render sidebar
 | 
			
		||||
        var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
 | 
			
		||||
        ComputerSidebar.renderBackground(spriteRenderer, GuiSprites.getComputerTextures(family), leftPos, topPos + sidebarYOffset);
 | 
			
		||||
        graphics.flush(); // Flush to ensure background textures are drawn before foreground.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,15 +4,13 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.gui.widgets;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.client.gui.GuiSprites;
 | 
			
		||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton.HintedMessage;
 | 
			
		||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
 | 
			
		||||
import dan200.computercraft.client.render.SpriteRenderer;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.InputHandler;
 | 
			
		||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.components.AbstractWidget;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BooleanSupplier;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
@@ -21,22 +19,18 @@ import java.util.function.Consumer;
 | 
			
		||||
 * Registers buttons to interact with a computer.
 | 
			
		||||
 */
 | 
			
		||||
public final class ComputerSidebar {
 | 
			
		||||
    private static final ResourceLocation TEXTURE = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/buttons.png");
 | 
			
		||||
 | 
			
		||||
    private static final int TEX_SIZE = 64;
 | 
			
		||||
 | 
			
		||||
    private static final int ICON_WIDTH = 12;
 | 
			
		||||
    private static final int ICON_HEIGHT = 12;
 | 
			
		||||
    private static final int ICON_MARGIN = 2;
 | 
			
		||||
 | 
			
		||||
    private static final int ICON_TEX_Y_DIFF = 14;
 | 
			
		||||
 | 
			
		||||
    private static final int CORNERS_BORDER = 3;
 | 
			
		||||
    private static final int FULL_BORDER = CORNERS_BORDER + ICON_MARGIN;
 | 
			
		||||
 | 
			
		||||
    private static final int BUTTONS = 2;
 | 
			
		||||
    private static final int HEIGHT = (ICON_HEIGHT + ICON_MARGIN * 2) * BUTTONS + CORNERS_BORDER * 2;
 | 
			
		||||
 | 
			
		||||
    private static final int TEX_HEIGHT = 14;
 | 
			
		||||
 | 
			
		||||
    private ComputerSidebar() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -50,16 +44,18 @@ public final class ComputerSidebar {
 | 
			
		||||
            Component.translatable("gui.computercraft.tooltip.turn_off.key")
 | 
			
		||||
        );
 | 
			
		||||
        add.accept(new DynamicImageButton(
 | 
			
		||||
            x, y, ICON_WIDTH, ICON_HEIGHT, () -> isOn.getAsBoolean() ? 15 : 1, 1, ICON_TEX_Y_DIFF,
 | 
			
		||||
            TEXTURE, TEX_SIZE, TEX_SIZE, b -> toggleComputer(isOn, input),
 | 
			
		||||
            x, y, ICON_WIDTH, ICON_HEIGHT,
 | 
			
		||||
            h -> isOn.getAsBoolean() ? GuiSprites.TURNED_ON.get(h) : GuiSprites.TURNED_OFF.get(h),
 | 
			
		||||
            b -> toggleComputer(isOn, input),
 | 
			
		||||
            () -> isOn.getAsBoolean() ? turnOff : turnOn
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        y += ICON_HEIGHT + ICON_MARGIN * 2;
 | 
			
		||||
 | 
			
		||||
        add.accept(new DynamicImageButton(
 | 
			
		||||
            x, y, ICON_WIDTH, ICON_HEIGHT, 29, 1, ICON_TEX_Y_DIFF,
 | 
			
		||||
            TEXTURE, TEX_SIZE, TEX_SIZE, b -> input.queueEvent("terminate"),
 | 
			
		||||
            x, y, ICON_WIDTH, ICON_HEIGHT,
 | 
			
		||||
            GuiSprites.TERMINATE::get,
 | 
			
		||||
            b -> input.queueEvent("terminate"),
 | 
			
		||||
            new HintedMessage(
 | 
			
		||||
                Component.translatable("gui.computercraft.tooltip.terminate"),
 | 
			
		||||
                Component.translatable("gui.computercraft.tooltip.terminate.key")
 | 
			
		||||
@@ -67,22 +63,12 @@ public final class ComputerSidebar {
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void renderBackground(GuiGraphics graphics, ResourceLocation texture, int x, int y) {
 | 
			
		||||
        graphics.blit(texture,
 | 
			
		||||
            x, y, 0, 102, AbstractComputerMenu.SIDEBAR_WIDTH, FULL_BORDER,
 | 
			
		||||
            ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
 | 
			
		||||
        );
 | 
			
		||||
    public static void renderBackground(SpriteRenderer renderer, GuiSprites.ComputerTextures textures, int x, int y) {
 | 
			
		||||
        var texture = textures.sidebar();
 | 
			
		||||
        if (texture == null) throw new NullPointerException(textures + " has no sidebar texture");
 | 
			
		||||
        var sprite = GuiSprites.get(texture);
 | 
			
		||||
 | 
			
		||||
        graphics.blit(texture,
 | 
			
		||||
            x, y + FULL_BORDER, AbstractComputerMenu.SIDEBAR_WIDTH, HEIGHT - FULL_BORDER * 2,
 | 
			
		||||
            0, 107, AbstractComputerMenu.SIDEBAR_WIDTH, 4,
 | 
			
		||||
            ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        graphics.blit(texture,
 | 
			
		||||
            x, y + HEIGHT - FULL_BORDER, 0, 111, AbstractComputerMenu.SIDEBAR_WIDTH, FULL_BORDER,
 | 
			
		||||
            ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
 | 
			
		||||
        );
 | 
			
		||||
        renderer.blitVerticalSliced(sprite, x, y, AbstractComputerMenu.SIDEBAR_WIDTH, HEIGHT, FULL_BORDER, FULL_BORDER, TEX_HEIGHT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void toggleComputer(BooleanSupplier isOn, InputHandler input) {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,15 @@
 | 
			
		||||
package dan200.computercraft.client.gui.widgets;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.systems.RenderSystem;
 | 
			
		||||
import it.unimi.dsi.fastutil.booleans.Boolean2ObjectFunction;
 | 
			
		||||
import net.minecraft.ChatFormatting;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.gui.components.Button;
 | 
			
		||||
import net.minecraft.client.gui.components.Tooltip;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
 | 
			
		||||
import net.minecraft.network.chat.Component;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.function.IntSupplier;
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -21,60 +21,40 @@ import java.util.function.Supplier;
 | 
			
		||||
 * dynamically.
 | 
			
		||||
 */
 | 
			
		||||
public class DynamicImageButton extends Button {
 | 
			
		||||
    private final ResourceLocation texture;
 | 
			
		||||
    private final IntSupplier xTexStart;
 | 
			
		||||
    private final int yTexStart;
 | 
			
		||||
    private final int yDiffTex;
 | 
			
		||||
    private final int textureWidth;
 | 
			
		||||
    private final int textureHeight;
 | 
			
		||||
    private final Boolean2ObjectFunction<TextureAtlasSprite> texture;
 | 
			
		||||
    private final Supplier<HintedMessage> message;
 | 
			
		||||
 | 
			
		||||
    public DynamicImageButton(
 | 
			
		||||
        int x, int y, int width, int height, int xTexStart, int yTexStart, int yDiffTex,
 | 
			
		||||
        ResourceLocation texture, int textureWidth, int textureHeight,
 | 
			
		||||
        OnPress onPress, HintedMessage message
 | 
			
		||||
        int x, int y, int width, int height, Boolean2ObjectFunction<TextureAtlasSprite> texture, OnPress onPress,
 | 
			
		||||
        HintedMessage message
 | 
			
		||||
    ) {
 | 
			
		||||
        this(
 | 
			
		||||
            x, y, width, height, () -> xTexStart, yTexStart, yDiffTex,
 | 
			
		||||
            texture, textureWidth, textureHeight,
 | 
			
		||||
            onPress, () -> message
 | 
			
		||||
        );
 | 
			
		||||
        this(x, y, width, height, texture, onPress, () -> message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public DynamicImageButton(
 | 
			
		||||
        int x, int y, int width, int height, IntSupplier xTexStart, int yTexStart, int yDiffTex,
 | 
			
		||||
        ResourceLocation texture, int textureWidth, int textureHeight,
 | 
			
		||||
        int x, int y, int width, int height,
 | 
			
		||||
        Boolean2ObjectFunction<TextureAtlasSprite> texture,
 | 
			
		||||
        OnPress onPress, Supplier<HintedMessage> message
 | 
			
		||||
    ) {
 | 
			
		||||
        super(x, y, width, height, Component.empty(), onPress, DEFAULT_NARRATION);
 | 
			
		||||
        this.textureWidth = textureWidth;
 | 
			
		||||
        this.textureHeight = textureHeight;
 | 
			
		||||
        this.xTexStart = xTexStart;
 | 
			
		||||
        this.yTexStart = yTexStart;
 | 
			
		||||
        this.yDiffTex = yDiffTex;
 | 
			
		||||
        this.texture = texture;
 | 
			
		||||
        this.message = message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        RenderSystem.enableBlend();
 | 
			
		||||
        var texture = this.texture.get(isHoveredOrFocused());
 | 
			
		||||
 | 
			
		||||
        RenderSystem.disableDepthTest();
 | 
			
		||||
        graphics.blit(getX(), getY(), 0, width, height, texture);
 | 
			
		||||
        RenderSystem.enableDepthTest();
 | 
			
		||||
 | 
			
		||||
        var yTex = yTexStart;
 | 
			
		||||
        if (isHoveredOrFocused()) yTex += yDiffTex;
 | 
			
		||||
 | 
			
		||||
        graphics.blit(texture, getX(), getY(), xTexStart.getAsInt(), yTex, width, height, textureWidth, textureHeight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Component getMessage() {
 | 
			
		||||
        return message.get().message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
 | 
			
		||||
        setTooltip(message.get().tooltip());
 | 
			
		||||
        var message = this.message.get();
 | 
			
		||||
        setMessage(message.message());
 | 
			
		||||
        setTooltip(message.tooltip());
 | 
			
		||||
        super.render(graphics, mouseX, mouseY, partialTicks);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ public class ShaderMod {
 | 
			
		||||
        Optional<ShaderMod> get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class Storage {
 | 
			
		||||
    private static final class Storage {
 | 
			
		||||
        static final ShaderMod INSTANCE = ServiceLoader.load(Provider.class)
 | 
			
		||||
            .stream()
 | 
			
		||||
            .flatMap(x -> x.get().get().stream())
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.integration.emi;
 | 
			
		||||
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.shared.ModRegistry;
 | 
			
		||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
 | 
			
		||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
 | 
			
		||||
import dev.emi.emi.api.EmiEntrypoint;
 | 
			
		||||
import dev.emi.emi.api.EmiPlugin;
 | 
			
		||||
import dev.emi.emi.api.EmiRegistry;
 | 
			
		||||
import dev.emi.emi.api.stack.Comparison;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import java.util.function.BiPredicate;
 | 
			
		||||
 | 
			
		||||
@EmiEntrypoint
 | 
			
		||||
public class EMIComputerCraft implements EmiPlugin {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void register(EmiRegistry registry) {
 | 
			
		||||
        registry.setDefaultComparison(ModRegistry.Items.TURTLE_NORMAL.get(), turtleComparison);
 | 
			
		||||
        registry.setDefaultComparison(ModRegistry.Items.TURTLE_ADVANCED.get(), turtleComparison);
 | 
			
		||||
 | 
			
		||||
        registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), pocketComparison);
 | 
			
		||||
        registry.setDefaultComparison(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get(), pocketComparison);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final Comparison turtleComparison = compareStacks((left, right) ->
 | 
			
		||||
        left.getItem() instanceof TurtleItem turtle
 | 
			
		||||
            && turtle.getUpgrade(left, TurtleSide.LEFT) == turtle.getUpgrade(right, TurtleSide.LEFT)
 | 
			
		||||
            && turtle.getUpgrade(left, TurtleSide.RIGHT) == turtle.getUpgrade(right, TurtleSide.RIGHT));
 | 
			
		||||
 | 
			
		||||
    private static final Comparison pocketComparison = compareStacks((left, right) ->
 | 
			
		||||
        left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
 | 
			
		||||
 | 
			
		||||
    private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
 | 
			
		||||
        return Comparison.of((left, right) -> {
 | 
			
		||||
            ItemStack leftStack = left.getItemStack(), rightStack = right.getItemStack();
 | 
			
		||||
            return leftStack.getItem() == rightStack.getItem() && test.test(leftStack, rightStack);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,103 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.model.turtle;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexFormat;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexFormatElement;
 | 
			
		||||
import com.mojang.math.Transformation;
 | 
			
		||||
import net.minecraft.client.renderer.block.model.BakedQuad;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.core.Direction;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
import org.joml.Vector4f;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Applies a {@link Transformation} (or rather a {@link Matrix4f}) to a list of {@link BakedQuad}s.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This does a little bit of magic compared with other system (i.e. Forge's {@code QuadTransformers}), as it needs to
 | 
			
		||||
 * handle flipping models upside down.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This is typically used with a {@link BakedModel} subclass - see the loader-specific projects.
 | 
			
		||||
 */
 | 
			
		||||
public class ModelTransformer {
 | 
			
		||||
    private static final int[] INVERSE_ORDER = new int[]{ 3, 2, 1, 0 };
 | 
			
		||||
 | 
			
		||||
    private static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
 | 
			
		||||
    private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
 | 
			
		||||
 | 
			
		||||
    protected final Matrix4f transformation;
 | 
			
		||||
    protected final boolean invert;
 | 
			
		||||
    private @Nullable TransformedQuads cache;
 | 
			
		||||
 | 
			
		||||
    public ModelTransformer(Transformation transformation) {
 | 
			
		||||
        this.transformation = transformation.getMatrix();
 | 
			
		||||
        invert = transformation.getMatrix().determinant() < 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<BakedQuad> transform(List<BakedQuad> quads) {
 | 
			
		||||
        if (quads.isEmpty()) return List.of();
 | 
			
		||||
 | 
			
		||||
        // We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
 | 
			
		||||
        // so it's not worth being smarter here.
 | 
			
		||||
        var cache = this.cache;
 | 
			
		||||
        if (cache != null && quads.equals(cache.original())) return cache.transformed();
 | 
			
		||||
 | 
			
		||||
        List<BakedQuad> transformed = new ArrayList<>(quads.size());
 | 
			
		||||
        for (var quad : quads) transformed.add(transformQuad(quad));
 | 
			
		||||
        this.cache = new TransformedQuads(quads, transformed);
 | 
			
		||||
        return transformed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private BakedQuad transformQuad(BakedQuad quad) {
 | 
			
		||||
        var inputData = quad.getVertices();
 | 
			
		||||
        var outputData = new int[inputData.length];
 | 
			
		||||
        for (var i = 0; i < 4; i++) {
 | 
			
		||||
            var inStart = STRIDE * i;
 | 
			
		||||
            // Reverse the order of the quads if we're inverting
 | 
			
		||||
            var outStart = getVertexOffset(i, invert);
 | 
			
		||||
            System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
 | 
			
		||||
 | 
			
		||||
            // Apply the matrix to our position
 | 
			
		||||
            var inPosStart = inStart + POS_OFFSET;
 | 
			
		||||
            var outPosStart = outStart + POS_OFFSET;
 | 
			
		||||
 | 
			
		||||
            var x = Float.intBitsToFloat(inputData[inPosStart]);
 | 
			
		||||
            var y = Float.intBitsToFloat(inputData[inPosStart + 1]);
 | 
			
		||||
            var z = Float.intBitsToFloat(inputData[inPosStart + 2]);
 | 
			
		||||
 | 
			
		||||
            // Transform the position
 | 
			
		||||
            var pos = new Vector4f(x, y, z, 1);
 | 
			
		||||
            transformation.transformProject(pos);
 | 
			
		||||
 | 
			
		||||
            outputData[outPosStart] = Float.floatToRawIntBits(pos.x());
 | 
			
		||||
            outputData[outPosStart + 1] = Float.floatToRawIntBits(pos.y());
 | 
			
		||||
            outputData[outPosStart + 2] = Float.floatToRawIntBits(pos.z());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var direction = Direction.rotate(transformation, quad.getDirection());
 | 
			
		||||
        return new BakedQuad(outputData, quad.getTintIndex(), direction, quad.getSprite(), quad.isShade());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static int getVertexOffset(int vertex, boolean invert) {
 | 
			
		||||
        return (invert ? ModelTransformer.INVERSE_ORDER[vertex] : vertex) * ModelTransformer.STRIDE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int findOffset(VertexFormat format, VertexFormatElement element) {
 | 
			
		||||
        var offset = 0;
 | 
			
		||||
        for (var other : format.getElements()) {
 | 
			
		||||
            if (other == element) return offset / Integer.BYTES;
 | 
			
		||||
            offset += element.getByteSize();
 | 
			
		||||
        }
 | 
			
		||||
        throw new IllegalArgumentException("Cannot find " + element + " in " + format);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,11 +4,13 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.model.turtle;
 | 
			
		||||
 | 
			
		||||
import com.google.common.cache.CacheBuilder;
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.math.Transformation;
 | 
			
		||||
import dan200.computercraft.api.client.TransformedModel;
 | 
			
		||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
 | 
			
		||||
import dan200.computercraft.api.turtle.TurtleSide;
 | 
			
		||||
import dan200.computercraft.api.upgrades.UpgradeData;
 | 
			
		||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
			
		||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
 | 
			
		||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
 | 
			
		||||
@@ -21,15 +23,17 @@ import net.minecraft.world.item.ItemStack;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Combines several individual models together to form a turtle.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> The type of the resulting "baked model".
 | 
			
		||||
 */
 | 
			
		||||
public final class TurtleModelParts {
 | 
			
		||||
public final class TurtleModelParts<T> {
 | 
			
		||||
    private static final Transformation identity, flip;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
@@ -42,33 +46,67 @@ public final class TurtleModelParts {
 | 
			
		||||
        flip = new Transformation(stack.last().pose());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public record Combination(
 | 
			
		||||
    private record Combination(
 | 
			
		||||
        boolean colour,
 | 
			
		||||
        @Nullable ITurtleUpgrade leftUpgrade,
 | 
			
		||||
        @Nullable ITurtleUpgrade rightUpgrade,
 | 
			
		||||
        @Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
 | 
			
		||||
        @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
 | 
			
		||||
        @Nullable ResourceLocation overlay,
 | 
			
		||||
        boolean christmas,
 | 
			
		||||
        boolean flip
 | 
			
		||||
    ) {
 | 
			
		||||
        Combination copy() {
 | 
			
		||||
            if (leftUpgrade == null && rightUpgrade == null) return this;
 | 
			
		||||
            return new Combination(
 | 
			
		||||
                colour, UpgradeData.copyOf(leftUpgrade), UpgradeData.copyOf(rightUpgrade),
 | 
			
		||||
                overlay, christmas, flip
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final BakedModel familyModel;
 | 
			
		||||
    private final BakedModel colourModel;
 | 
			
		||||
    private final Function<TransformedModel, BakedModel> transformer;
 | 
			
		||||
    private final Function<Combination, T> buildModel;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
 | 
			
		||||
     * instances, reducing memory usage and hopefully ensuring their caches are hit more often!
 | 
			
		||||
     */
 | 
			
		||||
    private final Map<TransformedModel, BakedModel> transformCache = new HashMap<>();
 | 
			
		||||
    private final Map<TransformedModel, BakedModel> transformCache = CacheBuilder.newBuilder()
 | 
			
		||||
        .concurrencyLevel(1)
 | 
			
		||||
        .expireAfterAccess(30, TimeUnit.SECONDS)
 | 
			
		||||
        .<TransformedModel, BakedModel>build()
 | 
			
		||||
        .asMap();
 | 
			
		||||
 | 
			
		||||
    public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer) {
 | 
			
		||||
    /**
 | 
			
		||||
     * A cache of {@link Combination}s to the combined model.
 | 
			
		||||
     */
 | 
			
		||||
    private final Map<Combination, T> modelCache = CacheBuilder.newBuilder()
 | 
			
		||||
        .concurrencyLevel(1)
 | 
			
		||||
        .expireAfterAccess(30, TimeUnit.SECONDS)
 | 
			
		||||
        .<Combination, T>build()
 | 
			
		||||
        .asMap();
 | 
			
		||||
 | 
			
		||||
    public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer, Function<List<BakedModel>, T> combineModel) {
 | 
			
		||||
        this.familyModel = familyModel;
 | 
			
		||||
        this.colourModel = colourModel;
 | 
			
		||||
        this.transformer = x -> transformer.transform(x.getModel(), x.getMatrix());
 | 
			
		||||
        buildModel = x -> combineModel.apply(buildModel(x));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Combination getCombination(ItemStack stack) {
 | 
			
		||||
    public T getModel(ItemStack stack) {
 | 
			
		||||
        var combination = getCombination(stack);
 | 
			
		||||
        var existing = modelCache.get(combination);
 | 
			
		||||
        if (existing != null) return existing;
 | 
			
		||||
 | 
			
		||||
        // Take a defensive copy of the upgrade data, and add it to the cache.
 | 
			
		||||
        var newCombination = combination.copy();
 | 
			
		||||
        var newModel = buildModel.apply(newCombination);
 | 
			
		||||
        modelCache.put(newCombination, newModel);
 | 
			
		||||
        return newModel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Combination getCombination(ItemStack stack) {
 | 
			
		||||
        var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS;
 | 
			
		||||
 | 
			
		||||
        if (!(stack.getItem() instanceof TurtleItem turtle)) {
 | 
			
		||||
@@ -76,8 +114,8 @@ public final class TurtleModelParts {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var colour = turtle.getColour(stack);
 | 
			
		||||
        var leftUpgrade = turtle.getUpgrade(stack, TurtleSide.LEFT);
 | 
			
		||||
        var rightUpgrade = turtle.getUpgrade(stack, TurtleSide.RIGHT);
 | 
			
		||||
        var leftUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.LEFT);
 | 
			
		||||
        var rightUpgrade = turtle.getUpgradeWithData(stack, TurtleSide.RIGHT);
 | 
			
		||||
        var overlay = turtle.getOverlay(stack);
 | 
			
		||||
        var label = turtle.getLabel(stack);
 | 
			
		||||
        var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
 | 
			
		||||
@@ -85,7 +123,7 @@ public final class TurtleModelParts {
 | 
			
		||||
        return new Combination(colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<BakedModel> buildModel(Combination combo) {
 | 
			
		||||
    private List<BakedModel> buildModel(Combination combo) {
 | 
			
		||||
        var mc = Minecraft.getInstance();
 | 
			
		||||
        var modelManager = mc.getItemRenderer().getItemModelShaper().getModelManager();
 | 
			
		||||
 | 
			
		||||
@@ -97,19 +135,20 @@ public final class TurtleModelParts {
 | 
			
		||||
        if (overlayModelLocation != null) {
 | 
			
		||||
            parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, overlayModelLocation), transformation));
 | 
			
		||||
        }
 | 
			
		||||
        if (combo.leftUpgrade() != null) {
 | 
			
		||||
            var model = TurtleUpgradeModellers.getModel(combo.leftUpgrade(), null, TurtleSide.LEFT);
 | 
			
		||||
            parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
 | 
			
		||||
        }
 | 
			
		||||
        if (combo.rightUpgrade() != null) {
 | 
			
		||||
            var model = TurtleUpgradeModellers.getModel(combo.rightUpgrade(), null, TurtleSide.RIGHT);
 | 
			
		||||
            parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
 | 
			
		||||
        addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
 | 
			
		||||
 | 
			
		||||
        return parts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BakedModel transform(BakedModel model, Transformation transformation) {
 | 
			
		||||
    private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
 | 
			
		||||
        if (upgrade == null) return;
 | 
			
		||||
        var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
 | 
			
		||||
        parts.add(transform(model.getModel(), transformation.compose(model.getMatrix())));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private BakedModel transform(BakedModel model, Transformation transformation) {
 | 
			
		||||
        if (transformation.equals(Transformation.identity())) return model;
 | 
			
		||||
        return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,13 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.platform;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import dan200.computercraft.shared.network.NetworkMessage;
 | 
			
		||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
 | 
			
		||||
    static ClientPlatformHelper get() {
 | 
			
		||||
@@ -18,4 +23,16 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
 | 
			
		||||
     * @param message The message to send.
 | 
			
		||||
     */
 | 
			
		||||
    void sendToServer(NetworkMessage<ServerNetworkContext> message);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a {@link BakedModel}, using any loader-specific hooks.
 | 
			
		||||
     *
 | 
			
		||||
     * @param transform     The current matrix transformation to apply.
 | 
			
		||||
     * @param buffers       The current pool of render buffers.
 | 
			
		||||
     * @param model         The model to draw.
 | 
			
		||||
     * @param lightmapCoord The current packed lightmap coordinate.
 | 
			
		||||
     * @param overlayLight  The current overlay light.
 | 
			
		||||
     * @param tints         Block colour tints to apply to the model.
 | 
			
		||||
     */
 | 
			
		||||
    void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] tints);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,25 +4,17 @@
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.render;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.Tesselator;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexConsumer;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.renderer.RenderType;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
import dan200.computercraft.client.gui.GuiSprites;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
 | 
			
		||||
 | 
			
		||||
import static dan200.computercraft.client.render.SpriteRenderer.u;
 | 
			
		||||
import static dan200.computercraft.client.render.SpriteRenderer.v;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Renders the borders of computers, either for a GUI ({@link dan200.computercraft.client.gui.ComputerScreen}) or
 | 
			
		||||
 * {@linkplain PocketItemRenderer in-hand pocket computers}.
 | 
			
		||||
 */
 | 
			
		||||
public class ComputerBorderRenderer {
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_normal.png");
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_advanced.png");
 | 
			
		||||
    private static final ResourceLocation BACKGROUND_COMMAND = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_command.png");
 | 
			
		||||
    public static final ResourceLocation BACKGROUND_COLOUR = new ResourceLocation(ComputerCraftAPI.MOD_ID, "textures/gui/corners_colour.png");
 | 
			
		||||
 | 
			
		||||
public final class ComputerBorderRenderer {
 | 
			
		||||
    /**
 | 
			
		||||
     * The margin between the terminal and its border.
 | 
			
		||||
     */
 | 
			
		||||
@@ -33,100 +25,51 @@ public class ComputerBorderRenderer {
 | 
			
		||||
     */
 | 
			
		||||
    public static final int BORDER = 12;
 | 
			
		||||
 | 
			
		||||
    private static final int CORNER_TOP_Y = 28;
 | 
			
		||||
    private static final int CORNER_BOTTOM_Y = CORNER_TOP_Y + BORDER;
 | 
			
		||||
    private static final int CORNER_LEFT_X = BORDER;
 | 
			
		||||
    private static final int CORNER_RIGHT_X = CORNER_LEFT_X + BORDER;
 | 
			
		||||
    private static final int BORDER_RIGHT_X = 36;
 | 
			
		||||
    private static final int LIGHT_BORDER_Y = 56;
 | 
			
		||||
    private static final int LIGHT_CORNER_Y = 80;
 | 
			
		||||
 | 
			
		||||
    public static final int LIGHT_HEIGHT = 8;
 | 
			
		||||
 | 
			
		||||
    public static final int TEX_SIZE = 256;
 | 
			
		||||
    private static final float TEX_SCALE = 1 / (float) TEX_SIZE;
 | 
			
		||||
    private static final int TEX_SIZE = 36;
 | 
			
		||||
 | 
			
		||||
    private final Matrix4f transform;
 | 
			
		||||
    private final VertexConsumer builder;
 | 
			
		||||
    private final int light;
 | 
			
		||||
    private final int z;
 | 
			
		||||
    private final float r, g, b;
 | 
			
		||||
 | 
			
		||||
    public ComputerBorderRenderer(Matrix4f transform, VertexConsumer builder, int z, int light, float r, float g, float b) {
 | 
			
		||||
        this.transform = transform;
 | 
			
		||||
        this.builder = builder;
 | 
			
		||||
        this.z = z;
 | 
			
		||||
        this.light = light;
 | 
			
		||||
        this.r = r;
 | 
			
		||||
        this.g = g;
 | 
			
		||||
        this.b = b;
 | 
			
		||||
    private ComputerBorderRenderer() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static ResourceLocation getTexture(ComputerFamily family) {
 | 
			
		||||
        return switch (family) {
 | 
			
		||||
            case NORMAL -> BACKGROUND_NORMAL;
 | 
			
		||||
            case ADVANCED -> BACKGROUND_ADVANCED;
 | 
			
		||||
            case COMMAND -> BACKGROUND_COMMAND;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static RenderType getRenderType(ResourceLocation location) {
 | 
			
		||||
        // See note in RenderTypes about why we use text rather than anything intuitive.
 | 
			
		||||
        return RenderType.text(location);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void render(Matrix4f transform, ResourceLocation location, int x, int y, int light, int width, int height) {
 | 
			
		||||
        var source = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder());
 | 
			
		||||
        render(transform, source.getBuffer(getRenderType(location)), x, y, 1, light, width, height, false, 1, 1, 1);
 | 
			
		||||
        source.endBatch();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void render(Matrix4f transform, VertexConsumer buffer, int x, int y, int z, int light, int width, int height, boolean withLight, float r, float g, float b) {
 | 
			
		||||
        new ComputerBorderRenderer(transform, buffer, z, light, r, g, b).doRender(x, y, width, height, withLight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void doRender(int x, int y, int width, int height, boolean withLight) {
 | 
			
		||||
    public static void render(SpriteRenderer renderer, GuiSprites.ComputerTextures textures, int x, int y, int width, int height, boolean withLight) {
 | 
			
		||||
        var endX = x + width;
 | 
			
		||||
        var endY = y + height;
 | 
			
		||||
 | 
			
		||||
        // Vertical bars
 | 
			
		||||
        renderLine(x - BORDER, y, 0, CORNER_TOP_Y, BORDER, endY - y);
 | 
			
		||||
        renderLine(endX, y, BORDER_RIGHT_X, CORNER_TOP_Y, BORDER, endY - y);
 | 
			
		||||
        var border = GuiSprites.get(textures.border());
 | 
			
		||||
 | 
			
		||||
        // Top bar
 | 
			
		||||
        renderLine(x, y - BORDER, 0, 0, endX - x, BORDER);
 | 
			
		||||
        renderCorner(x - BORDER, y - BORDER, CORNER_LEFT_X, CORNER_TOP_Y);
 | 
			
		||||
        renderCorner(endX, y - BORDER, CORNER_RIGHT_X, CORNER_TOP_Y);
 | 
			
		||||
        blitBorder(renderer, border, x - BORDER, y - BORDER, 0, 0, BORDER, BORDER);
 | 
			
		||||
        blitBorder(renderer, border, x, y - BORDER, BORDER, 0, width, BORDER);
 | 
			
		||||
        blitBorder(renderer, border, endX, y - BORDER, BORDER * 2, 0, BORDER, BORDER);
 | 
			
		||||
 | 
			
		||||
        // Vertical bars
 | 
			
		||||
        blitBorder(renderer, border, x - BORDER, y, 0, BORDER, BORDER, height);
 | 
			
		||||
        blitBorder(renderer, border, endX, y, BORDER * 2, BORDER, BORDER, height);
 | 
			
		||||
 | 
			
		||||
        // Bottom bar. We allow for drawing a stretched version, which allows for additional elements (such as the
 | 
			
		||||
        // pocket computer's lights).
 | 
			
		||||
        if (withLight) {
 | 
			
		||||
            renderTexture(x, endY, 0, LIGHT_BORDER_Y, endX - x, BORDER + LIGHT_HEIGHT, BORDER, BORDER + LIGHT_HEIGHT);
 | 
			
		||||
            renderTexture(x - BORDER, endY, CORNER_LEFT_X, LIGHT_CORNER_Y, BORDER, BORDER + LIGHT_HEIGHT);
 | 
			
		||||
            renderTexture(endX, endY, CORNER_RIGHT_X, LIGHT_CORNER_Y, BORDER, BORDER + LIGHT_HEIGHT);
 | 
			
		||||
            var pocketBottomTexture = textures.pocketBottom();
 | 
			
		||||
            if (pocketBottomTexture == null) throw new NullPointerException(textures + " has no pocket texture");
 | 
			
		||||
            var pocketBottom = GuiSprites.get(pocketBottomTexture);
 | 
			
		||||
 | 
			
		||||
            renderer.blitHorizontalSliced(
 | 
			
		||||
                pocketBottom, x - BORDER, endY, width + BORDER * 2, BORDER + LIGHT_HEIGHT,
 | 
			
		||||
                BORDER, BORDER, BORDER * 3
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            renderLine(x, endY, 0, BORDER, endX - x, BORDER);
 | 
			
		||||
            renderCorner(x - BORDER, endY, CORNER_LEFT_X, CORNER_BOTTOM_Y);
 | 
			
		||||
            renderCorner(endX, endY, CORNER_RIGHT_X, CORNER_BOTTOM_Y);
 | 
			
		||||
            blitBorder(renderer, border, x - BORDER, endY, 0, BORDER * 2, BORDER, BORDER);
 | 
			
		||||
            blitBorder(renderer, border, x, endY, BORDER, BORDER * 2, width, BORDER);
 | 
			
		||||
            blitBorder(renderer, border, endX, endY, BORDER * 2, BORDER * 2, BORDER, BORDER);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void renderCorner(int x, int y, int u, int v) {
 | 
			
		||||
        renderTexture(x, y, u, v, BORDER, BORDER, BORDER, BORDER);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void renderLine(int x, int y, int u, int v, int width, int height) {
 | 
			
		||||
        renderTexture(x, y, u, v, width, height, BORDER, BORDER);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void renderTexture(int x, int y, int u, int v, int width, int height) {
 | 
			
		||||
        renderTexture(x, y, u, v, width, height, width, height);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void renderTexture(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight) {
 | 
			
		||||
        builder.vertex(transform, x, y + height, z).color(r, g, b, 1.0f).uv(u * TEX_SCALE, (v + textureHeight) * TEX_SCALE).uv2(light).endVertex();
 | 
			
		||||
        builder.vertex(transform, x + width, y + height, z).color(r, g, b, 1.0f).uv((u + textureWidth) * TEX_SCALE, (v + textureHeight) * TEX_SCALE).uv2(light).endVertex();
 | 
			
		||||
        builder.vertex(transform, x + width, y, z).color(r, g, b, 1.0f).uv((u + textureWidth) * TEX_SCALE, v * TEX_SCALE).uv2(light).endVertex();
 | 
			
		||||
        builder.vertex(transform, x, y, z).color(r, g, b, 1.0f).uv(u * TEX_SCALE, v * TEX_SCALE).uv2(light).endVertex();
 | 
			
		||||
    private static void blitBorder(SpriteRenderer renderer, TextureAtlasSprite sprite, int x, int y, int u, int v, int width, int height) {
 | 
			
		||||
        renderer.blit(
 | 
			
		||||
            x, y, width, height,
 | 
			
		||||
            u(sprite, u, TEX_SIZE), v(sprite, v, TEX_SIZE),
 | 
			
		||||
            u(sprite, u + BORDER, TEX_SIZE), v(sprite, v + BORDER, TEX_SIZE)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,103 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.render;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexConsumer;
 | 
			
		||||
import dan200.computercraft.client.model.turtle.ModelTransformer;
 | 
			
		||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.renderer.block.model.BakedQuad;
 | 
			
		||||
import net.minecraft.client.renderer.entity.ItemRenderer;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.world.item.ItemStack;
 | 
			
		||||
import org.joml.Vector4f;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utilities for rendering {@link BakedModel}s and {@link BakedQuad}s.
 | 
			
		||||
 */
 | 
			
		||||
public final class ModelRenderer {
 | 
			
		||||
    private ModelRenderer() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a list of {@linkplain BakedQuad quads} to a buffer.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The
 | 
			
		||||
     * implementation here is pretty similar to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, ItemStack, int, int)},
 | 
			
		||||
     * but supports inverted quads (i.e. those with a negative scale).
 | 
			
		||||
     *
 | 
			
		||||
     * @param transform     The current matrix transformation to apply.
 | 
			
		||||
     * @param buffer        The buffer to draw to.
 | 
			
		||||
     * @param quads         The quads to draw.
 | 
			
		||||
     * @param lightmapCoord The current packed lightmap coordinate.
 | 
			
		||||
     * @param overlayLight  The current overlay light.
 | 
			
		||||
     * @param tints         Block colour tints to apply to the model.
 | 
			
		||||
     */
 | 
			
		||||
    public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, @Nullable int[] tints) {
 | 
			
		||||
        var matrix = transform.last();
 | 
			
		||||
        var inverted = matrix.pose().determinant() < 0;
 | 
			
		||||
 | 
			
		||||
        for (var bakedquad : quads) {
 | 
			
		||||
            var tint = -1;
 | 
			
		||||
            if (tints != null && bakedquad.isTinted()) {
 | 
			
		||||
                var idx = bakedquad.getTintIndex();
 | 
			
		||||
                if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var r = (float) (tint >> 16 & 255) / 255.0F;
 | 
			
		||||
            var g = (float) (tint >> 8 & 255) / 255.0F;
 | 
			
		||||
            var b = (float) (tint & 255) / 255.0F;
 | 
			
		||||
            putBulkQuad(buffer, matrix, bakedquad, r, g, b, lightmapCoord, overlayLight, inverted);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, int, int)} which
 | 
			
		||||
     * will reverse vertex order when the matrix is inverted.
 | 
			
		||||
     *
 | 
			
		||||
     * @param buffer        The buffer to draw to.
 | 
			
		||||
     * @param pose          The current matrix stack.
 | 
			
		||||
     * @param quad          The quad to draw.
 | 
			
		||||
     * @param red           The red tint of this quad.
 | 
			
		||||
     * @param green         The  green tint of this quad.
 | 
			
		||||
     * @param blue          The blue tint of this quad.
 | 
			
		||||
     * @param lightmapCoord The lightmap coordinate
 | 
			
		||||
     * @param overlayLight  The overlay light.
 | 
			
		||||
     * @param invert        Whether to reverse the order of this quad.
 | 
			
		||||
     */
 | 
			
		||||
    private static void putBulkQuad(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, float red, float green, float blue, int lightmapCoord, int overlayLight, boolean invert) {
 | 
			
		||||
        var matrix = pose.pose();
 | 
			
		||||
        // It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
 | 
			
		||||
        // Direction.rotate (so not out of nowhere!), but is a little suspicious.
 | 
			
		||||
        var dirNormal = quad.getDirection().getNormal();
 | 
			
		||||
        var vector = new Vector4f();
 | 
			
		||||
 | 
			
		||||
        matrix.transform(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f, vector).normalize();
 | 
			
		||||
        float normalX = vector.x(), normalY = vector.y(), normalZ = vector.z();
 | 
			
		||||
 | 
			
		||||
        var vertices = quad.getVertices();
 | 
			
		||||
        for (var vertex = 0; vertex < 4; vertex++) {
 | 
			
		||||
            var i = ModelTransformer.getVertexOffset(vertex, invert);
 | 
			
		||||
 | 
			
		||||
            var x = Float.intBitsToFloat(vertices[i]);
 | 
			
		||||
            var y = Float.intBitsToFloat(vertices[i + 1]);
 | 
			
		||||
            var z = Float.intBitsToFloat(vertices[i + 2]);
 | 
			
		||||
 | 
			
		||||
            matrix.transform(x, y, z, 1, vector);
 | 
			
		||||
 | 
			
		||||
            var u = Float.intBitsToFloat(vertices[i + 4]);
 | 
			
		||||
            var v = Float.intBitsToFloat(vertices[i + 5]);
 | 
			
		||||
            buffer.vertex(
 | 
			
		||||
                vector.x(), vector.y(), vector.z(),
 | 
			
		||||
                red, green, blue, 1.0F, u, v, overlayLight, lightmapCoord,
 | 
			
		||||
                normalX, normalY, normalZ
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@ package dan200.computercraft.client.render;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.math.Axis;
 | 
			
		||||
import dan200.computercraft.client.gui.GuiSprites;
 | 
			
		||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
 | 
			
		||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
 | 
			
		||||
import dan200.computercraft.core.util.Colour;
 | 
			
		||||
@@ -72,13 +73,14 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void renderFrame(Matrix4f transform, MultiBufferSource render, ComputerFamily family, int colour, int light, int width, int height) {
 | 
			
		||||
        var texture = colour != -1 ? ComputerBorderRenderer.BACKGROUND_COLOUR : ComputerBorderRenderer.getTexture(family);
 | 
			
		||||
        var texture = colour != -1 ? GuiSprites.COMPUTER_COLOUR : GuiSprites.getComputerTextures(family);
 | 
			
		||||
 | 
			
		||||
        var r = ((colour >>> 16) & 0xFF) / 255.0f;
 | 
			
		||||
        var g = ((colour >>> 8) & 0xFF) / 255.0f;
 | 
			
		||||
        var b = (colour & 0xFF) / 255.0f;
 | 
			
		||||
        var r = (colour >>> 16) & 0xFF;
 | 
			
		||||
        var g = (colour >>> 8) & 0xFF;
 | 
			
		||||
        var b = colour & 0xFF;
 | 
			
		||||
 | 
			
		||||
        ComputerBorderRenderer.render(transform, render.getBuffer(ComputerBorderRenderer.getRenderType(texture)), 0, 0, 0, light, width, height, true, r, g, b);
 | 
			
		||||
        var spriteRenderer = new SpriteRenderer(transform, render.getBuffer(RenderTypes.GUI_SPRITES), 0, light, r, g, b);
 | 
			
		||||
        ComputerBorderRenderer.render(spriteRenderer, texture, 0, 0, width, height, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void renderLight(PoseStack transform, MultiBufferSource render, int colour, int width, int height) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ package dan200.computercraft.client.render;
 | 
			
		||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexFormat;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
import dan200.computercraft.client.gui.GuiSprites;
 | 
			
		||||
import dan200.computercraft.client.render.monitor.MonitorTextureBufferShader;
 | 
			
		||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
 | 
			
		||||
import net.minecraft.client.renderer.GameRenderer;
 | 
			
		||||
@@ -53,6 +54,11 @@ public class RenderTypes {
 | 
			
		||||
     */
 | 
			
		||||
    public static final RenderType PRINTOUT_BACKGROUND = RenderType.text(new ResourceLocation("computercraft", "textures/gui/printout.png"));
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render type for {@linkplain GuiSprites GUI sprites}.
 | 
			
		||||
     */
 | 
			
		||||
    public static final RenderType GUI_SPRITES = RenderType.text(GuiSprites.TEXTURE);
 | 
			
		||||
 | 
			
		||||
    public static MonitorTextureBufferShader getMonitorTextureBufferShader() {
 | 
			
		||||
        if (monitorTboShader == null) throw new NullPointerException("MonitorTboShader has not been registered");
 | 
			
		||||
        return monitorTboShader;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,134 @@
 | 
			
		||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
 | 
			
		||||
//
 | 
			
		||||
// SPDX-License-Identifier: MPL-2.0
 | 
			
		||||
 | 
			
		||||
package dan200.computercraft.client.render;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexConsumer;
 | 
			
		||||
import net.minecraft.client.gui.GuiGraphics;
 | 
			
		||||
import net.minecraft.client.renderer.RenderType;
 | 
			
		||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A {@link GuiGraphics}-equivalent which is suitable for both rendering in to a GUI and in-world (as part of an entity
 | 
			
		||||
 * renderer).
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This batches all render calls together, though requires that all {@link TextureAtlasSprite}s are on the same sprite
 | 
			
		||||
 * sheet.
 | 
			
		||||
 */
 | 
			
		||||
public class SpriteRenderer {
 | 
			
		||||
    private final Matrix4f transform;
 | 
			
		||||
    private final VertexConsumer builder;
 | 
			
		||||
    private final int light;
 | 
			
		||||
    private final int z;
 | 
			
		||||
    private final int r, g, b;
 | 
			
		||||
 | 
			
		||||
    public SpriteRenderer(Matrix4f transform, VertexConsumer builder, int z, int light, int r, int g, int b) {
 | 
			
		||||
        this.transform = transform;
 | 
			
		||||
        this.builder = builder;
 | 
			
		||||
        this.z = z;
 | 
			
		||||
        this.light = light;
 | 
			
		||||
        this.r = r;
 | 
			
		||||
        this.g = g;
 | 
			
		||||
        this.b = b;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static SpriteRenderer createForGui(GuiGraphics graphics, RenderType renderType) {
 | 
			
		||||
        return new SpriteRenderer(
 | 
			
		||||
            graphics.pose().last().pose(), graphics.bufferSource().getBuffer(renderType),
 | 
			
		||||
            0, RenderTypes.FULL_BRIGHT_LIGHTMAP, 255, 255, 255
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a single sprite.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sprite The texture to draw.
 | 
			
		||||
     * @param x      The x position of the rectangle we'll draw.
 | 
			
		||||
     * @param y      The x position of the rectangle we'll draw.
 | 
			
		||||
     * @param width  The width of the rectangle we'll draw.
 | 
			
		||||
     * @param height The height of the rectangle we'll draw.
 | 
			
		||||
     */
 | 
			
		||||
    public void blit(TextureAtlasSprite sprite, int x, int y, int width, int height) {
 | 
			
		||||
        blit(x, y, width, height, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a horizontal 3-sliced texture (i.e. split into left, middle and right). Unlike {@link GuiGraphics#blitNineSliced},
 | 
			
		||||
     * the middle texture is stretched rather than repeated.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sprite       The texture to draw.
 | 
			
		||||
     * @param x            The x position of the rectangle we'll draw.
 | 
			
		||||
     * @param y            The x position of the rectangle we'll draw.
 | 
			
		||||
     * @param width        The width of the rectangle we'll draw.
 | 
			
		||||
     * @param height       The height of the rectangle we'll draw.
 | 
			
		||||
     * @param leftBorder   The width of the left border.
 | 
			
		||||
     * @param rightBorder  The width of the right border.
 | 
			
		||||
     * @param textureWidth The width of the whole texture.
 | 
			
		||||
     */
 | 
			
		||||
    public void blitHorizontalSliced(TextureAtlasSprite sprite, int x, int y, int width, int height, int leftBorder, int rightBorder, int textureWidth) {
 | 
			
		||||
        // TODO(1.20.2)/TODO(1.21.0): Drive this from mcmeta files, like vanilla does.
 | 
			
		||||
        if (width < leftBorder + rightBorder) throw new IllegalArgumentException("width is less than two borders");
 | 
			
		||||
 | 
			
		||||
        var centerStart = SpriteRenderer.u(sprite, leftBorder, textureWidth);
 | 
			
		||||
        var centerEnd = SpriteRenderer.u(sprite, textureWidth - rightBorder, textureWidth);
 | 
			
		||||
 | 
			
		||||
        blit(x, y, leftBorder, height, sprite.getU0(), sprite.getV0(), centerStart, sprite.getV1());
 | 
			
		||||
        blit(x + leftBorder, y, width - leftBorder - rightBorder, height, centerStart, sprite.getV0(), centerEnd, sprite.getV1());
 | 
			
		||||
        blit(x + width - rightBorder, y, rightBorder, height, centerEnd, sprite.getV0(), sprite.getU1(), sprite.getV1());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a vertical 3-sliced texture (i.e. split into top, middle and bottom). Unlike {@link GuiGraphics#blitNineSliced},
 | 
			
		||||
     * the middle texture is stretched rather than repeated.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sprite        The texture to draw.
 | 
			
		||||
     * @param x             The x position of the rectangle we'll draw.
 | 
			
		||||
     * @param y             The x position of the rectangle we'll draw.
 | 
			
		||||
     * @param width         The width of the rectangle we'll draw.
 | 
			
		||||
     * @param height        The height of the rectangle we'll draw.
 | 
			
		||||
     * @param topBorder     The height of the top border.
 | 
			
		||||
     * @param bottomBorder  The height of the bottom border.
 | 
			
		||||
     * @param textureHeight The height of the whole texture.
 | 
			
		||||
     */
 | 
			
		||||
    public void blitVerticalSliced(TextureAtlasSprite sprite, int x, int y, int width, int height, int topBorder, int bottomBorder, int textureHeight) {
 | 
			
		||||
        // TODO(1.20.2)/TODO(1.21.0): Drive this from mcmeta files, like vanilla does.
 | 
			
		||||
        if (width < topBorder + bottomBorder) throw new IllegalArgumentException("height is less than two borders");
 | 
			
		||||
 | 
			
		||||
        var centerStart = SpriteRenderer.v(sprite, topBorder, textureHeight);
 | 
			
		||||
        var centerEnd = SpriteRenderer.v(sprite, textureHeight - bottomBorder, textureHeight);
 | 
			
		||||
 | 
			
		||||
        blit(x, y, width, topBorder, sprite.getU0(), sprite.getV0(), sprite.getU1(), centerStart);
 | 
			
		||||
        blit(x, y + topBorder, width, height - topBorder - bottomBorder, sprite.getU0(), centerStart, sprite.getU1(), centerEnd);
 | 
			
		||||
        blit(x, y + height - bottomBorder, width, bottomBorder, sprite.getU0(), centerEnd, sprite.getU1(), sprite.getV1());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The low-level blit function, used to render a portion of the sprite sheet. Unlike other functions, this takes uvs rather than a single sprite.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x      The x position of the rectangle we'll draw.
 | 
			
		||||
     * @param y      The x position of the rectangle we'll draw.
 | 
			
		||||
     * @param width  The width of the rectangle we'll draw.
 | 
			
		||||
     * @param height The height of the rectangle we'll draw.
 | 
			
		||||
     * @param u0     The first U coordinate.
 | 
			
		||||
     * @param v0     The first V coordinate.
 | 
			
		||||
     * @param u1     The second U coordinate.
 | 
			
		||||
     * @param v1     The second V coordinate.
 | 
			
		||||
     */
 | 
			
		||||
    public void blit(
 | 
			
		||||
        int x, int y, int width, int height, float u0, float v0, float u1, float v1) {
 | 
			
		||||
        builder.vertex(transform, x, y + height, z).color(r, g, b, 255).uv(u0, v1).uv2(light).endVertex();
 | 
			
		||||
        builder.vertex(transform, x + width, y + height, z).color(r, g, b, 255).uv(u1, v1).uv2(light).endVertex();
 | 
			
		||||
        builder.vertex(transform, x + width, y, z).color(r, g, b, 255).uv(u1, v0).uv2(light).endVertex();
 | 
			
		||||
        builder.vertex(transform, x, y, z).color(r, g, b, 255).uv(u0, v0).uv2(light).endVertex();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static float u(TextureAtlasSprite sprite, int x, int width) {
 | 
			
		||||
        return sprite.getU((double) x / width * 16);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static float v(TextureAtlasSprite sprite, int y, int height) {
 | 
			
		||||
        return sprite.getV((double) y / height * 16);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
package dan200.computercraft.client.render;
 | 
			
		||||
 | 
			
		||||
import com.mojang.blaze3d.vertex.PoseStack;
 | 
			
		||||
import com.mojang.blaze3d.vertex.VertexConsumer;
 | 
			
		||||
import com.mojang.math.Axis;
 | 
			
		||||
import com.mojang.math.Transformation;
 | 
			
		||||
import dan200.computercraft.api.ComputerCraftAPI;
 | 
			
		||||
@@ -14,25 +13,20 @@ import dan200.computercraft.client.platform.ClientPlatformHelper;
 | 
			
		||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
 | 
			
		||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
 | 
			
		||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
 | 
			
		||||
import dan200.computercraft.shared.util.DirectionUtil;
 | 
			
		||||
import dan200.computercraft.shared.util.Holiday;
 | 
			
		||||
import net.minecraft.client.Minecraft;
 | 
			
		||||
import net.minecraft.client.gui.Font;
 | 
			
		||||
import net.minecraft.client.renderer.MultiBufferSource;
 | 
			
		||||
import net.minecraft.client.renderer.Sheets;
 | 
			
		||||
import net.minecraft.client.renderer.block.model.BakedQuad;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
 | 
			
		||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
 | 
			
		||||
import net.minecraft.client.resources.model.BakedModel;
 | 
			
		||||
import net.minecraft.client.resources.model.ModelResourceLocation;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import net.minecraft.util.RandomSource;
 | 
			
		||||
import net.minecraft.world.phys.BlockHitResult;
 | 
			
		||||
import net.minecraft.world.phys.HitResult;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
 | 
			
		||||
    private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
 | 
			
		||||
@@ -40,8 +34,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
 | 
			
		||||
    private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
 | 
			
		||||
    private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
 | 
			
		||||
 | 
			
		||||
    private final RandomSource random = RandomSource.create(0);
 | 
			
		||||
 | 
			
		||||
    private final BlockEntityRenderDispatcher renderer;
 | 
			
		||||
    private final Font font;
 | 
			
		||||
 | 
			
		||||
@@ -107,23 +99,22 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
 | 
			
		||||
        var family = turtle.getFamily();
 | 
			
		||||
        var overlay = turtle.getOverlay();
 | 
			
		||||
 | 
			
		||||
        var buffer = buffers.getBuffer(Sheets.translucentCullBlockSheet());
 | 
			
		||||
        renderModel(transform, buffer, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
 | 
			
		||||
        renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
 | 
			
		||||
 | 
			
		||||
        // Render the overlay
 | 
			
		||||
        var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
 | 
			
		||||
        if (overlayModel != null) {
 | 
			
		||||
            renderModel(transform, buffer, lightmapCoord, overlayLight, overlayModel, null);
 | 
			
		||||
            renderModel(transform, buffers, lightmapCoord, overlayLight, overlayModel, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Render the upgrades
 | 
			
		||||
        renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
 | 
			
		||||
        renderUpgrade(transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
 | 
			
		||||
        renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks);
 | 
			
		||||
        renderUpgrade(transform, buffers, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks);
 | 
			
		||||
 | 
			
		||||
        transform.popPose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void renderUpgrade(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
 | 
			
		||||
    private void renderUpgrade(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, TurtleBlockEntity turtle, TurtleSide side, float f) {
 | 
			
		||||
        var upgrade = turtle.getUpgrade(side);
 | 
			
		||||
        if (upgrade == null) return;
 | 
			
		||||
        transform.pushPose();
 | 
			
		||||
@@ -134,46 +125,33 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
 | 
			
		||||
        transform.translate(0.0f, -0.5f, -0.5f);
 | 
			
		||||
 | 
			
		||||
        var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
 | 
			
		||||
        pushPoseFromTransformation(transform, model.getMatrix());
 | 
			
		||||
        renderModel(transform, renderer, lightmapCoord, overlayLight, model.getModel(), null);
 | 
			
		||||
        transform.popPose();
 | 
			
		||||
        applyTransformation(transform, model.getMatrix());
 | 
			
		||||
        renderModel(transform, buffers, lightmapCoord, overlayLight, model.getModel(), null);
 | 
			
		||||
 | 
			
		||||
        transform.popPose();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
 | 
			
		||||
    private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
 | 
			
		||||
        var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
 | 
			
		||||
        renderModel(transform, renderer, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
 | 
			
		||||
        renderModel(transform, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void renderModel(PoseStack transform, VertexConsumer renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
 | 
			
		||||
        random.setSeed(0);
 | 
			
		||||
        renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, null, random), tints);
 | 
			
		||||
        for (var facing : DirectionUtil.FACINGS) {
 | 
			
		||||
            renderQuads(transform, renderer, lightmapCoord, overlayLight, model.getQuads(null, facing, random), tints);
 | 
			
		||||
        }
 | 
			
		||||
    /**
 | 
			
		||||
     * Render a block model.
 | 
			
		||||
     *
 | 
			
		||||
     * @param transform     The current matrix stack.
 | 
			
		||||
     * @param renderer      The buffer to write to.
 | 
			
		||||
     * @param lightmapCoord The current lightmap coordinate.
 | 
			
		||||
     * @param overlayLight  The overlay light.
 | 
			
		||||
     * @param model         The model to render.
 | 
			
		||||
     * @param tints         Tints for the quads, as an array of RGB values.
 | 
			
		||||
     * @see net.minecraft.client.renderer.block.ModelBlockRenderer#renderModel
 | 
			
		||||
     */
 | 
			
		||||
    private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, @Nullable int[] tints) {
 | 
			
		||||
        ClientPlatformHelper.get().renderBakedModel(transform, renderer, model, lightmapCoord, overlayLight, tints);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void renderQuads(PoseStack transform, VertexConsumer buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, @Nullable int[] tints) {
 | 
			
		||||
        var matrix = transform.last();
 | 
			
		||||
 | 
			
		||||
        for (var bakedquad : quads) {
 | 
			
		||||
            var tint = -1;
 | 
			
		||||
            if (tints != null && bakedquad.isTinted()) {
 | 
			
		||||
                var idx = bakedquad.getTintIndex();
 | 
			
		||||
                if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var r = (float) (tint >> 16 & 255) / 255.0F;
 | 
			
		||||
            var g = (float) (tint >> 8 & 255) / 255.0F;
 | 
			
		||||
            var b = (float) (tint & 255) / 255.0F;
 | 
			
		||||
            buffer.putBulkData(matrix, bakedquad, r, g, b, lightmapCoord, overlayLight);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void pushPoseFromTransformation(PoseStack stack, Transformation transformation) {
 | 
			
		||||
        stack.pushPose();
 | 
			
		||||
 | 
			
		||||
    private static void applyTransformation(PoseStack stack, Transformation transformation) {
 | 
			
		||||
        var trans = transformation.getTranslation();
 | 
			
		||||
        stack.translate(trans.x(), trans.y(), trans.z());
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,9 @@ import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
 | 
			
		||||
import net.minecraft.resources.ResourceLocation;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A {@link TurtleUpgradeModeller} for modems, providing different models depending on if the modem is on/off.
 | 
			
		||||
 */
 | 
			
		||||
@@ -48,4 +51,9 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
 | 
			
		||||
            ? TransformedModel.of(active ? leftOnModel : leftOffModel)
 | 
			
		||||
            : TransformedModel.of(active ? rightOnModel : rightOffModel);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Collection<ResourceLocation> getDependencies() {
 | 
			
		||||
        return List.of(leftOffModel, rightOffModel, leftOnModel, rightOnModel);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user