1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-22 17:37:38 +00:00

Compare commits

...

31 Commits

Author SHA1 Message Date
Jonathan Coates
dd7e8fcefc Bump CC:T to 1.115.1 2025-03-01 22:35:29 +00:00
Jonathan Coates
29c8f96912 Sync translations from CrowdIn
I remembered to do this *before* the update! 🎉
2025-03-01 22:34:45 +00:00
Jonathan Coates
b9267ecbfc Resize lectern pocket computer textures
This bumps them to be 48x48, which allows them to be downscaled to a
mipmap level of 4. We possibly should bump these to be 64x64 (actual
power of two), but I kinda want to avoid that, as it's so much wasted
space. If this does become a problem, we should probably put these on a
separate atlas instead.
2025-03-01 22:24:27 +00:00
Jonathan Coates
9d2c2db22b Fix speaker.playAudio not updating volume
Honestly, the whole design around volume and playSound/playAudio is a
little janky — it probably should be a separate setVolume method which
updates directly. But too late to change that now, so let's do what we
can.

See #2108
2025-03-01 20:28:37 +00:00
ellellie
6660966320 Update Create dependency to 6.0.0 (#2117) 2025-03-01 19:56:43 +00:00
Jonathan Coates
16324e1eac Flesh out detail provider/registry docs 2025-02-19 21:08:56 +00:00
Jonathan Coates
32f5c38485 Remove IMedia implementations from our items
We now register these separately, rather than relying on the implicit
IMedia. This allows us to share a bit more logic between
PocketComputerItem and AbstractComputerItem. This doesn't make much
difference on 1.20.1, but does help a bit more on 1.21.1.
2025-02-16 20:32:01 +00:00
Jonathan Coates
01fe949b3e Use Fabric's Item Lookup for registering media providers
We'll switch to capabilities on the (Neo)Forge side for 1.21, when the
capability system is less painful, and then fully deprecate in 1.21.4.
2025-02-16 20:32:01 +00:00
Jonathan Coates
c03fce275e Add some abstractions for registering capabilities
This is pretty useless right now (not even going to try to use it for
Forge), but should be a bit more useful for 1.21.
2025-02-16 20:32:00 +00:00
Jonathan Coates
0998acaa82 Switch to JSpecify annotations
Now, hear me out, what if instead of having three @Nullable annotations,
we had *four*?

I've been wanting to switch away from javax.annoations for a while. The
library has been deprecated for ever and, unlike other @Nullable
annotations, the annotation is attached to the parameter/function
itself, rather than the type.

We use JSpecify rather than one of the alternatives (JetBrains,
CheckerFramework) mostly because it's what NullAway recommends. We keep
CheckerFramework around for @DefaultQualifier, and JB's for @Contract.

There are some ugly changes here — for instance, `@Nullable byte[]` is
replace by `byte @Nullable`, and `@Nullable ILuaMachine.Factory` is
`ILuaMachine.@Nullable Factory`. Ughr, I understand why, but it does not
spark joy :).
2025-02-16 18:09:15 +00:00
Jonathan Coates
12a44fed6f Sync translations from CrowdIn 2025-02-15 18:35:05 +00:00
Jonathan Coates
0a8d505323 Bump CC:T to 1.115.0 2025-02-14 20:20:30 +00:00
Jonathan Coates
237a0ac3bb Expose printout contents to the API
Closes #2099
2025-02-14 18:13:20 +00:00
Jonathan Coates
b185d088b3 Suggest alternative table keys on nil errors (#2097)
We now suggest alternative table keys when code errors with "attempt
to index/call 'foo' (a nil value)". For example: "redstone.getinput()",
will now suggest "Did you mean: getInput".

This is a bit tricky to get right! In the above example, our code reads
like:

   1    GETTABUP 0 0 0 ; r0 := _ENV["redstone"]
   2    GETFIELD 0 0 1 ; r0 := r0["getinput"]
   3    CALL 0 1 1     ; r0()

Note, that when we get to the problematic line, we don't have access to
the original table that we attempted to index. In order to do this, we
borrow ideas from Lua's getobjname — we effectively write an evaluator
that walks back over the code and tries to reconstruct the expression
that resulted in nil.

For example, in the above case:
 - We know an instruction happened at pc=3, so we try to find the
   expression that computed r0.
 - We know this was set at pc=2, so we step back one. This is a GETFIELD
   instruction, so we check the key (it's a constant, so worth
   reporting), and then try to evaluate the table.
 - This version of r0 was set at pc=1, so we step back again. It's a
   GETTABUP instruction, so we can just evaluate that directly.

We then use this information (indexing _ENV.redstone with "getinput") to
find alternative keys (e.g. getInput, getOutput, etc...) and then pick
some likely suggestions with Damerau-Levenshtein/OSD.

I'm not entirely thrilled by the implementation here. The core
interpretation logic is implemented in Java. Which is *fine*, but a)
feels a little cheaty and b) means we're limited to what Lua bytecode
can provide (for instance, we can't inspect outer functions, or list all
available names in scope). We obviously can expand the bytecode if
needed, but something we'd want to be careful with.

The alternative approach would be to handle all the parsing in
Lua. Unfortunately, this is quite hard to get right — I think we'd need
some lazy parsing strategy to avoid constructing the whole AST, while
still retaining all the scope information we need.

I don't know. We really could make this as complex as we like, and I
don't know what the right balance is. It'd be cool to detect patterns
like the following, but is it *useful*?

    local monitor = peripheral.wrap("left")
    monitor.write("Hello")
        -- ^ monitor is nil. Is there a peripheral to the left of the
        -- computer?

For now, the current approach feels the easiest, and should allow us to
prototype things and see what does/doesn't work.
2025-02-13 21:57:29 +00:00
Jonathan Coates
051c70a731 Propagate exceptions from parallel where possible (#2095)
In the original implementation of our prettier runtime errors (#1320), we
wrapped the errors thrown within parallel functions into an exception
object. This means the call-stack is available to the catching-code, and
so is able to report a pretty exception message.

Unfortunately, this was a breaking change, and so we had to roll that
back. Some people were pcalling the parallel function, and matching on
the result of the error.

This is a second attempt at this, using a technique I've affectionately
dubbed "magic throws". The parallel API is now aware of whether it is
being pcalled or not, and thus able to decide whether to wrap the error
into an exception or not:

 - Add a new `cc.internal.tiny_require` module. This is a tiny
   reimplementation of require, for use in our global APIs.

 - Add a new (global, in the debug registry) `cc_try_barrier` function.
   This acts as a marker function, and is used to store additional
   information about the current coroutine.

   Currently this stores the parent coroutine (used to walk the full call
   stack) and a cache of whether any `pcall`-like function is on the
   stack.

   Both `parallel` and `cc.internal.exception.try` add this function to
   the root of the call stack.

 - When an error occurs within `parallel`, we walk up the call stack,
   using `cc_try_barrier` to traverse up the parent coroutine's stack
   too. If we do not find any `pcall`-like functions, then we know the
   error is never intercepted by user code, and so its safe to throw a
   full exception.
2025-02-13 17:38:57 +00:00
Jonathan Coates
2e2f308ff3 Support placing pocket computers on lecterns (#2098)
This allows shift+clicking a pocket computer on to a lectern. These
computers can be right clicked, opening the no-term computer GUI.
Terminal contents is rendered in-world, and broadcast to everyone in
range.

 - Add a new lectern PocketHolder.
 - Refactor some of the `PocketItemComputer` code to allow ticking pocket
   computers from a non-player/entity source.
 - Add a new model for pocket computers. This requires several new
   textures (somewhat mirroring the item ones), which is a little
   unfortunate, but looks much better than reusing the map renderer or
   item form.
2025-02-13 17:31:08 +00:00
Jonathan Coates
0f123b5efd Ignore unrepresentable characters when typing
In 94ad6dab0e, we changed it so typing
characters outside of CC's codepage were replaced with '?' rather than
ignored. This can be quite annoying for non-European users (where latin1
isn't very helpful!), so it makes sense to revert this change.

See discussion in #860 for more context.
2025-02-12 18:45:40 +00:00
Jonathan Coates
88cb03be6b Clean up the parallel API
- Store the filter alongside the coroutine rather than in a separate
   table (like we do in multishell).

 - Remove the redudant (I think!) second loop that checks for dead
   coroutines. We already check for dead coroutines in the main loop.

 - Rename some variables to be a bit more consistent. This makes this
   commit look noisier than it is. Sorry!
2025-02-09 16:53:59 +00:00
Jonathan Coates
4360485880 Bump CC:T to 1.114.4 2025-01-31 21:09:57 +00:00
Jonathan Coates
b69a44a927 Redo turtle move checks
Oh, this is so broken, and really has been since the 1.13 update, if not
earlier.

 - Fix call to isUnobstructed using the bounding box of the
   *destination* block rather than the turtle. This is almost always
   air, so the box is empty.

 - Because the above check has been wrong for so many years, we now
   significantly relax the "can push" checks for entities. We now allow
   pushing entities in any direction.

   We also remove the "isUnobstructed" check for the destination entity
   pos. This causes problems (if two entities are standing on a turtle,
   they'll obstruct each other), and given pistons don't perform such a
   check, I don't think we need it.

 - Also do a bit of cleanup around air/liquid checks. We often ended up
   reading the block state multiple times, which is a little ugly.
2025-01-27 22:45:52 +00:00
Jonathan Coates
7d8f609c49 literally sobbing rn 2025-01-26 22:27:50 +00:00
Jonathan Coates
e7f56c4d25 Remove ComponentMap
I'd originally kept this around to prevent mods that use CC internals,
but fa2140d00b has probably broken those
anyway, so let's not worry too much.
2025-01-26 15:13:24 +00:00
Jonathan Coates
fa2140d00b Clean up container data interface
- Remove ContainerData.open.

 - Change PlatformHelper.openMenu to take a separate display name and
   MenuConstructor, rather than a MenuProvider. This makes the interface
   slightly easier to use in the common case, where we want to use
   lambdas instead.
2025-01-26 14:56:09 +00:00
Jonathan Coates
03388149b1 Fix command computers being exposed as peripherals
- Check whether the computer is a command computer before registering
   the capability.

 - Add tests to check what is/isn't a peripheral. See also #2020, where
   we forgot to register a peripheral on NeoForge 1.21.1.

Fixes #2070.
2025-01-26 11:13:52 +00:00
Jonathan Coates
f212861370 Mark command computers as onlyOpCanSetNbt
This isn't required in vanilla, as the command computer is a
GameMasterBlock, and so isn't placeable in the first place.

*However*, this is a problem with Create contraptions — with those it's
possible to "place" a command computer complete with NBT. We override
onlyOpCanSetNbt to prevent this [^1].

[^1]: 7a7993deb8/src/main/java/com/simibubi/create/foundation/utility/NBTProcessors.java (L179)
2025-01-21 20:41:55 +00:00
Jonathan Coates
55edced9de Move GUI sprites to the sprites/ folder
This is where vanilla will read the sprites from in future versions, so
means we have a consistent layout between versions.

Also move the turtle "selected slot" texture to a sprite sheet. It would
be good to do more of these in the future (e.g. printer progress, maybe
bits of printouts).

Sorry to resource pack artists for causing trouble again.
2025-01-20 20:21:22 +00:00
Jonathan Coates
dc969c5a78 Configure ServerComputer via a Properties builder
We currently need to pass a whole bunch of arguments to a ServerComputer
in order to construct it, and if we implement #1814, this will get a
whole lot worse. Instead, we now pass most parameters (computer id,
family, label, term size, components) via a separate Properties class,
much like Minecraft does for blocks and items.

I'm not wild about the design of the API here, but I think it's a step
in the right direction.
2025-01-20 19:47:18 +00:00
Jonathan Coates
94ad6dab0e Map Unicode to CC's charset for char/paste events
We now convert uncode characters from "char" and "paste" events to CC's
charset[^1], rather than just leaving them unconverted. This means you
can paste in special characters like "♠" or "🮙" and they will be
converted correctly. Characters outside that range will be replaced with
"?", as before.

It would be nice to make this a bi-directional mapping, and do this for
Lua methods too (e.g. os.setComputerLabel). However, that has much wider
ramifications (and more likelyhood of breaking something), so avoiding
that for now.

 - Remove the generic "queue event" client->server message, and replace
   it with separate char/terminate/paste messages. This allows us to
   delete a chunk of code (all the NBT<->Object conversion), and makes
   server-side validation of events possible.

 - Fix os.setComputerLabel accepting the section sign — this is treated
   as special by Minecraft's formatting code. Sorry, no fun allowed.

 - Convert paste/char codepoints to CC's charset. Sadly MC's char hook
   splits the codepoint into surrogate pairs, which we *don't* attempt
   to reconstruct, so you can't currently use unicode input for block
   characters — you can paste them though!

[^1]: I'm referring this to the "terminal charset" within the code. I've
flip-flopped between "CraftOS", "terminal", "ComputerCraft", but feel
especially great.
2025-01-19 11:07:29 +00:00
Jonathan Coates
938eb38ad5 Move computer events to a single point
This abstraction never made much sense on InputHandler, as we only leave
the default methods on ServerComputer.

We now add a new class (ComputerEvents), which has a series of *static*
methods, that can queue an event on a ComputerEvents.Receiver object.
This is a bit of an odd indirection (why not just make them instance
methods on Receiver?!), but I don't really want those methods leaking
everywhere.
2025-01-18 19:26:10 +00:00
Jonathan Coates
6739c4c6c0 Wait for computers to run each tick in gametests 2025-01-17 18:43:19 +00:00
Jonathan Coates
d6749f8461 Set issue type in the templates
We already have the label, so it's not quite clear if it's worth it, but
let make our issue board look even more like a tube of smarties.
2025-01-17 17:25:39 +00:00
420 changed files with 3330 additions and 1551 deletions

View File

@@ -1,6 +1,7 @@
name: Bug report
description: Report some misbehaviour in the mod
labels: [ bug ]
type: bug
body:
- type: dropdown
id: mc-version
@@ -29,3 +30,5 @@ body:
Description of the bug. Please include the following:
- Logs: These will be located in the `logs/` directory of your Minecraft instance. This is always useful, even if it doesn't include errors, so please upload this!
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.
![A gif of burning text reading "Upload your logs!!!"](https://tweaked.cc/images/logs.gif)

View File

@@ -2,6 +2,7 @@
name: Feature request
about: Suggest an idea or improvement
labels: enhancement
type: feature
---
<!--

View File

@@ -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.4.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer

View File

@@ -84,7 +84,6 @@ sourceSets.all {
options.errorprone {
check("InvalidBlockTag", CheckSeverity.OFF) // Broken by @cc.xyz
check("InvalidParam", CheckSeverity.OFF) // Broken by records.
check("InlineMeSuggester", CheckSeverity.OFF) // Minecraft uses @Deprecated liberally
// Too many false positives right now. Maybe we need an indirection for it later on.
check("ReferenceEquality", CheckSeverity.OFF)

View File

@@ -10,9 +10,7 @@ import org.gradle.api.GradleException
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.SourceSet
@@ -168,7 +166,7 @@ abstract class CCTweakedExtension(private val project: Project) {
jacoco.applyTo(this)
extensions.configure(JacocoTaskExtension::class.java) {
includes = listOf("dan200.computercraft.*")
includes = listOf("dan200.computercraft.*")
excludes = listOf(
"dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime.
"dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.

View File

@@ -92,7 +92,10 @@ SPDX-License-Identifier: MPL-2.0
<module name="InvalidJavadocPosition" />
<module name="JavadocBlockTagLocation" />
<module name="JavadocMethod"/>
<module name="JavadocType"/>
<module name="JavadocType">
<!-- Seems to complain about @hidden!? -->
<property name="allowUnknownTags" value="true" />
</module>
<module name="JavadocStyle">
<property name="checkHtml" value="false" />
</module>
@@ -143,7 +146,10 @@ SPDX-License-Identifier: MPL-2.0
<module name="NoWhitespaceAfter">
<property name="tokens" value="AT,INC,DEC,UNARY_MINUS,UNARY_PLUS,BNOT,LNOT,DOT,ARRAY_DECLARATOR,INDEX_OP,METHOD_REF" />
</module>
<module name="NoWhitespaceBefore" />
<module name="NoWhitespaceBefore">
<!-- Allow whitespace before "..." for @Nullable annotations -->
<property name="tokens" value="COMMA,SEMI,POST_INC,POST_DEC,LABELED_STAT" />
</module>
<!-- TODO: Decide on an OperatorWrap style. -->
<module name="ParenPad" />
<module name="SeparatorWrap">

View File

@@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=false
modVersion=1.114.3
modVersion=1.115.1
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1

View File

@@ -29,9 +29,9 @@ checkerFramework = "3.42.0"
cobalt = { strictly = "0.9.5" }
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
jspecify = "1.0.0"
jzlib = "1.1.3"
kotlin = "2.1.0"
kotlin = "2.1.10"
kotlin-coroutines = "1.10.1"
nightConfig = "3.8.1"
@@ -46,7 +46,7 @@ oculus = "1.2.5"
rei = "12.0.626"
rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
create-forge = "0.5.1.f-33"
create-forge = "6.0.0-9"
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
# Testing
@@ -58,9 +58,9 @@ jmh = "1.37"
# Build tools
cctJavadoc = "1.8.3"
checkstyle = "10.14.1"
errorProne-core = "2.27.0"
errorProne-plugin = "3.1.0"
checkstyle = "10.21.2"
errorProne-core = "2.36.0"
errorProne-plugin = "4.1.0"
fabric-loom = "1.9.2"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
@@ -69,7 +69,7 @@ illuaminate = "0.1.0-74-gf1551d5"
lwjgl = "3.3.3"
minotaur = "2.8.7"
modDevGradle = "2.0.74"
nullAway = "0.10.25"
nullAway = "0.12.3"
shadow = "8.3.1"
spotless = "6.23.3"
taskTree = "2.1.1"
@@ -89,7 +89,7 @@ fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "jsr305" }
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
jzlib = { module = "com.jcraft:jzlib", version.ref = "jzlib" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-platform = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
@@ -178,7 +178,7 @@ taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
[bundles]
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
annotations = ["checkerFramework", "jetbrainsAnnotations", "jspecify"]
kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
# Minecraft

View File

@@ -12,8 +12,8 @@ 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 org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;

View File

@@ -14,8 +14,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
final class TurtleUpgradeModellers {
private static final Transformation leftTransform = getMatrixFor(-0.4065f);

View File

@@ -11,8 +11,7 @@ import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
@ApiStatus.Internal
public interface ClientPlatformHelper {

View File

@@ -10,8 +10,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.Services;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Backing interface for {@link ComputerCraftAPIClient}

View File

@@ -26,8 +26,7 @@ import net.minecraft.core.Direction;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* The static entry point to the ComputerCraft API.

View File

@@ -6,8 +6,8 @@ package dan200.computercraft.api.detail;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

View File

@@ -8,8 +8,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* A reference to a block in the world, used by block detail providers.

View File

@@ -14,6 +14,7 @@ import java.util.Map;
*
* @param <T> The type of object that this provider can provide details for.
* @see DetailRegistry
* @see dan200.computercraft.api.detail An overview of the detail system.
*/
@FunctionalInterface
public interface DetailProvider<T> {

View File

@@ -17,6 +17,7 @@ import java.util.Map;
* also in this package.
*
* @param <T> The type of object that this registry provides details for.
* @see dan200.computercraft.api.detail An overview of the detail system.
*/
@ApiStatus.NonExtendable
public interface DetailRegistry<T> {

View File

@@ -17,6 +17,9 @@ public class VanillaDetailRegistries {
* <p>
* This instance's {@link DetailRegistry#getBasicDetails(Object)} is thread safe (assuming the stack is immutable)
* and may be called from the computer thread.
* <p>
* This does not have special handling for {@linkplain ItemStack#isEmpty() empty item stacks}, and so the returned
* details will be an empty stack of air. Callers should generally check for empty stacks before calling this.
*/
public static final DetailRegistry<ItemStack> ITEM_STACK = ComputerCraftAPIService.get().getItemStackDetailRegistry();

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
/**
* The detail system provides a standard way for mods to return descriptions of common game objects, such as blocks or
* items, as well as registering additional detail to be included in those descriptions.
* <p>
* For instance, the built-in {@code turtle.getItemDetail()} method uses
* {@linkplain dan200.computercraft.api.detail.VanillaDetailRegistries#ITEM_STACK in order to provide information about}
* the selected item:
*
* <pre class="language language-lua">{@code
* local item = turtle.getItemDetail(nil, true)
* --[[
* item = {
* name = "minecraft:wheat",
* displayName = "Wheat",
* count = 1,
* maxCount = 64,
* tags = {},
* }
* ]]
* }</pre>
*
* <h2>Built-in detail providers</h2>
* While you can define your own detail providers (perhaps for types from your own mod), CC comes with several built-in
* detail registries for vanilla and mod-loader objects:
*
* <ul>
* <li>{@link dan200.computercraft.api.detail.VanillaDetailRegistries}, for vanilla objects</li>
* <li>{@code dan200.computercraft.api.detail.ForgeDetailRegistries} for Forge-specific objects</li>
* <li>{@code dan200.computercraft.api.detail.FabricDetailRegistries} for Fabric-specific objects</li>
* </ul>
*
* <h2>Example: Returning details from methods</h2>
* Here we define a {@code getHeldItem()} method for pocket computers which finds the currently held item of the player
* and returns it to the user using {@link dan200.computercraft.api.detail.VanillaDetailRegistries#ITEM_STACK} and
* {@link dan200.computercraft.api.detail.DetailRegistry#getDetails(java.lang.Object)}.
*
* {@snippet class=com.example.examplemod.ExamplePocketPeripheral region=details}
*
* <h2>Example: Registering custom detail registries</h2>
* Here we define a new detail provider for items that includes the nutrition and saturation values in the returned object.
*
* {@snippet class=com.example.examplemod.ExampleMod region=details}
*/
package dan200.computercraft.api.detail;

View File

@@ -9,8 +9,7 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* An interface passed to {@link ILuaAPIFactory} in order to provide additional information

View File

@@ -5,8 +5,7 @@
package dan200.computercraft.api.lua;
import dan200.computercraft.api.ComputerCraftAPI;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Construct an {@link ILuaAPI} for a computer.

View File

@@ -12,8 +12,7 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Represents an item that can be placed in a disk drive and used by a Computer.

View File

@@ -5,8 +5,7 @@
package dan200.computercraft.api.media;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* This interface is used to provide {@link IMedia} implementations for {@link ItemStack}.

View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.api.media;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import java.util.stream.Stream;
/**
* The contents of a page (or book) created by a ComputerCraft printer.
*
* @since 1.115
*/
@Nullable
public interface PrintoutContents {
/**
* Get the (possibly empty) title for this printout.
*
* @return The title of this printout.
*/
String getTitle();
/**
* Get the text contents of this printout, as a sequence of lines.
* <p>
* The lines in the printout may include blank lines at the end of the document, as well as trailing spaces on each
* line.
*
* @return The text contents of this printout.
*/
Stream<String> getTextLines();
/**
* Get the printout contents for a particular stack.
*
* @param stack The stack to get the contents for.
* @return The printout contents, or {@code null} if this is not a printout item.
*/
static @Nullable PrintoutContents get(ItemStack stack) {
return ComputerCraftAPIService.get().getPrintoutContents(stack);
}
}

View File

@@ -19,8 +19,8 @@ import dan200.computercraft.api.ComputerCraftAPI;
*/
public interface WiredElement extends WiredSender {
/**
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
* peripherals change.
* Called when peripherals on the network change. This may occur when network nodes are added or removed, or when
* peripherals are attached or detached from a modem.
*
* @param change The change which occurred.
* @see WiredNetworkChange

View File

@@ -14,8 +14,8 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Map;
/**

View File

@@ -7,8 +7,7 @@ package dan200.computercraft.api.pocket;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* A peripheral which can be equipped to the back side of a pocket computer.

View File

@@ -17,8 +17,7 @@ import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* The interface passed to turtle by turtles, providing methods that they can call.

View File

@@ -9,8 +9,8 @@ import dan200.computercraft.api.upgrades.UpgradeBase;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.Items;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.BiFunction;
/**

View File

@@ -5,8 +5,7 @@
package dan200.computercraft.api.turtle;
import net.minecraft.core.Direction;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Used to indicate the result of executing a turtle command.
@@ -60,9 +59,9 @@ public final class TurtleCommandResult {
private final boolean success;
private final @Nullable String errorMessage;
private final @Nullable Object[] results;
private final @Nullable Object @Nullable [] results;
private TurtleCommandResult(boolean success, @Nullable String errorMessage, @Nullable Object[] results) {
private TurtleCommandResult(boolean success, @Nullable String errorMessage, @Nullable Object @Nullable [] results) {
this.success = success;
this.errorMessage = errorMessage;
this.results = results;
@@ -92,8 +91,7 @@ public final class TurtleCommandResult {
*
* @return The command's result, or {@code null} if it was a failure.
*/
@Nullable
public Object[] getResults() {
public @Nullable Object @Nullable [] getResults() {
return results;
}
}

View File

@@ -18,8 +18,8 @@ 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 org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.Consumer;
/**

View File

@@ -9,8 +9,7 @@ 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;
import org.jspecify.annotations.Nullable;
/**
* An upgrade (i.e. a {@link ITurtleUpgrade}) and its current upgrade data.

View File

@@ -19,8 +19,8 @@ import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

View File

@@ -12,6 +12,7 @@ import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.media.PrintoutContents;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
@@ -27,8 +28,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Backing interface for {@link ComputerCraftAPI}
@@ -75,6 +75,9 @@ public interface ComputerCraftAPIService {
DetailRegistry<BlockReference> getBlockInWorldDetailRegistry();
@Nullable
PrintoutContents getPrintoutContents(ItemStack stack);
final class Instance {
static final @Nullable ComputerCraftAPIService INSTANCE;
static final @Nullable Throwable ERROR;

View File

@@ -12,8 +12,7 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Abstraction layer for Forge and Fabric. See implementations for more details.

View File

@@ -5,8 +5,8 @@
package dan200.computercraft.impl;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.io.Serial;
/**

View File

@@ -5,8 +5,8 @@
package dan200.computercraft.impl;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ServiceLoader;
import java.util.stream.Collectors;

View File

@@ -37,7 +37,6 @@ dependencies {
clientApi(clientClasses(project(":common-api")))
compileOnly(libs.bundles.externalMods.common)
compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
annotationProcessorEverywhere(libs.autoService)

View File

@@ -38,8 +38,8 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.Consumer;
/**

View File

@@ -46,8 +46,8 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.function.BiConsumer;

View File

@@ -15,8 +15,8 @@ import net.minecraft.client.gui.components.ChatComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import org.apache.commons.lang3.StringUtils;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Objects;
/**

View File

@@ -26,11 +26,11 @@ import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;

View File

@@ -10,10 +10,10 @@ import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
import dan200.computercraft.shared.network.server.KeyEventServerMessage;
import dan200.computercraft.shared.network.server.MouseEventServerMessage;
import dan200.computercraft.shared.network.server.QueueEventServerMessage;
import dan200.computercraft.shared.network.server.PasteEventComputerMessage;
import net.minecraft.world.inventory.AbstractContainerMenu;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
/**
* An {@link InputHandler} for use on the client.
@@ -27,6 +27,11 @@ public final class ClientInputHandler implements InputHandler {
this.menu = menu;
}
@Override
public void terminate() {
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TERMINATE));
}
@Override
public void turnOn() {
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
@@ -42,11 +47,6 @@ public final class ClientInputHandler implements InputHandler {
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
}
@Override
public void queueEvent(String event, @Nullable Object[] arguments) {
ClientNetworking.sendToServer(new QueueEventServerMessage(menu, event, arguments));
}
@Override
public void keyDown(int key, boolean repeat) {
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.Action.REPEAT : KeyEventServerMessage.Action.DOWN, key));
@@ -57,6 +57,16 @@ public final class ClientInputHandler implements InputHandler {
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.UP, key));
}
@Override
public void charTyped(byte chr) {
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.CHAR, chr));
}
@Override
public void paste(ByteBuffer contents) {
ClientNetworking.sendToServer(new PasteEventComputerMessage(menu, contents));
}
@Override
public void mouseClick(int button, int x, int y) {
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.CLICK, button, x, y));

View File

@@ -11,8 +11,8 @@ 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 org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.stream.Stream;
@@ -32,10 +32,13 @@ public final class GuiSprites extends TextureAtlasHolder {
public static final ComputerTextures COMPUTER_COMMAND = computer("command", false, true);
public static final ComputerTextures COMPUTER_COLOUR = computer("colour", true, false);
public static final ResourceLocation TURTLE_NORMAL_SELECTED_SLOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/turtle_normal_selected_slot");
public static final ResourceLocation TURTLE_ADVANCED_SELECTED_SLOT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/turtle_advanced_selected_slot");
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")
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/buttons/" + name),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "gui/sprites/buttons/" + name + "_hover")
);
}

View File

@@ -15,9 +15,9 @@ import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.MenuAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import org.jspecify.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import javax.annotation.Nullable;
import java.util.Objects;
import static dan200.computercraft.core.util.Nullability.assertNonNull;

View File

@@ -12,8 +12,8 @@ import net.minecraft.client.gui.components.MultiLineLabel;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.List;
import static dan200.computercraft.core.util.Nullability.assertNonNull;

View File

@@ -149,6 +149,7 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
// Skip rendering labels.
}
@SuppressWarnings("ArrayRecordComponent")
record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) {
public static final PrintoutInfo DEFAULT;

View File

@@ -54,9 +54,9 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
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,
0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE
graphics.blit(
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0, 22, 22,
GuiSprites.get(advanced ? GuiSprites.TURTLE_ADVANCED_SELECTED_SLOT : GuiSprites.TURTLE_NORMAL_SELECTED_SLOT)
);
}

View File

@@ -55,7 +55,7 @@ public final class ComputerSidebar {
add.accept(new DynamicImageButton(
x, y, ICON_WIDTH, ICON_HEIGHT,
GuiSprites.TERMINATE::get,
b -> input.queueEvent("terminate"),
b -> input.terminate(),
new HintedMessage(
Component.translatable("gui.computercraft.tooltip.terminate"),
Component.translatable("gui.computercraft.tooltip.terminate.key")

View File

@@ -12,8 +12,8 @@ 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 org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.function.Supplier;
/**

View File

@@ -71,11 +71,8 @@ public class TerminalWidget extends AbstractWidget {
@Override
public boolean charTyped(char ch, int modifiers) {
if (ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255) {
// Queue the char event for any printable chars in byte range
computer.queueEvent("char", new Object[]{ Character.toString(ch) });
}
var terminalChar = StringUtil.unicodeToTerminal(ch);
if (StringUtil.isTypableChar(terminalChar)) computer.charTyped((byte) terminalChar);
return true;
}
@@ -112,8 +109,8 @@ public class TerminalWidget extends AbstractWidget {
}
private void paste() {
var clipboard = StringUtil.normaliseClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
var clipboard = StringUtil.getClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
if (clipboard.remaining() > 0) computer.paste(clipboard);
}
@Override
@@ -222,7 +219,7 @@ public class TerminalWidget extends AbstractWidget {
public void update() {
if (terminateTimer >= 0 && terminateTimer < TERMINATE_TIME && (terminateTimer += 0.05f) > TERMINATE_TIME) {
computer.queueEvent("terminate");
computer.terminate();
}
if (shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME) {

View File

@@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.model;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.pocket.PocketComputerData;
import dan200.computercraft.client.render.CustomLecternRenderer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.model.Material;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FastColor;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.item.ItemStack;
/**
* A model for {@linkplain PocketComputerItem pocket computers} placed on a lectern.
*
* @see CustomLecternRenderer
*/
public class LecternPocketModel {
public static final ResourceLocation TEXTURE_NORMAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_normal");
public static final ResourceLocation TEXTURE_ADVANCED = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_advanced");
public static final ResourceLocation TEXTURE_COLOUR = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_colour");
public static final ResourceLocation TEXTURE_FRAME = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_frame");
public static final ResourceLocation TEXTURE_LIGHT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_light");
private static final Material MATERIAL_NORMAL = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_NORMAL);
private static final Material MATERIAL_ADVANCED = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_ADVANCED);
private static final Material MATERIAL_COLOUR = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_COLOUR);
private static final Material MATERIAL_FRAME = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_FRAME);
private static final Material MATERIAL_LIGHT = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_LIGHT);
// The size of the terminal within the model.
public static final float TERM_WIDTH = 12.0f / 32.0f;
public static final float TERM_HEIGHT = 14.0f / 32.0f;
// The size of the texture. The texture is 36x36, but is at 2x resolution.
private static final int TEXTURE_WIDTH = 48 / 2;
private static final int TEXTURE_HEIGHT = 48 / 2;
private final ModelPart root;
public LecternPocketModel() {
root = buildPages();
}
private static ModelPart buildPages() {
var mesh = new MeshDefinition();
var parts = mesh.getRoot();
parts.addOrReplaceChild(
"root",
CubeListBuilder.create().texOffs(0, 0).addBox(0f, -5.0f, -4.0f, 1f, 10.0f, 8.0f),
PartPose.ZERO
);
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
}
private void render(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int colour) {
int red = FastColor.ARGB32.red(colour), green = FastColor.ARGB32.green(colour), blue = FastColor.ARGB32.blue(colour), alpha = FastColor.ARGB32.alpha(colour);
root.render(poseStack, buffer, packedLight, packedOverlay, red / 255.0f, green / 255.0f, blue / 255.0f, alpha / 255.0f);
}
/**
* Render the pocket computer model.
*
* @param poseStack The current pose stack.
* @param bufferSource The buffer source to draw to.
* @param packedLight The current light level.
* @param packedOverlay The overlay texture (used for entity hurt animation).
* @param family The computer family.
* @param frameColour The pocket computer's {@linkplain PocketComputerItem#getColour(ItemStack) colour}.
* @param lightColour The pocket computer's {@linkplain PocketComputerData#getLightState() light colour}.
*/
public void render(PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, ComputerFamily family, int frameColour, int lightColour) {
if (frameColour != -1) {
root.render(poseStack, MATERIAL_FRAME.buffer(bufferSource, RenderType::entityCutout), packedLight, packedOverlay, 1, 1, 1, 1);
render(poseStack, MATERIAL_COLOUR.buffer(bufferSource, RenderType::entityCutout), packedLight, packedOverlay, frameColour);
} else {
var buffer = (family == ComputerFamily.ADVANCED ? MATERIAL_ADVANCED : MATERIAL_NORMAL).buffer(bufferSource, RenderType::entityCutout);
root.render(poseStack, buffer, packedLight, packedOverlay, 1, 1, 1, 1);
}
render(poseStack, MATERIAL_LIGHT.buffer(bufferSource, RenderType::entityCutout), LightTexture.FULL_BRIGHT, packedOverlay, lightColour);
}
}

View File

@@ -13,8 +13,8 @@ import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;

View File

@@ -20,8 +20,8 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

View File

@@ -26,8 +26,8 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.UUID;
/**

View File

@@ -13,8 +13,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ServerGamePacketListener;
import net.minecraft.sounds.SoundEvent;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
static ClientPlatformHelper get() {
@@ -39,7 +38,7 @@ public interface ClientPlatformHelper extends dan200.computercraft.impl.client.C
* @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);
void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, int @Nullable [] tints);
/**
* Play a record at a particular position.

View File

@@ -10,8 +10,8 @@ import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

View File

@@ -8,8 +8,7 @@ import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Clientside data about a pocket computer.

View File

@@ -6,15 +6,28 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.client.model.LecternPocketModel;
import dan200.computercraft.client.model.LecternPrintoutModel;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import dan200.computercraft.shared.util.ARGB32;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
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.renderer.blockentity.LecternRenderer;
import net.minecraft.world.level.block.LecternBlock;
import net.minecraft.world.phys.Vec3;
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
/**
* A block entity renderer for our {@linkplain CustomLecternBlockEntity lectern}.
@@ -22,10 +35,17 @@ import net.minecraft.world.level.block.LecternBlock;
* This largely follows {@link LecternRenderer}, but with support for multiple types of item.
*/
public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternBlockEntity> {
private static final int POCKET_TERMINAL_RENDER_DISTANCE = 32;
private final BlockEntityRenderDispatcher berDispatcher;
private final LecternPrintoutModel printoutModel;
private final LecternPocketModel pocketModel;
public CustomLecternRenderer(BlockEntityRendererProvider.Context context) {
berDispatcher = context.getBlockEntityRenderDispatcher();
printoutModel = new LecternPrintoutModel();
pocketModel = new LecternPocketModel();
}
@Override
@@ -44,8 +64,46 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
} else {
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutItem.getPageCount(item));
}
} else if (item.getItem() instanceof PocketComputerItem pocket) {
var computer = ClientPocketComputers.get(item);
pocketModel.render(
poseStack, buffer, packedLight, packedOverlay, pocket.getFamily(), pocket.getColour(item),
ARGB32.opaque(computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState())
);
// Jiggle the terminal about a bit, so (0, 0) is in the top left of the model's terminal hole.
poseStack.mulPose(Axis.YP.rotationDegrees(90f));
poseStack.translate(-0.5 * LecternPocketModel.TERM_WIDTH, 0.5 * LecternPocketModel.TERM_HEIGHT + 1f / 32.0f, 1 / 16.0f);
poseStack.mulPose(Axis.XP.rotationDegrees(180));
// Either render the terminal or a black screen, depending on how close we are.
var terminal = computer == null ? null : computer.getTerminal();
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(poseStack, buffer.getBuffer(RenderTypes.TERMINAL));
if (terminal != null && Vec3.atCenterOf(lectern.getBlockPos()).closerThan(berDispatcher.camera.getPosition(), POCKET_TERMINAL_RENDER_DISTANCE)) {
renderPocketTerminal(poseStack, quadEmitter, terminal);
} else {
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, LecternPocketModel.TERM_WIDTH, LecternPocketModel.TERM_HEIGHT);
}
}
poseStack.popPose();
}
private static void renderPocketTerminal(PoseStack poseStack, FixedWidthFontRenderer.QuadEmitter quadEmitter, Terminal terminal) {
var width = terminal.getWidth() * FONT_WIDTH;
var height = terminal.getHeight() * FONT_HEIGHT;
// Scale the terminal down to fit in the available space.
var scaleX = LecternPocketModel.TERM_WIDTH / (width + MARGIN * 2);
var scaleY = LecternPocketModel.TERM_HEIGHT / (height + MARGIN * 2);
var scale = Math.min(scaleX, scaleY);
poseStack.scale(scale, scale, -1.0f);
// Convert the model dimensions to terminal space, then find out how large the margin should be.
var marginX = ((LecternPocketModel.TERM_WIDTH / scale) - width) / 2;
var marginY = ((LecternPocketModel.TERM_HEIGHT / scale) - height) / 2;
FixedWidthFontRenderer.drawTerminal(quadEmitter, marginX, marginY, terminal, marginY, marginY, marginX, marginX);
}
}

View File

@@ -14,8 +14,8 @@ 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 org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.List;
/**
@@ -39,7 +39,7 @@ public final class ModelRenderer {
* @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) {
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, int @Nullable [] tints) {
var matrix = transform.last();
var inverted = matrix.pose().determinant() < 0;

View File

@@ -38,8 +38,8 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
int termWidth, termHeight;
if (terminal == null) {
termWidth = Config.pocketTermWidth;
termHeight = Config.pocketTermHeight;
termWidth = Config.DEFAULT_POCKET_TERM_WIDTH;
termHeight = Config.DEFAULT_POCKET_TERM_HEIGHT;
} else {
termWidth = terminal.getWidth();
termHeight = terminal.getHeight();

View File

@@ -16,8 +16,8 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceProvider;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Objects;
import java.util.function.BiConsumer;

View File

@@ -23,8 +23,7 @@ import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
@@ -127,7 +126,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
transform.popPose();
}
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, @Nullable int[] tints) {
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, int @Nullable [] tints) {
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
renderModel(transform, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
}
@@ -143,7 +142,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
* @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) {
private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, int @Nullable [] tints) {
ClientPlatformHelper.get().renderBakedModel(transform, renderer, model, lightmapCoord, overlayLight, tints);
}

View File

@@ -27,11 +27,11 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.jspecify.annotations.Nullable;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL31;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.function.Consumer;

View File

@@ -11,12 +11,12 @@ import dan200.computercraft.client.render.vbo.DirectVertexBuffer;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import net.minecraft.core.BlockPos;
import org.jspecify.annotations.Nullable;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Set;

View File

@@ -14,12 +14,12 @@ import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.core.util.Colour;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.server.packs.resources.ResourceProvider;
import org.jspecify.annotations.Nullable;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL31;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.ByteBuffer;

View File

@@ -43,7 +43,7 @@ public final class FixedWidthFontRenderer {
static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH;
private static final int BLACK = FastColor.ARGB32.color(255, byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()));
private static final float Z_OFFSET = 1e-3f;
private static final float Z_OFFSET = 1e-4f;
private FixedWidthFontRenderer() {
}

View File

@@ -10,9 +10,9 @@ import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.sounds.AudioStream;
import net.minecraft.client.sounds.SoundEngine;
import org.jspecify.annotations.Nullable;
import org.lwjgl.BufferUtils;
import javax.annotation.Nullable;
import javax.sound.sampled.AudioFormat;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

View File

@@ -10,8 +10,7 @@ import dan200.computercraft.shared.peripheral.speaker.EncodedAudio;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* An instance of a speaker, which is either playing a {@link DfpwmStream} stream or a normal sound.
@@ -25,7 +24,7 @@ public class SpeakerInstance {
SpeakerInstance() {
}
private void pushAudio(EncodedAudio buffer) {
private void pushAudio(EncodedAudio buffer, float volume) {
var sound = this.sound;
var stream = currentStream;
@@ -33,18 +32,30 @@ public class SpeakerInstance {
var exhausted = stream.isEmpty();
stream.push(buffer);
// If we've got nothing left in the buffer, enqueue an additional one just in case.
if (exhausted && sound != null && sound.stream == stream && stream.channel != null && stream.executor != null) {
if (sound == null) return;
var volumeChanged = sound.setVolume(volume);
if ((exhausted || volumeChanged) && sound.stream == stream && stream.channel != null && stream.executor != null) {
var actualStream = sound.stream;
stream.executor.execute(() -> {
var channel = Nullability.assertNonNull(actualStream.channel);
if (!channel.stopped()) channel.pumpBuffers(1);
if (channel.stopped()) return;
// If we've got nothing left in the buffer, enqueue an additional one just in case.
if (exhausted) channel.pumpBuffers(1);
// Update the attenuation if the volume has changed: SoundEngine.tickNonPaused updates the volume
// itself, but leaves the attenuation unchanged. We mirror the logic of SoundEngine.play here.
if (volumeChanged) {
channel.linearAttenuation(Math.max(volume, 1) * sound.getSound().getAttenuationDistance());
}
});
}
}
public void playAudio(SpeakerPosition position, float volume, EncodedAudio buffer) {
pushAudio(buffer);
pushAudio(buffer, volume);
var soundManager = Minecraft.getInstance().getSoundManager();

View File

@@ -16,8 +16,8 @@ import net.minecraft.client.sounds.SoundBufferLibrary;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.concurrent.CompletableFuture;
/**
@@ -83,4 +83,10 @@ public class SpeakerSound extends AbstractSoundInstance implements TickableSound
public @Nullable AudioStream getStream() {
return stream;
}
boolean setVolume(float volume) {
if (volume == this.volume) return false;
this.volume = volume;
return true;
}
}

View File

@@ -11,7 +11,7 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
import java.util.List;

View File

@@ -6,6 +6,7 @@ package dan200.computercraft.data;
import com.mojang.serialization.Codec;
import dan200.computercraft.client.gui.GuiSprites;
import dan200.computercraft.client.model.LecternPocketModel;
import dan200.computercraft.client.model.LecternPrintoutModel;
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
@@ -54,9 +55,12 @@ public final class DataProviders {
out.accept(new ResourceLocation("blocks"), makeSprites(Stream.of(
UpgradeSlot.LEFT_UPGRADE,
UpgradeSlot.RIGHT_UPGRADE,
LecternPrintoutModel.TEXTURE
LecternPrintoutModel.TEXTURE,
LecternPocketModel.TEXTURE_NORMAL, LecternPocketModel.TEXTURE_ADVANCED,
LecternPocketModel.TEXTURE_COLOUR, LecternPocketModel.TEXTURE_FRAME, LecternPocketModel.TEXTURE_LIGHT
)));
out.accept(GuiSprites.SPRITE_SHEET, makeSprites(
Stream.of(GuiSprites.TURTLE_NORMAL_SELECTED_SLOT, GuiSprites.TURTLE_ADVANCED_SELECTED_SLOT),
// Buttons
GuiSprites.TURNED_OFF.textures(),
GuiSprites.TURNED_ON.textures(),

View File

@@ -10,8 +10,8 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.crafting.RecipeSerializer;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

View File

@@ -6,7 +6,7 @@ import dan200.computercraft.api.lua.Coerced;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.turtle.ITurtleAccess;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
/**
* An example API that will be available on every turtle. This demonstrates both registering an API, and how to write

View File

@@ -3,6 +3,7 @@ package com.example.examplemod;
import com.example.examplemod.data.TurtleDataProvider;
import com.example.examplemod.peripheral.FurnacePeripheral;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
/**
@@ -34,6 +35,16 @@ public final class ExampleMod {
ComputerCraftAPI.registerGenericSource(new FurnacePeripheral());
// @end region=generic_source
// @start region=details
VanillaDetailRegistries.ITEM_STACK.addProvider((out, stack) -> {
var food = stack.getItem().getFoodProperties();
if (food == null) return;
out.put("saturation", food.getSaturationModifier());
out.put("nutrition", food.getNutrition());
});
// @end region=details
ExampleAPI.register();
}
}

View File

@@ -0,0 +1,49 @@
package com.example.examplemod;
import dan200.computercraft.api.detail.DetailRegistry;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.pocket.IPocketAccess;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.LivingEntity;
import org.jspecify.annotations.Nullable;
import java.util.Map;
/**
* An example peripheral for pocket computers. This currently doesn't have an associated upgrade it mostly exists to
* demonstrate other functionality.
*/
public class ExamplePocketPeripheral implements IPeripheral {
private final IPocketAccess pocket;
public ExamplePocketPeripheral(IPocketAccess pocket) {
this.pocket = pocket;
}
@Override
public String getType() {
return "example";
}
/**
* An example of using {@linkplain DetailRegistry detail registries} to get the current player's held item.
*
* @return The item details, or {@code null} if the player is not holding an item.
*/
// @start region=details
@LuaFunction(mainThread = true)
public final @Nullable Map<String, ?> getHeldItem() {
if (!(pocket.getEntity() instanceof LivingEntity entity)) return null;
var heldItem = entity.getItemInHand(InteractionHand.MAIN_HAND);
return heldItem.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(heldItem);
}
// @end region=details
@Override
public boolean equals(@Nullable IPeripheral other) {
return other instanceof ExamplePocketPeripheral o && pocket == o.pocket;
}
}

View File

@@ -3,7 +3,7 @@ package com.example.examplemod.peripheral;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
/**
* A peripheral that adds a {@code getFuel()} method to brewing stands. This demonstrates the usage of

View File

@@ -4,7 +4,7 @@ import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.AttachedComputerSet;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.Nullable;
/**
* A peripheral that tracks what computers it is attached to.

View File

@@ -1,11 +1,13 @@
{
"sources": [
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/turned_off"},
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/turned_off_hover"},
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/turned_on"},
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/turned_on_hover"},
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/terminate"},
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/terminate_hover"},
{"type": "minecraft:single", "resource": "computercraft:gui/sprites/turtle_normal_selected_slot"},
{"type": "minecraft:single", "resource": "computercraft:gui/sprites/turtle_advanced_selected_slot"},
{"type": "minecraft:single", "resource": "computercraft:gui/sprites/buttons/turned_off"},
{"type": "minecraft:single", "resource": "computercraft:gui/sprites/buttons/turned_off_hover"},
{"type": "minecraft:single", "resource": "computercraft:gui/sprites/buttons/turned_on"},
{"type": "minecraft:single", "resource": "computercraft:gui/sprites/buttons/turned_on_hover"},
{"type": "minecraft:single", "resource": "computercraft:gui/sprites/buttons/terminate"},
{"type": "minecraft:single", "resource": "computercraft:gui/sprites/buttons/terminate_hover"},
{"type": "minecraft:single", "resource": "computercraft:gui/border_normal"},
{"type": "minecraft:single", "resource": "computercraft:gui/pocket_bottom_normal"},
{"type": "minecraft:single", "resource": "computercraft:gui/sidebar_normal"},

View File

@@ -2,6 +2,11 @@
"sources": [
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"},
{"type": "minecraft:single", "resource": "computercraft:entity/printout"}
{"type": "minecraft:single", "resource": "computercraft:entity/printout"},
{"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_normal"},
{"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_advanced"},
{"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_colour"},
{"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_frame"},
{"type": "minecraft:single", "resource": "computercraft:entity/pocket_computer_light"}
]
}

View File

@@ -11,7 +11,7 @@ import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.filesystem.WritableMount;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.MediaProvider;
import dan200.computercraft.api.media.PrintoutContents;
import dan200.computercraft.api.network.PacketNetwork;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.network.wired.WiredNode;
@@ -26,6 +26,7 @@ import dan200.computercraft.shared.computer.core.ResourceMount;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.details.BlockDetails;
import dan200.computercraft.shared.details.ItemDetails;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
@@ -34,11 +35,12 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.stream.Stream;
public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIService {
private final DetailRegistry<ItemStack> itemStackDetails = new DetailRegistryImpl<>(ItemDetails::fillBasic);
@@ -90,11 +92,6 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
return BundledRedstone.getDefaultOutput(world, pos, side);
}
@Override
public final void registerMediaProvider(MediaProvider provider) {
MediaProviders.register(provider);
}
@Override
public final PacketNetwork getWirelessNetwork(MinecraftServer server) {
return ServerContext.get(server).wirelessNetwork();
@@ -134,4 +131,21 @@ public abstract class AbstractComputerCraftAPI implements ComputerCraftAPIServic
public final DetailRegistry<BlockReference> getBlockInWorldDetailRegistry() {
return blockDetails;
}
@Override
public @Nullable PrintoutContents getPrintoutContents(ItemStack stack) {
return stack.getItem() instanceof PrintoutItem ? new PrintoutContentsImpl(stack) : null;
}
public record PrintoutContentsImpl(ItemStack stack) implements PrintoutContents {
@Override
public String getTitle() {
return PrintoutItem.getTitle(stack);
}
@Override
public Stream<String> getTextLines() {
return Stream.of(PrintoutItem.getText(stack));
}
}
}

View File

@@ -1,46 +0,0 @@
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.impl;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.MediaProvider;
import net.minecraft.world.item.ItemStack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
public final class MediaProviders {
private static final Logger LOG = LoggerFactory.getLogger(MediaProviders.class);
private static final Set<MediaProvider> providers = new LinkedHashSet<>();
private MediaProviders() {
}
public static synchronized void register(MediaProvider provider) {
Objects.requireNonNull(provider, "provider cannot be null");
providers.add(provider);
}
public static @Nullable IMedia get(ItemStack stack) {
if (stack.isEmpty()) return null;
// Try the handlers in order:
for (var mediaProvider : providers) {
try {
var media = mediaProvider.getMedia(stack);
if (media != null) return media;
} catch (Exception e) {
// Mod misbehaved, ignore it
LOG.error("Media provider " + mediaProvider + " errored.", e);
}
}
return null;
}
}

View File

@@ -18,10 +18,10 @@ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.item.ItemStack;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;

View File

@@ -5,11 +5,10 @@
package dan200.computercraft.impl.network.wired;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
/**
* Verifies certain elements of a network are well-formed.
* <p>

View File

@@ -5,8 +5,8 @@
package dan200.computercraft.impl.network.wired;
import dan200.computercraft.api.network.wired.WiredNode;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Objects;
/**

View File

@@ -11,8 +11,8 @@ import dan200.computercraft.api.network.wired.WiredNetwork;
import dan200.computercraft.api.network.wired.WiredNode;
import dan200.computercraft.api.network.wired.WiredSender;
import dan200.computercraft.api.peripheral.IPeripheral;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;

View File

@@ -30,8 +30,8 @@ import net.minecraft.world.level.storage.loot.BuiltInLootTables;
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.entries.LootTableReference;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -101,7 +101,7 @@ public final class CommonHooks {
BuiltInLootTables.VILLAGE_CARTOGRAPHER
);
public static @Nullable LootPool.Builder getExtraLootPool(ResourceLocation lootTable) {
public static LootPool.@Nullable Builder getExtraLootPool(ResourceLocation lootTable) {
if (!lootTable.getNamespace().equals("minecraft") || !TREASURE_DISK_LOOT_TABLES.contains(lootTable)) {
return null;
}

View File

@@ -10,6 +10,8 @@ import dan200.computercraft.api.component.ComputerComponents;
import dan200.computercraft.api.detail.DetailProvider;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.api.upgrades.UpgradeData;
@@ -28,6 +30,7 @@ import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.computer.items.CommandComputerItem;
import dan200.computercraft.shared.computer.items.ComputerItem;
@@ -42,15 +45,14 @@ import dan200.computercraft.shared.details.ItemDetails;
import dan200.computercraft.shared.integration.PermissionRegistry;
import dan200.computercraft.shared.lectern.CustomLecternBlock;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.media.MountMedia;
import dan200.computercraft.shared.media.PrintoutMenu;
import dan200.computercraft.shared.media.items.DiskItem;
import dan200.computercraft.shared.media.items.PrintoutItem;
import dan200.computercraft.shared.media.items.RecordMedia;
import dan200.computercraft.shared.media.items.TreasureDiskItem;
import dan200.computercraft.shared.media.items.*;
import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
@@ -89,11 +91,11 @@ import dan200.computercraft.shared.turtle.recipes.TurtleOverlayRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.ComponentMap;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.cauldron.CauldronInteraction;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
@@ -103,6 +105,7 @@ import net.minecraft.world.item.*;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -112,6 +115,7 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.material.MapColor;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import org.jspecify.annotations.Nullable;
import java.util.Objects;
import java.util.function.BiFunction;
@@ -462,16 +466,10 @@ public final class ModRegistry {
// Register bundled power providers
ComputerCraftAPI.registerBundledRedstoneProvider(new DefaultBundledRedstoneProvider());
ComputerCraftAPI.registerRefuelHandler(new FurnaceRefuelHandler());
ComputerCraftAPI.registerMediaProvider(stack -> {
var item = stack.getItem();
if (item instanceof IMedia media) return media;
if (item instanceof RecordItem) return RecordMedia.INSTANCE;
return null;
});
ComputerCraftAPI.registerAPIFactory(computer -> {
var turtle = computer.getComponent(ComputerComponents.TURTLE);
var metrics = Objects.requireNonNull(computer.getComponent(ComponentMap.METRICS));
var metrics = Objects.requireNonNull(computer.getComponent(ServerComputer.METRICS));
return turtle == null ? null : new TurtleAPI(metrics, (TurtleAccessInternal) turtle);
});
@@ -497,6 +495,76 @@ public final class ModRegistry {
CauldronInteraction.WATER.put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
}
/**
* Register our peripherals.
*
* @param peripherals The object to register our peripheral capability/lookups with.
*/
public static void registerPeripherals(BlockComponent<IPeripheral, Direction> peripherals) {
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.COMPUTER_NORMAL.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.COMPUTER_ADVANCED.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.SPEAKER.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.PRINTER.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.DISK_DRIVE.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.WIRELESS_MODEM_NORMAL.get(), WirelessModemBlockEntity::getPeripheral);
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.WIRELESS_MODEM_ADVANCED.get(), WirelessModemBlockEntity::getPeripheral);
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.WIRED_MODEM_FULL.get(), WiredModemFullBlockEntity::getPeripheral);
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.CABLE.get(), CableBlockEntity::getPeripheral);
peripherals.registerForBlockEntity(ModRegistry.BlockEntities.REDSTONE_RELAY.get(), (b, d) -> b.peripheral());
peripherals.registerForBlockEntity(BlockEntityType.COMMAND_BLOCK, (b, d) -> Config.enableCommandBlock ? new CommandBlockPeripheral(b) : null);
}
public static void registerWiredElements(BlockComponent<WiredElement, Direction> wiredElements) {
wiredElements.registerForBlockEntity(ModRegistry.BlockEntities.WIRED_MODEM_FULL.get(), (b, d) -> b.getElement());
wiredElements.registerForBlockEntity(ModRegistry.BlockEntities.CABLE.get(), CableBlockEntity::getWiredElement);
}
/**
* Register our custom {@link IMedia} implementations.
*
* @param media The object to register our media capabilities/lookups with.
*/
public static void registerMedia(ItemComponent<IMedia> media) {
media.registerForItems((s, c) -> MountMedia.COMPUTER,
ModRegistry.Items.COMPUTER_NORMAL.get(), ModRegistry.Items.COMPUTER_ADVANCED.get(),
ModRegistry.Items.TURTLE_NORMAL.get(), ModRegistry.Items.TURTLE_ADVANCED.get(),
ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get()
);
media.registerForItems((s, c) -> MountMedia.DISK, ModRegistry.Items.DISK.get());
media.registerForItems((s, c) -> TreasureDiskMedia.INSTANCE, ModRegistry.Items.TREASURE_DISK.get());
media.registerFallback((stack, ctx) -> {
if (stack.getItem() instanceof IMedia m) return m;
if (stack.getItem() instanceof RecordItem) return RecordMedia.INSTANCE;
return null;
});
}
/**
* An abstraction for registering capabilities/block lookups for blocks and block entities.
*
* @param <T> The type of the component.
* @param <C> The context parameter to the component.
*/
public interface BlockComponent<T, C extends @Nullable Object> {
<B extends BlockEntity> void registerForBlockEntity(BlockEntityType<B> blockEntityType, BiFunction<? super B, C, @Nullable T> provider);
}
/**
* An abstraction for registering capabilities/block lookups for items.
*
* @param <T> The type of the component.
*/
public interface ItemComponent<T> {
void registerForItems(BiFunction<ItemStack, @Nullable Void, @Nullable T> provider, ItemLike... items);
void registerFallback(BiFunction<ItemStack, @Nullable Void, @Nullable T> provider);
}
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {
out.accept(turtle.create(-1, null, -1, null, null, 0, null));
TurtleUpgrades.getVanillaUpgrades()

View File

@@ -24,20 +24,18 @@ import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
import dan200.computercraft.shared.computer.metrics.basic.BasicComputerMetricsObserver;
import dan200.computercraft.shared.computer.metrics.basic.ComputerMetrics;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.RelativeMovement;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.io.File;
import java.util.*;
@@ -260,18 +258,11 @@ public final class CommandComputerCraft {
* @return The constant {@code 1}.
*/
private static int view(CommandSourceStack source, ServerComputer computer) throws CommandSyntaxException {
var player = source.getPlayerOrException();
new ComputerContainerData(computer, ItemStack.EMPTY).open(player, new MenuProvider() {
@Override
public Component getDisplayName() {
return Component.translatable("gui.computercraft.view_computer");
}
@Override
public AbstractContainerMenu createMenu(int id, Inventory player, Player entity) {
return new ComputerMenuWithoutInventory(ModRegistry.Menus.COMPUTER.get(), id, player, p -> true, computer);
}
});
PlatformHelper.get().openMenu(
source.getPlayerOrException(), Component.translatable("gui.computercraft.view_computer"),
(id, player, entity) -> new ComputerMenuWithoutInventory(ModRegistry.Menus.COMPUTER.get(), id, player, p -> true, computer),
new ComputerContainerData(computer, ItemStack.EMPTY)
);
return 1;
}

View File

@@ -22,8 +22,8 @@ import net.minecraft.network.chat.ComponentContents;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
@@ -43,11 +43,11 @@ public record ComputerSelector(
@Nullable String label,
@Nullable ComputerFamily family,
@Nullable AABB bounds,
@Nullable MinMaxBounds.Doubles range
MinMaxBounds.@Nullable Doubles range
) {
private static final ComputerSelector all = new ComputerSelector("@c[]", OptionalInt.empty(), null, OptionalInt.empty(), null, null, null, null);
private static UuidArgument uuidArgument = UuidArgument.uuid();
private static final UuidArgument uuidArgument = UuidArgument.uuid();
/**
* A {@link ComputerSelector} which matches all computers.
@@ -279,7 +279,7 @@ public record ComputerSelector(
private @Nullable String label;
private @Nullable ComputerFamily family;
private @Nullable AABB bounds;
private @Nullable MinMaxBounds.Doubles range;
private MinMaxBounds.@Nullable Doubles range;
}
private static final Map<String, Option> options;

View File

@@ -12,8 +12,8 @@ import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.CommandNode;
import dan200.computercraft.shared.command.arguments.RepeatArgumentType;
import net.minecraft.commands.CommandSourceStack;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

View File

@@ -14,8 +14,8 @@ import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Predicate;

View File

@@ -12,8 +12,7 @@ import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* Various helpers for building chat messages.

View File

@@ -7,8 +7,7 @@ package dan200.computercraft.shared.command.text;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
public class ServerTableFormatter implements TableFormatter {
private final CommandSourceStack source;

View File

@@ -11,15 +11,15 @@ import dan200.computercraft.shared.network.server.ServerNetworking;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class TableBuilder {
private final String id;
private int columns = -1;
private final @Nullable Component[] headers;
private final Component @Nullable [] headers;
private final ArrayList<Component[]> rows = new ArrayList<>();
private int additional;
@@ -72,8 +72,7 @@ public class TableBuilder {
return columns;
}
@Nullable
public Component[] getHeaders() {
public Component @Nullable [] getHeaders() {
return headers;
}

View File

@@ -7,8 +7,7 @@ package dan200.computercraft.shared.command.text;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;

View File

@@ -23,8 +23,7 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.BlockHitResult;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
/**
* A block which has a container and can be placed in a horizontal direction.

View File

@@ -9,6 +9,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.core.BlockPos;
@@ -35,8 +36,8 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.BlockHitResult;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.List;
public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock {
@@ -161,7 +162,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
var serverComputer = computer.createServerComputer();
serverComputer.turnOn();
new ComputerContainerData(serverComputer, getItem(computer)).open(player, computer);
PlatformHelper.get().openMenu(player, computer.getName(), computer, new ComputerContainerData(serverComputer, getItem(computer)));
}
return InteractionResult.sidedSuccess(level.isClientSide);
}

View File

@@ -26,19 +26,20 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.Container;
import net.minecraft.world.LockCode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.Nameable;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.MenuConstructor;
import net.minecraft.world.level.block.GameMasterBlock;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.jspecify.annotations.Nullable;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.UUID;
public abstract class AbstractComputerBlockEntity extends BlockEntity implements Nameable, MenuProvider {
public abstract class AbstractComputerBlockEntity extends BlockEntity implements Nameable, MenuConstructor {
private static final String NBT_ID = "ComputerId";
private static final String NBT_LABEL = "Label";
private static final String NBT_ON = "On";
@@ -310,6 +311,10 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
return label;
}
public final boolean isAdminOnly() {
return getBlockState().getBlock() instanceof GameMasterBlock;
}
public final void setComputerID(int id) {
if (getLevel().isClientSide || computerID == id) return;
@@ -417,4 +422,9 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
public Component getDisplayName() {
return Nameable.super.getDisplayName();
}
@Override
public boolean onlyOpCanSetNbt() {
return isAdminOnly();
}
}

View File

@@ -17,8 +17,7 @@ import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import javax.annotation.Nullable;
import org.jspecify.annotations.Nullable;
public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComputerBlock<T> {
public static final EnumProperty<ComputerState> STATE = EnumProperty.create("state", ComputerState.class);

Some files were not shown because too many files have changed in this diff Show More